面向对象设计原则是学习设计模式的基础,每一种设计模式都符合一种或多种面向对象设计原则。遵循这些设计原则可以有效地提高系统的可复用性和可维护性。另外,面向对象设计原则和设计模式也是对系统进行合理重构的指南针,关于重构可以参考这本书:Refactoring Improving the Design of Existing Code,作者是软件工程大师Martin Fowler。
一、单一职责原则 Single Responsibility Principle
Definition: Every Object should have a single resonsibility, and that responsibility should be entirely encapsulated by the class.
类的职责主要包括两个方面:数据职责和行为职责,数据职责通过其属性来体现,而行为职责通过其方法来体现。
单一职责原则是实现高内聚、低耦合的指导方针,是最简单也是最难用的原则。
二、开闭原则 Open-Closed Principle
Definition: Software entities should be open for extension, but closed for modification.
这里的entities可以指一个软件模块、一个由多个类组成的局部结构或一个独立的类。
为了满足开闭原则,需要对系统进行抽象化设计,抽象化是开闭原则的关键。
百分百的开闭原则很难达到,但是要尽可能使系统设计符合开闭原则,后面所学的里氏替换原则、依赖倒置原则等都是开闭原则的实现方法。在即将学习的24种(简单工厂模式不属于GoF23)设计模式中,绝大部分的设计模式都符合开闭原则,在对每一个模式进行优缺点评价时都会以开闭原则作为一个重要的评价依据,以判断基于该模式设计的系统是否具有良好的灵活性和可扩展性。
三、里氏替换 Liskov Substitution Principle
Definition: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
里氏替换是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
在使用里氏替换原则时需要注意如下几个问题:
(1)子类的所有方法必须在父类中声明,且子类必须实现父类中声明的所有方法。
(2)在运用里氏替换原则时,尽量把父类设计为抽象类或接口。
(3)java编译器在编译阶段会检查一个程序是否符合里氏替换原则。这是一个与实现无关的、纯语法意义上的检查,它的检查是有限的。
四、依赖倒置原则 Dependency Inversion Principle
Definition: High level modules should not depend upon low level modules, both should depend upon abstractions. Abstractions should not depend upon details, details should depend upon abstractions.
如果说开闭原则是面向对象设计的目标的话,那么依赖倒置原则就是实现面向对象设计的主要机制,依赖倒置原则是系统抽象化的具体实现,
通俗的讲,就是要针对接口编程,不要针对实现编程。也就是说,在程序代码中传递参数时或在组合聚合关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。为了确保该原则的应用,一个具体类应当只实现接口和抽象类中声明过的方法,而不要给出多余的方法,否则将无法调用到在子类中增加的新方法。
依赖倒置原则是COM、CORBA、EJB、Spring等技术和框架背后的基本原则之一。
下面介绍下依赖倒置原则中经常提到的两个概念:
1. 类之间的耦合(依赖关系)
(1) 零耦合。即两个类之间没有任何耦合。
(2) 具体耦合。具体耦合发生在两个具体类(可实例化的类)之间,由一个类对另一个具体类实例的直接引用产生。
(3)抽象耦合关系:抽象耦合关系发生在一个具体类和一个抽象类之间,也可以发生在两个抽象类之间,使两个发生关系的类之间存有最大的灵活性。由于在抽象耦合中至少有一端是抽象的,因此可以通过不同的具体实现来进行扩展。
依赖倒置原则要求客户端依赖于抽象耦合,以抽象方式耦合是依赖倒置原则的关键。由于一个抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以替换成其子类,因此,里氏替换原则是依赖倒置原则的基础。
2. 依赖注入(Dependency Injection)
依赖注入就是将一个类的对象传入另一个类,注入时应该尽量注入父类对象,而在程序运行时再通过子类对象来覆盖父类对象,依赖注入有以下三种方式:
(1) 构造注入 (2)设值注入 (3)接口注入
五、接口隔离原则 Interface Segregation Principle
definition: Clients should not be forced to depend upon interfaces that they do not use.
实质上,接口隔离原则是指使用多个专门的接口,而不是使用单一的总接口。每一个接口应该承担一种相对独立的角色,不多不少,不干不该干的事,该干的事都要干。
使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
六、合成复用原则 Composite Reuse Principle
definition: Favor composition of objects over inheritance as a reuse mechanism
这个原则很简单,一句话概括下就是:尽量使用组合/聚合关系,少用继承。
继承相信大家早已耳熟能详,这里简单讲下对象间的四种关系,关联、组合、聚合、泛化。
关联 Association 一种结构化关系,表示一类对象与另一类对象之间有联系,可以是双向关联,也可以是单向关联
聚合 Aggregation 表示一个整体与部分的关系。成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。在UML中,聚合关系用带空心菱形直线表示。比如,汽车和汽车发动机
组合 Composition 也表示类之间整体和部分的关系,但是组合关系中整体与部分具有统一的生存期。一旦整体对象不存在,部分对象也将不存在。比如在GUI开发中,一个窗口和其包含的按钮、文本框的关系。在组合关系中,整体类可以控制成员类的生命周期,即成员类的存在依赖于整体类。在UML中,组合关系用带实心菱形的直线表示
泛化 Generalization 也就是继承,用于描述父类与子类的关系
七、迪米特法则 Law of Demeter
Definition:
(1) Don't talk to strangers
(2) Talk only to your immediate friends
(3) Each unit should have only limited kownledge about other units: only units 'closely' related to the current unit
又称为最少知识原则。
简单的讲,迪米特法则就是指一个软件实体应当尽可能少的与其他实体发生相互作用。这样当一个模块修改时,就会尽量少的影响其他的模块,扩展会相对容易。
在迪米特法则中,对于一个对象,其朋友包括以下几类:
(1)当前对象本身
(2)以参数形式传入到当前对象方法中的对象
(3)当前对象的成员对象
(4)如果当前对象的成员对象是一个集合,那么集合中的元素也都是朋友
(5)当前对象所创建的对象