面向对象设计7大原则

面向对象 设计模式


1. 单一职责原则(Single Responsibility Principle)

一句话概括:每一个类应该专注于做一件事情。
高逼格专业描述: 是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因的话,以提高内聚性来减少引起变化的原因。不要为类实现过多的功能点,以保证类实体只有一个引起它变化的原因

非要多说一嘴: 单一职责原则不只是面向对象编程思想所特有的,只要是模块化的程序设计,都适用单一职责原则

2. 里氏替换原则(Liskov Substitution Principle)

一句话概括:超类/基类存在的地方,子类是可以替换的。【反之不一定可以】
高逼格专业描述: 子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
里氏替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了里氏替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
里氏替换原则是关于继承机制的设计原则,违反了里氏替换原则就必然导致违反开放封闭原则【解释:实现开闭原则的关键是抽象化,而里氏代换原则中的基类和子类的继承关系正是抽象化的具体体现,所以里氏代换原则是对实现抽象化的具体步骤的规范。】
里氏替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
非要多说一嘴: 尽量使用精准的抽象类或者接口【子类和父类划分精准】,里氏代换原则是要求我们在使用继承时,必须满足一定的条件。不能为了复用,一味去继承。

3. 依赖倒置原则(Dependence Inversion Principle)

一句话概括:实现尽量依赖抽象,不依赖具体实现。【可以映射为活着与吃饭的关系,活着不是为了吃饭,但是吃饭为了活着。】
高逼格专业描述 我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而基础层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对面向接口编程,不要对实现编程

非要多说一嘴: 依赖倒置原则就是要求调用者和被调用者都依赖抽象,这样两者没有直接的关联和接触,在变动的时候,一方的变动不会影响另一方的变动。面向抽象编程,解耦调用和被调用者

4. 接口隔离原则(Interface Segregation Principle)

一句话概括:应当为客户端提供尽可能小的单独的接口,而不是提供大的总的接口。
高逼格专业描述接口隔离原则可以说是单一职责的必要手段,它的含义是尽量使用职能单一的接口,而不使用职能复杂、全面的接口。很好理解,接口是为了让子类实现的,如果子类想达到职能单一,那么接口也必须满足职能单一。相反,如果接口融合了多个不相关的方法,那它的子类就被迫要实现所有方法,尽管有些方法是根本用不到的。这就是接口污染。【大接口存在明显的弊端,会导致实现的类型必须完全实现该接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对大接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将大接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。】
非要多说一嘴 拆分,从接口开始。

5. 迪米特法则(Law Of Demeter)

一句话概括:又叫最少知识原则,一个对象对另一个对象知道的越少越好,即一个软件实体应当尽可能少的与其他实体发生相互作用。
高逼格专业描述 迪米特原则要求尽量的封装,尽量的独立,尽量的使用低级别的访问修饰符。这是封装特性的典型体现。 一个类如果暴露太多私用的方法和字段,会让调用者很茫然。并且会给类造成不必要的判断代码。所以,我们使用尽量低的访问修饰符,让外界不知道我们的内部。这也是面向对象的基本思路。这是迪米特原则的一个特性,无法了解类更多的私有信息。 另外,迪米特原则要求类之间的直接联系尽量的少,两个类的访问,通过第三个中介类来实现。
非要多说一嘴 不和陌生人说话,有事去中介

6. 开闭原则(Open Close Principle)

一句话概括:面向扩展/修改开放,面向修改关闭。
高逼格专业描述 其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。开放封闭原则主要体现在两个方面1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。 实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。
非要多说一嘴 “需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。 一条放在第一位来理解,它的含义是对扩展开放,对修改关闭。解释一下就是,我们写完的代码,不能因为需求变化就修改。我们可以通过新增代码的方式来解决变化的需求。

