写点什么

极客时间架构 1 期:第 2 周框架设计 - 学习总结

用户头像
Null
关注
发布于: 2020 年 09 月 27 日

面向对象编程思想的由来

摘自 https://www.cnblogs.com/baishuchao/articles/9043166.html

1940 年以前:面向机器

最早的程序设计都是采用机器语言来编写的,直接使用二进制码来表示机器能够识别和执行的指令和数 据。简单来说,就是直接编写 0 和 1 的序列来代表程序语言。例如:使用 0000 代表 加载(LOAD),0001 代表 存储(STORE)等。

机器语言由机器直接执行,速度快,但一个很明显的缺点就是:写起来实在是太困难了,一旦你发现自己

写错了,改起来更蛋疼!这样直接导致程序编写效率十分低下,编写程序花费的时间往往是实际运行时间

的几十倍或几百倍。

有一个关于机器语言和比尔盖茨的笑话,是说比尔盖茨拿着绣花针在一张光盘上戳,把 Windows 给戳出 来了!但如果真的让你去戳,不要说 Windows,连一个简单的“Hello world”都要让人戳到眼睛冒烟!

由于机器语言实在是太难编写了,于是就发展出了汇编语言。汇编语言亦称符号语言,用助记符代替机器 指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址,。汇编语言由于是采用 了助记符号来编写程序,比用机器语言的二进制代码编程要方便些,在一定程度上简化了编程过程。例如 使用 LOAD 来代替 0000,使用 STORE 来代替 0001。

即使汇编语言相比机器语言提升了可读性,但其本质上还是一种面向机器的语言,编写同样困难,也很容 易出错。相信很多计算机毕业的学生至今都对学校的汇编课程中的练习程序心有余悸。

脱离机器第一步:面向过程

面向机器的语言通常情况下被认为是一种“低级语言”,为了解决面向机器的语言存在的问题,计算机科 学的前辈们又创建了面向过程的语言。面向过程的语言被认为是一种“高级语言”,相比面向机器的语言 来说,面向过程的语言已经不再关注机器本身的操作指令、存储等方面,而是关注如何一步一步的解决具体的问题,即:解决问题的过程,这应该也是面向过程说法的来由。

相比面向机器的思想来说,面向过程是一次思想上的飞跃,将程序员从复杂的机器操作和运行的细节中解 放出来,转而关注具体需要解决的问题;面向过程的语言也不再需要和具体的机器绑定,从而具备了移植性和通用性;面向过程的语言本身也更加容易编写和维护。这些因素叠加起来,大大减轻了程序员的负担, 提升了程序员的工作效率,从而促进了软件行业的快速发展。

典型的面向过程的语言有:COBOL、FORTRAN、BASIC、C 语言等。

第一次软件危机:结构化程序设计

根本原因就是一些面向过程语言中的 goto 语句导致的面条式代码,极大的限制了程序的规模。结构化程序设计(英语:Structured programming),一种编程范型。它采用子程序(函数就是一种子程序)、代码区块、for 循环以及 while 循环等结构,来替换传统的 goto。希望借此来改善计算机程序的明晰性、质量以及开发时间,并且避免写出面条式代码。

