“什么是面向对象?”这个问题每每会问到刚毕业的新手or实习生上,也是每每做为一个技术面试的开头题。在这里咱们不去谈如何答(fu)好(yan)问(guo)题(qu),仅谈谈我所理解的面向对象。
从历史上看,从20世纪60年代末期到70年代,分别有几个不一样领域都发展了面向对象的思想。好比数据抽象的研究、人工智能领域中的知识表现(框架模型)、仿真对象的管理方法(Simula)、并行计算模型(Actor)以及在结构化编程思想影响下而产生的面向对象方法。
框架模型是现实世界的模型化。从这个角度来看,“对象是对现实世界中具体事物的反映”这个观点并无错。javascript
可是无论过去怎样,如今对面向对象最好的理解是,面向对象编程是结构化编程的延伸。html
结构化编程基本上实现了控制流程的结构化。可是程序流程虽然结构化了,要处理的数据却并无被结构化。面向对象的设计方法是在结构化编程对控制流程实现告终构化后,又加上了对数据的结构化。
众多面向对象的编程思想虽不尽一致,可是不管哪一种面向对象编程语言都具备如下的共通功能。
1 . 不须要知道内部的详细处理就能够进行操做(封装、数据抽象)。
2 . 根据不一样的数据类型自动选择适当的方法(多态性)。java
最先的时候是面向过程。想象一下一堆C语言or汇编堆砌在一块儿的函数互相调(shang)用(hai)的场景————什么?你说你没学过C语言?那么你就想象一下一个复杂的SQL语句吧,有点像。程序员
评论中有人提到了C语言并不是彻底不支持面向对象,Struct就是一个不错的选择。的确,可是C语言对面向对象的支持并非那么的好。在绝大多数语言中都为Class(C++同时支持Struct和Class),但也有小部分语言沿用了这个经典的名字——好比Go语言。 在这里特别说明是为了防止误导新手
咱们以“把大象装进冰箱须要几步”这个经典的脑经急转弯来举个例子吧:面试
打开冰箱,装入大象,关上冰箱。这三步就是面向过程的思考方式,这种思想强调的是过程,也能够叫作动做。编程
open(icebox); putIn(icebox,elephant); close(icebox);
冰箱打开,冰箱存储,冰箱关闭。这就是面向对象的思考方式,这种方式强调是对象,也能够说是实例。设计模式
//咱们有一个冰箱 Icebox iceBox = new iceBox(); //可不能忘记大象,就叫它jake吧 Elephant jake = new Elephant(); icebox.open(); icebox.save(jake); icebox.close();
一种编程范式,相对于面向过程。为了方便在编程中更接近地去描述现实世界中的万物(万物皆对象),咱们将对一个事物的描述称之为类,而对象则是该事物的实例。安全
而面向对象的编程方法常见的有三种:框架
在类中,咱们把事物的属性转变为编程中的变量,把事物的行为转变为方法。编程语言
Class Elephant{ public String name; public int age; public double weight; //更多的属性...... //在这里的方法为了方便演示都是void public void eat(Food food){ //吃东西 } //更多的行为....... }
//咱们再次召唤了jake Elephant jake = new Elephant(); //他随便吃了点什么 jake.eat(new Something);
可使子类复用父类公开的变量、方法
//几百年后,jake和它的子孙们进化成了更强的大象 //它们被称为:飞象 Class FlyElephant extends Elephant{ public void fly(){ //i belive i can fly~~ } } //其中有一头飞象叫jason FlyElephant jason = new FlyElephant(); //不要问大象为何能飞! jason.fly(); //并且还能够像其余大象同样正常的吃东西 jason.eat(new Something);
若是把类看成模块,继承就是利用模块的方法。继承的思想好像有其现实的知识基础,可是把它看作纯粹的模块利用方法则更恰当。
由于继承只不过是抽象的功能利用方法,因此没必要把对继承的理解束缚在“继承是对现实事物的分类的反映”。实际上这样的想法反而妨碍了咱们对继承的理解。
屏蔽一系列的细节。使外部调用时只要知道这个方法的存在
父类的方法继承的到子类之后能够有不一样的实现方式
以类为中心的传统面向对象编程,是以类为基础生成新对象。类和对象的关系能够类比成铸模和铸件的关系。
而原型模式的面向对象编程语言没有类这样一个概念。
以JavaScript为例。须要生成新的对象时,只要给对象追加属性。设置函数对象做为属性的话,就成为方法。当访问对象中不存在的属性时,JavaScript 会去搜索该对象 prototype 属性所指向的对象。
JavaScript 利用这个功能,使用“委派”而非“继承”来实现面向对象编程。
// 生成Doge。...(1) function Doge(){ this.sit = function () {return "I'm the king of the world"} } // 从Doge 生成对象dog...(2) var doge = new Doge() // doge 是狗,因此能 sit...(3) alert(doge.sit()) // 生成新型myDoge...(4) function MyDoge () {} // 指定委派原型 MyDoge.prototype = new Dog() // 从MyDoge 生成新对象myDoge...(5) var myDoge = new MyDoge() document.write(myDoge.sit())
从原型生成对象:
和以前的Java经过类模板来实现面向对象的编程方式相比,原型对象系统支持一个更为直接的对象建立方法。例如,在 JavaScript 中,一个对象是一个简单的属性列表。每一个对象包含另外一个父类或原型 的一个特别引用,对象从父类或原型中继承行为。
传统对象系统和原型对象系统有本质的区别。传统对象被抽象地定义为概念组的一部分,从对象的其余类或组中继承一些特性。相反,原型对象被具体地定义为特定对象,从其余特定对象中继承行为。
所以,基于类的面向对象语言具备双重特性,至少须要 2 个基础结构:类和对象。因为这种双重性,随着基于类的软件的发展,复杂的类层次结构继承也将逐渐开发出来。一般没法预测出将来类须要使用的方法,所以,类层次结构须要不断重构,让更改变得更轻松。
基于原型的语言会减小上述双重性需求,促进对象的直接建立和操做。若是没有经过类来束缚对象,则会建立更为松散的类系统,这有助于维护模块性并减小重构需求。
然而话虽这么讲,一大串原型链仍是会让人头痛不已的,特别仍是在动态语言中。
继承(inheritance)是实现代码重用的有力手段,但它并不是永远是完成这项任务的最佳工做。使用不当会致使软件变得很脆弱。在包的内部使用继承是很是安全的,在那里,子类和超类的实现都处于同一个程序员的控制下。对于专门为了继承而设计的而且具备很好的文档说明的类来讲,使用继承也是很是安全的。然而,对于普通的具体类进行跨超包边界的继承则是很是危险的。本条目并不适用于接口继承(一个类实现一个接口,或者一个接口扩展另外一个接口)。
方法调用不一样的是,继承打破了封装性。子类信赖于其超类中特定功能的实现细节。超类的实现有可能会随着发行版本的不一样而有变化,子类有可能会被破坏。
在Java中,咱们老是推荐使用interface
而不是abstract class
,这样可使代码更加的灵活。在Java8后interface
也是获得了加强——能够提供默认的方法实现。
另外,Go语言也是将组合发挥到极致的语言。
一种相对于面向过程的编程范式。
面向对象设计原则是 OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数 Java 程序员追逐像 Singleton、Decorator、Observer 这样的设计模式,而不重视面向对象的分析和设计。甚至还有经验丰富的 Java 程序员没有据说过 OOPS 和 SOLID设计原则,他们根本不知道设计原则的好处,也不知道如何依照这些原则来进行编程。
众所周知,Java 编程最基本的原则就是要追求高内聚和低耦合的解决方案和代码模块设计。查看 Apache 和 Sun 的开放源代码能帮助你发现其余 Java 设计原则在这些代码中的实际运用。Java Development Kit 则遵循如下模式:BorderFactory 类中的工厂模式、Runtime 类中的单件模式。你能够经过 Joshua Bloch 的《Effective Java》一书来了解更多信息。我我的偏向的另外一种面向对象的设计模式是 Kathy Sierra 的 《Head First设计模式》 以及 《Head First Object Oriented Analysis and Design》。
虽然实际案例是学习设计原则或模式的最佳途径,但经过本文的介绍,没有接触过这些原则或还在学习阶段的 Java 程序员也可以了解这 10 个面向对象的设计原则。其实每条原则都须要大量的篇幅才能讲清楚,但我会尽力作到言简意赅。
即不要写重复的代码,而是用“abstraction”类来抽象公有的东西。若是你须要屡次用到一个硬编码值,那么能够设为公共常量;若是你要 在两个以上的地方使用一个代码块,那么能够将它设为一个独立的方法。SOLID 设计原则的优势是易于维护,但要注意,不要滥用,duplicate 不是针对代码,而是针对功能。这意味着,即便用公共代码来验证 OrderID 和 SSN,两者也不会是相同的。使用公共代码来实现两个不一样的功能,其实就是近似地把这两个功能永远捆绑到了一块儿,若是 OrderID 改变了其格式,SSN 验证代码也会中断。所以要慎用这种组合,不要随意捆绑相似但不相关的功能。
在软件领域中惟一不变的就是“Change”,所以封装你认为或猜想将来将发生变化的代码。OOPS 设计模式的优势在于易于测试和维护封转的代码。若是你使用 Java 编码,能够默认私有化变量和方法,并逐步增长访问权限,好比从 private 到 protected 和 not public。有几种 Java 设计模式也使用封装,好比 Factory 设计模式是封装“对象建立”,其灵活性使得以后引进新代码不会对现有的代码形成影响。
即对扩展开放,对修改关闭。这是另外一种很是棒的设计原则,能够防止其余人更改已经测试好的代码。理论上,能够在不修改原有的模块的基础上,扩展功能。这也是开闭原则的宗旨。
类被修改的概率很大,所以应该专一于单一的功能。若是你把多个功能放在同一个类中,功能之间就造成了关联,改变其中一个功能,有可能停止另外一个功能,这时就须要新一轮的测试来避免可能出现的问题。
这个设计原则的亮点在于任何被 DI 框架注入的类很容易用 mock 对象进行测试和维护,由于对象建立代码集中在框架中,客户端代码也不混乱。有不少方式能够实现依赖倒置,好比像 AspectJ 等的 AOP(Aspect Oriented programming)框架使用的字节码技术,或 Spring 框架使用的代理等。
若是可能的话,优先利用组合而不是继承。一些人可能会质疑,但我发现,组合比继承灵活得多。组合容许在运行期间经过设置类的属性来改变类的行为,也能够经过使用接口来组合一个类,它提供了更高的灵活性,并能够随时实现。《Effective Java》也推荐此原则。
根据该原则,子类必须可以替换掉它们的基类,也就是说使用基类的方法或函数可以顺利地引用子类对象。LSP 原则与单一职责原则和接口分离原则密切相关,若是一个类比子类具有更多功能,颇有可能某些功能会失效,这就违反了 LSP 原则。为了遵循该设计原则,派生类或子类必须加强功能。
采用多个与特定客户类有关的接口比采用一个通用的涵盖多个业务方法的接口要好。设计接口很棘手,由于一旦释放接口,你就没法在不中断执行的状况 下改变它。在 Java 中,该原则的另外一个优点在于,在任何类使用接口以前,接口不利于实现全部的方法,因此单一的功能意味着更少的实现方法。
该原则可使代码更加灵活,以即可以在任何接口实现中使用。所以,在 Java 中最好使用变量接口类型、方法返回类型、方法参数类型等。《Effective Java》 和《Head First Design Pattern》书中也有提到。
该原则最典型的例子是 Java 中的 equals () 和 hashCode () 方法。为了平等地比较两个对象,咱们用类自己而不是客户端类来作比较。这个设计原则的好处是没有重复的代码,并且很容易对其进行修改。
总之,但愿这些面向对象的设计原则能帮助你写出更灵活更好的代码。理论是第一步,更重要的是须要开发者在实践中去运用和体会。