通过相关编程语言的发展历史,探究面向对象的思想。
面向对象发展历史
从面向过程说起
自C语言出现以来,风靡一世,操作系统、各种软件、工具使用C语言开发。更甚的是尼古拉斯·沃斯喊出的那句,“算法+数据结构=程序”。仿佛所有问题只要一步一步走下去就能解决。事实上,如果要使用面向过程的思想来处理当然也是没问题的。不过这时候,总有一个疑惑萦绕不去,这种方法是不是最优解?如果一个项目,使用面向过程的方法,新的需求出来了,是不是容易扩展?代码是否能够重用?是否容易维护?面向过程是计算机的思维,人毕竟不是机器。随着项目人数增多,代码越来越复杂,如何有一种方法来简化,面向对象就是这样的一把利器。
Simula 67 第一个面向对象语言
1967年,面向对象技术最早是在编程语言Simula 67中出现的,被认为是第一个面向对象程式设计的编程语言。它引入了所有后来面向对象程序设计语言所遵循的基础概念:对象、类、继承,但它的实现并不是很完整。Simula影响了Smalltalk以及接下来所有的面向对象设计编程语言。
Smalltalk 第一个完整实现了面向对象的语言
Alan Kay在70年代创造了 Smalltalk,被公认为历史上第二个面向对象的程序设计语言。它是世界上第一个真正把面向对象作为程序组织基础手段的编程语言。它首次明确实现了"消息"和"继承"这两个重要概念,对于"封装"和 "多态"也给出了里程碑式的解决方案。在 Smalltalk 程序中,一切程序元素(除了词法元素)都是对象,一切操作都是消息。而且,Smalltalk 的实现本身就是用 Smalltalk 写成的,这也就意味着,对于一个 Smalltalk 程序来说,它的底层也是以面向对象为基础的。Smalltalk引领了面向对象的设计思想的思潮,对其它众多的程序设计语言的产生起到了极大的推动作用。C++,C#,Objective-C,Actor,Java和Ruby等,无一不受到Smalltalk的影响。
C++ 第一个大规模使用面向对象的语言
面向对象程序设计在80年代成为了一种主导思想,这很大程度上得益于C++的流行。C++由贝尔实验室的Bjarne Strou-strup与1983年推出,C++进一步扩充和完善了C语言,成为一种面向 对象的程序设计语言。C++最开始的时候不是叫做C++,而是C with class。正是因为C++兼容C,同时又具备了面向对象的能力,所以C++至今仍旧广受欢迎。然而由于C++想要面面俱到,吸收了其他各种语言的特性,搞成了一个大杂烩。语法十分复杂,导致针对C++存在很多批评和争议。
Java是目前使用最广的面向对象语言
自Sun公司于 1995年5月由“Java之父”James Gosling推出Java以来,面向对象技术才算被推上了王座。Java至今仍旧是使用最广的面向对象编程语言,拥有全球最多的开发者。Sun公司对Java语言的解释是:“Java编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言”。所以要了解Java,面向对象是绕不过去的。对象在Java中是一等公民。
面向对象究竟为何物?
什么是对象?
那么究竟什么是对象呢?对象是相关状态和行为的软件实体。我所理解的对象其实是现实世界的客体在代码中的一个映射,或者说是一种模型。我们将客观存在进行抽象,比如一只狗,将它的大小、品种、颜色、毛长等等数据抽象为属性。将它的一些吃饭、睡觉、吠叫等行为进行抽象为方法。然后在代码世界里面就真的创造了这么一只狗对象。它具有现实世界狗的特征和动作。
什么是类?
类是创建对象的蓝图或原型,类是不占内存空间的,对象是类的实例,是需要存在内存中的。
为什么要面向对象?
面向对象是为了解决系统的可维护性,可扩展性,可重用性,面向对象的具有模块化、信息隐藏、代码重用、可扩展及易调试等优势。并且使用面向对象能够最容易地解决我们遇到的大部分问题。
面向对象的基本特征和基本原则
三大基本特征
我们从面向对象的三大特征就能体现其思想优势了。
- 封装: 将对象特有的属性和方法隐藏起来,不对外暴露,就可以在不影响其它部分的情况下修改或扩展被封装的变化部分,这是所有设计模式的基础,就是封装变化,因此封装的作用,就解决了程序的可扩展性。
- 继承: 子类继承父类,可以继承父类的方法及属性,实现了多态以及代码的重用,因此也解决了系统的重用性和扩展性,但是继承破坏了封装,因为他是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,所以继承需要慎用,只有明确的IS-A关系才能使用,同时继承在在程序开发过程中重构得到的,而不是程序设计之初就使用继承,很多面向对象开发者滥用继承,结果造成后期的代码解决不了需求的变化了。因此优先使用组合,而不是继承,是面向对象开发中一个重要的经验。
- 多态: 为不同数据类型的实体提供统一的接口就是多态。狭义上的多态就是指父类引用指向子类对象,调用方法时会调用子类的实现,而不是父类的实现,这种是运行时多态。多态需要满足三个条件:1.要有继承关系 。2.子类要重写父类的方法。 3.父类数据类型的引用指向子类对象。这里有两个概念需要区分一下,方法重载(Overload):一个方法名,参数不同。方法覆盖(Override):父类与子类有同样的方法名和参数。多态与方法覆盖有关,与方法重载无关。广义上来讲,不同的类通过实现同一接口来实现各自的功能也算是一种多态。比如鸟会飞,但是超人也会飞,通过飞这个接口,我们可以让鸟和超人,都实现这个接口,这就实现了系统的可维护性,可扩展性。
五大基本原则
- 单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。 单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
- 开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。 也就是,对扩展开放,对修改封闭的。 开放封闭原则主要体现在两个方面1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
- 里氏替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。 这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
- 依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。 具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。
- 接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口。 具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
面向对象的弊端
那么是不是有了面向对象以后,面向过程就完全无用了?当然不是。面向对象只不过是我们对现实世界进行抽象思考然后使用计算机符号进行表示的其中一种方式而已。其他的包括面向过程,面向函数,面向服务等等思想都有各自的应用场景。面向对象是主流方法,但在一些场景下,它也是有其劣势的。比如,在一个很小的项目中,使用面向过程就可以很方便直接解决的话,使用面向过程就可能会显得冗余了。而在一些需要更高抽象,更接近人类思考的实现时,函数式编程可能会是一个更优的方案。总之,技术思想无所谓好坏,主要还是看使用场景。能够最快速最方便解决问题,并且后遗症最少的方法就是最优解。
参考资料
原文地址:https://www.cnblogs.com/universal/p/10441006.html