随着计算机硬件的飞速发展,以及应用复杂度越来越高,软件规模越来越大,原有的程序开发方式已经越 来越不能满足需求了。1960 年代中期开始爆发了第一次软件危机,典型表现有软件质量低下、项目无法 如期完成、项目严重超支等,因为软件而导致的重大事故时有发生。例如 1963 年美国 (http://en.wikipedia.org/wiki/Mariner_1) 的水手一号火箭发射失败事故,就是因为一行 FORTRAN 代码 错误导致的。

软件危机最典型的例子莫过于 IBM 的 System/360 的操作系统开发。佛瑞德·布鲁克斯(Frederick P. Brooks, Jr.)作为项目主管,率领 2000 多个程序员夜以继日的工作,共计花费了 5000 人一年的工作量,写出将 近 100 万行的源码,总共投入 5 亿美元,是美国的“曼哈顿”原子弹计划投入的 1/4。尽管投入如此巨大, 但项目进度却一再延迟,软件质量也得不到保障。布鲁克斯后来基于这个项目经验而总结的《人月神话》 一书,成了史上最畅销的软件工程书籍。

为了解决问题,在 1968、1969 年连续召开两次著名的 NATO 会议,会议正式创造了“软件危机”一词, 并提出了针对性的解决方法“软件工程”。虽然“软件工程”提出之后也曾被视为软件领域的银弹,但后 来事实证明,软件工程同样无法解决软件危机。

差不多同一时间,“结构化程序设计”作为另外一种解决软件危机的方案被提出来了。 Edsger Dijkstra 于 1968 发表了著名的《GOTO 有害论》的论文,引起了长达数年的论战,并由此产生了结构化程序设计方 法。同时,第一个结构化的程序语言 Pascal 也在此时诞生,并迅速流行起来。

结构化程序设计的主要特点是抛弃 goto 语句,采取“自顶向下、逐步细化、模块化”的指导思想。结构化程序设计本质上还是一种面向过程的设计思想,但通过“自顶向下、逐步细化、模块化”的方法,将软 件的复杂度控制在一定范围内,从而从整体上降低了软件开发的复杂度。结构化程序方法成为了 1970 年 代软件开发的潮流。

科学研究证明,人脑存在人类短期记忆一般一次只能记住 5-9 个事物,这就是著名的 7+- 2 原理。结构化 程序设计是面向过程设计思想的一个改进,使得软件开发更加符合人类思维的 7+-2 特点。

第二次软件危机:面向对象程序设计

结构化编程的风靡在一定程度上缓解了软件危机,然而好景不长,随着硬件的快速发展,业务需求越来越

复杂,以及编程应用领域越来越广泛,第二次软件危机很快就到来了。

第二次软件危机的根本原因还是在于软件生产力远远跟不上硬件和业务的发展,相比第一次软件危机主要 体现在“复杂性”,第二次软件危机主要体现在“可扩展性”、“可维护性”上面。传统的面向过程(包括 结构化程序设计)方法已经越来越不能适应快速多变的业务需求了,软件领域迫切希望找到新的银弹来解 决软件危机,在这种背景下,面向对象的思想开始流行起来。

面向对象的思想并不是在第二次软件危机后才出现的,早在 1967 年的 Simula 语言中就开始提出来了,但 第二次软件危机促进了面向对象的发展。 面向对象真正开始流行是在 1980s 年代,主要得益于 C++的功 劳,后来的 Java、C#把面向对象推向了新的高峰。到现在为止,面向对象已经成为了主流的开发思想。

虽然面向对象开始也被当做解决软件危机的银弹,但事实证明,和软件工程一样,面向对象也不是银弹, 而只是一种新的软件方法而已。

虽然面向对象并不是解决软件危机的银弹,但和面向过程相比,面向对象的思想更加贴近人类思维的特点, 更加脱离机器思维,是一次软件设计思想上的飞跃。


面向对象编程的定义

摘自维基百科 https://zh.wikipedia.org/wiki/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1

面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程典范,同时也是一种程序开发的抽象方针。它可能包含资料属性代码方法。对象则指的是的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的资料。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象[1][2]

面向对象程序设计可以看作一种在程序中包含各种独立而又互相调用的对象的思想,这与传统的思想刚好相反:传统的程序设计主张将程序看作一系列函数的集合,或者直接就是一系列对电脑下达的指令。面向对象程序设计中的每一个对象都应该能够接受数据、处理数据并将数据传达给其它对象,因此它们都可以被看作一个小型的“机器”,即对象。目前已经被证实的是,面向对象程序设计推广了程序的灵活性和可维护性,并且在大型项目设计中广为应用。此外,支持者声称面向对象程序设计要比以往的做法更加便于学习,因为它能够让人们更简单地设计并维护程序,使得程序更加便于分析、设计、理解。反对者在某些领域对此予以否认。

当我们提到面向对象的时候,它不仅指一种程序设计方法。它更多意义上是一种程序开发方式。在这一方面,我们必须了解更多关于面向对象系统分析面向对象设计(Object Oriented Design,简称 OOD)方面的知识。许多流行的编程语言是面向对象的,它们的风格就是会透由对象来创出实例。


面向对象编程的目的和原则

设计目的

强内聚、低耦合,从而使系统,易扩展(易于增加新功能),更强壮(不容易被粗心的程序员破坏),可移植(能够在多样的环境下运行),更简单(易理解,易维护)


设计原则

为了达到上述设计目的,有人总结出了多种指导原则,“原则”是独立于编程语言的,甚至也可以用于非面向对象的编程语言中

“原则”是独立于编程语言的,甚至也可以用于非面向对象的编程语言中


设计模式

设计模式是用于解决某一种问题的通用的解决方案

设计模式也是语言中立的

设计模式贯彻了设计原则


面向对象设计原则

开闭原则(Open Close Principle)


它是面向对象最重要的设计原则,由Bertrand Meyer(勃兰特.梅耶)在 1988 年出版的《面向对象软件构造》。中提出的。


定义:开闭原则(Open-Closed Principle, OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。


提倡一个类一旦开发完成,后续增加新的功能就不应该通过修改这个类来完成,而是通过继承,增加新的类。 大家想必都听过软件需求不断变化的那个段子,在软件开发这个行当唯一不变的就是变化本身。那为什么应该对修改关闭呢,因为你一旦修改了某个类就有可能破坏系统原来的功能,就需要重新测试。其实我知道你们此刻在想什么,回忆一下自己的日常工作,有几个遵守了这个原则,都是需求来了就找到原来的类,进去改代码呗,^_^。看看有指导原则尚且如此,没有的话就更加乱套了。


那么是不是就一定不能修改原来的类的,当然不是了,我们都是成年人了,要清楚的认识到,这个世界不是非黑即白的。当我们发现原来的类已经烂到家了,当然在有条件的情况下及时重构,避免系统加速腐败。


依赖倒置原则(Dependence Inversion Principle)

定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象,抽象不能依赖实现而是实现依赖抽象,抽象属于高层模块

依赖导致原则是设计框架的核心原则


里氏替换原则(Liskov Substitution Principle)

定义:里氏代换原则(Liskov Substitution Principle, LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。


简单点说,一个软件系统中所有用到一个类的地方都替换成其子类,系统应该仍然可以正常工作。这个原则依赖面向对象的继承特性和多态特性,这个原则我们有意无意中使用的就比较多了。因为一个优秀的程序员一定面向抽象(接口)编程的,如果你不是,说明你还有很大的进步空间。


例如我们有如下的代码,一个图形的基类Shap,以及它的两个子类Rectangle ,Triangle,安装里式替换原则,所有使用Shape的地方都可以安全的替换成其子类。


//基类public abstract class Shape {    public abstract void draw();}//子类矩形public class Rectangle extends Shape {    @Override    public void draw() {        System.out.println("绘制矩形");    }}//子类三角形public class Triangle extends Shape {    @Override    public void draw() {        System.out.println("绘制三角形");    }}
复制代码


写一个使用 Shape 类的函数


public static void main(String[] args) {        //使用Shape的子类Triangle 的实例来替换Shape的实例,程序工作正常        drawShape(new Triangle());    }    private static void drawShape(Shape shape){        System.out.println("开始画图");        shape.draw();        System.out.println("结束画图");    }
复制代码


输出结果:


开始画图绘制三角形结束画图
复制代码


如上代码所示:本来drawShape()函数需要一个Shape的实例,而我们却传给他一个其子类的实例,但是它正常工作了。我们使用Shape的子类Triangle的实例来替换Shape的实例,程序工作正常。这个原则也非常重要而常用,面向抽象编程。


单一职责(Single Responsibility Principle)


这个原则顾名就可以思义,就是一个类应该只负责一个职责,术语叫:仅有一个引起其变化的原因。简单点说:一个类中应该是一组相关性很高的函数及数据的封装,个中含义请自行意会。看起来简单,但是做起来就难了,这可能是六大原则中最难以熟练掌握的一个原则了,它高度依赖程序员的自身素质及业务场景。


例如两个码农能为是否应该将一个函数写进某个类里面吵一天,最后谁也没有说服谁,最后他两成了!


接口隔离原则(Interface Segregation Principle)


其实这个原则是很容易理解的,就是让调用者依赖的接口尽可能的小。例如人类分男人和女人,男人和女人都要吃饭,但是只有女人每个月来大姨妈,那么如果你设计一个接口里面除了吃饭还有来大姨妈同时给男人和女人用就不合适了。


interface IHuman{    void eat();    void sleep();    void laiDaYiMa();//来大姨妈}
复制代码


这你让男人情何以堪,万一有个菜鸟程序员抽风了,直接给把来大姨妈的方法实现了,那后果就。。。


//男人类不需要接口中的laiDaYiMa方法class man implements IHuman{    @Override    public void eat() {            }    @Override    public void laiDaYiMa() {        //老子不来大姨妈,所以方法置空,啥也不干!    }}class woman implements IHuman{    @Override    public void eat() {            }    @Override    public void laiDaYiMa() {        System.out.println("王二狗,给老娘倒一杯热水");    }}
复制代码


上面的例子就违反了接口隔离原则,正确的做法是申明两个接口,使接口保持最小


interface IHuman{    void eat();}interface ISpecialForWoman{    void laiDaYiMa();//来大姨妈}
复制代码


男人只实现IHuman,女人实现IHuman 和ISpecialForWoman


class man implements IHuman{    @Override    public void eat() {    }}class woman implements IHuman,ISpecialForWoman{    @Override    public void eat() {    }
@Override public void laiDaYiMa() { System.out.println("王二狗,给老娘倒一杯热水"); }}
复制代码


用户头像

Null

关注

还未添加个人签名 2017.12.29 加入

还未添加个人简介

评论

发布
暂无评论
极客时间架构1期:第2周框架设计-学习总结