当然,这是一种理想的状态,在现实中,我们要尽量的缩小这种修改。再解释一下这条原则的意义所在,我们采用逆向思维方式来想。如果每次需求变动都去修改原有的代码,那原有的代码就存在==被修改错误的风险==,当然这其中存在有意和无意的修改,都会导致原有正常运行的功能失效的风险,这样很有可能会展开可怕的蝴蝶效应,使维护工作剧增。说到底,开闭原则除了表面上的可扩展性强以外,在企业中更看重的是维护成本。所以,开闭原则是设计模式的第一大原则,其他的几个原则,都是为此原则服务的。

7. 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)

一句话概括:尽量使用合成/聚合达到复用,尽量少用继承。
高逼格专业描述 此原则的含义是,如果只是达到代码复用的目的,尽量使用组合与聚合,而不是继承。这里需要解释一下,组合聚合只是引用其他的类的方法,而不会受引用的类的继承而改变血统。 继承的耦合性更大【使用继承的方式将两个不相干的类联系在一起,也会违反里氏代换原则】,比如一个父类后来添加实现一个接口或者去掉一个接口,那子类可能会遭到毁灭性的编译错误,但如果只是组合聚合【类与类之间的耦合度降低】,只是引用类的方法,就不会有这种巨大的风险,同时也实现了复用。

非要多说一嘴 意思就是我只是用你的方法,我们不一定是同类。

在学习面向对象七大设计原则时需要注意以下几点:

a)高内聚、低耦合和单一职能的“冲突”

实际上,这两者是一回事。内聚,要求一个类把所有相关的方法放在一起,初看是职能多,但有个“高”,就是要求把联系非常紧密的功能放在一起,也就是说,从整体看,是一个职能的才能放在一起,所以,两者是不同的表述而已。

这里很多人理解成复合类,但复合类不是高内聚,而是杂乱的放在一起,是一种设计失误而已。

b)多个单一职能接口的灵活性和声明类型问题

如果一个类实现多个接口,那么这个类应该用哪个接口类型声明呢?应该是用一个抽象类来继承多个接口,而实现类来继承这个接口。声明的时候,类型是抽象类。

c)最少知识原则和中介类泛滥两种极端情况

这是另一种设计的失误。迪米特原则要求类之间要用中介来通讯,但类多了以后,会造成中介类泛滥的情况,这种情况,我们可以考虑中介模式,用一个总的中介类来实现。

当然,设计模式都有自己的缺陷,迪米特原则也不是十全十美,交互类非常繁多的情况下,要适当的牺牲设计原则。

d)继承和组合聚合复用原则的“冲突”

继承也能实现复用,那这个原则是不是要抛弃继承了?不是的。

继承更注重的是“血统”,也就是什么类型的。而组合聚合更注重的是借用“技能”。并且,组合聚合中,两个类是部分与整体的关系,组合聚合可以由多个类的技能组成。在C#和Java中只有单继承。

这个原则不是告诉我们不用继承了,都用组合聚合,而是在“复用”这个点上,我们优先使用组合聚合。

面向对象设计原则的共性问题

1、这么多设计模式,都要学习和使用么?

答:我们只是掌握总体的原则,然后学习常用的就行了。实际开发中也不是每种设计模式都会经常用到。因为归根结底,设计模式也好,架构也好,都是为需求服务的,没有需求业务模型,不能生搬硬套模式。我们在学习的时候,多学一些总是好的,但只是为了开阔自己的眼界。

2、设计模式是规范么?是不是好的程序必须用设计模式?

答:严格来说,好的程序遵循的是设计原则,而非设计模式。现在就出现很多新的演变出来的模式,这些都是因为出现了新业务的原因,设计模式不是规范,只是一种借鉴。

3、使用设计模式会不会增加开发难度?

答:开发阶段会的,而且会延长开发时间。但一个项目或产品从开始到结束,开发只是其中很小的一部分,考虑到维护和扩展成本,才会出现设计模式。从整体考虑,设计模式是减少了开发时间和成本的。