六大设计原则

一、单一职责原则:英文名称是Single Responsibility Principle,简称SRP。有且只有一个原因引起类的变更。There should never be more than one reason for a class to change.

   例如在电话类的设计中,接口包含三个方法:拨号,通话和挂电话。但是这个接口包含了两个职责,拨号和挂电话属于协议管理,通话属于数据传输。不符合单一职责原则。可以将拨号和挂电话作为一个接口,通话作为一个接口,一个类实现了这两个接口,把两个职责融合在一个类中,对外公布的是接口,而不是实现类。

 

优点:类的复杂度降低,提高了可读性,可维护性,降低了变更引起的风险。

单一职责不仅适用于接口,类,还适用于方法,尽量使每个方法的职责清晰。

 

二、里氏替换原则:Functions that use pointers or references to base classes must beable to use objects of derived classes without knowing it. 所有引用基类的地方必须透明地使用其子类的对象。

     通俗的讲,只要父类出现的地方,子类都可以出现,而且替换为子类也不会发生任何异常或错误,使用者根本不需要知道是子类还是父类。

 

包含四层含义:

子类必须完全实现父类的方法。

子类可以有自己的方法。

覆盖或实现父类的方法时,输入参数可以放大。例如父类有一个的doSomething方法,其参数为(HashMap),而子类中重载了这个方法,参数为(Map),因为参数不一样,所以不是覆写,而是重载,此时,当子类当做父类使用时,传入HashMap参数,执行的为父类的方法。若反过来,传入HashMap时,执行的是子类的方法。(#add 此做法显然是java伎俩,c++中禁止)

覆盖或实现父类的方法时,返回值要变小。

   3和4条是为了保证版本升级时的兼容性,比如以前版本是创建了一个father类,新版本创建了子类,并使用子类,满足上述两条后可以实现系统兼容。传递不同的子类,

 

三、依赖倒置原则(Dependence Inversion Principle, DIP):High level modules should not depend upon low level modules. Bothshould depend upon abstractions. Abstractions should not depends upon details.Details should depend upon abstractions.

 

包含三层含义:

高层模块不应该依赖底层模块,两者都应该依赖其抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

简单的说就是面向接口编程(#add 基类控制子类的接口),模块间的依赖是通过抽象发生的,实现类之间不发生直接的依赖关系,其依赖是通过接口或者抽象类发生的。

优点:依赖倒置原则可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。

实现类不直接发生依赖关系,降低了耦合性,采用接口编程,由于调用的是接口,不必等实现类完成,就可以对调用者进行开发,测试。

例如在顶层模块实现司机开车功能,司机和车都属于底层。在编写的过程中,要编写司机和车的接口,司机里调用车时,调用的是接口,通过接口,构造函数或者SET方法给接口赋值。当车模型改变时,不需要修改底层的司机类。符合了单一职责,只有司机改变时,才能引起司机类的变化。

依赖的三种实现方法:

构造函数:上例中,建立司机类的时候,通过构造函数给司机类中的车接口赋值。

SET方法:在司机类中,增加一个PUBLIC 的SET方法,调用该方法时,给司机类中的车赋值。

接口方法:在调用的车的方法参数中,有一个参数为车的接口。

编程原则:

1.每个接口尽量有接口或者抽象类,或者抽象类接口都有。

2.变量的表面类型尽量是接口或者抽象类。第1条和第2条不是必须的,比如编写工具类XXXUtils时。

3.尽量不要覆写基类的方法。类间依赖的是抽象,覆写了抽象的方法,会对依赖的稳定性造成一定的影响。

总结:依赖倒置要求编程时,面向抽象或者接口。这就要求子类继承时,尽量不要新增方法,否则按照依赖倒置原则编程,根本访问不到子类新增的方法。

 

四、接口隔离原则:Client should not beforced to depend upon interfaces that they don’t use. (客户端不应该依赖它不需要的接口。)

The dependency of one class to another oneshould depend on the smallest possible interface. (类间的依赖关系应该建立在最小的接口上)

根据第一种定义,客户端需要什么接口就提供什么接口,把不需要的接口剔掉,这就需要对接口进行细化。

在第二种定义中,要求接口细化。

这两种定义实质上都是要求建立单一接口。

实例:比如星探查找美女,美女的定义是相貌美丽并且气质好。如果在一个接口里定义相貌美丽和气质好会造成接口臃肿,如果美女的标准变了,会对接口造成影响。这里应该使美女实现相貌美丽和气质好两个接口,这样星探的抽象类就依赖两个接口,而不是一个臃肿的接口,灵活性增强。

接口是我们设计时对外提供的契约,通过分散定义多个接口,可以预防未来变更的扩赛,提高系统的灵活性和可维护性。

接口设计原则

1.接口要尽量小。但是小是有限度的,不能违反单一职责原则,也就不能把一个职责拆分成两个接口。

2.接口要高内聚。高内聚就是提高接口,类和模块的处理能力,减少对外的交互。接口要尽量少的公布public方法,接口是对外的承诺,承诺的越少,对系统开发越有利,变更的风险就越少,同时有利于降低成本。

3.定制服务。定制服务就是单独为一个个体提供优良的服务,我们在做系统设计时,也需要考虑系统之间或模块之间的接口提供定制服务。这就要求接口只提供访问者需要的方法。(#add 其它的一律private)

4.接口设计是有限度的。接口设计粒度越小,系统越灵活,但是灵活的同时也会带来结构的复杂化,开发难度增加,可维护性降低。

最佳实践

一个接口只服务于一个子模块或者业务逻辑

通过业务逻辑压缩接口中的public方法。

被污染的接口尽量去修改,若修改的风险太大,则可以使用适配器模式进行转化处理,

 

五、迪米特原则(Law of Demeter, LoD)也叫最小知识原则(Least KnowledgePrinciple ,LKP):一个对象应该对其他对象有最少的了解,通俗的讲,一个类应该对自己需要耦合或调用的类知道得最少。

四层含义

1.只和朋友交流。迪米特原则还有一个英文解释是:Only talk to your immediate friends(只与直接的朋友通信)。例如,教师向班长发送命令,数女生的个数。则教师中发送命令方法需要传入班长类,由班长类执行数女生个数。在这中情况下,在教师类中不应该建立女生类,而应该在班长类中建立女生类,对女生个数进行统计。朋友类:出现在成员变量,或者输入参数的类称为方法类,而出现在方法体内部的类不属于朋友类。因而在上例中,教师类中不应该出现女生类。

2.朋友之间也是有距离的。例如在安装软件时,通过向导类,第一步安装成功可以安装第二步,第二步安装成功,可以执行第三步。在这种情况下,向导类应该对外提供一个安装方法,此方法根据判断条件调用三步安装方法,实现了整个安装过程。而不是让用户去调用每一步安装方法,并根据安装是否成功,决定下一步安装。在与MES集成的项目中,提供服务的方法类很简单,其主要实现都是调用其他类来实现的。

3.是自己的终究是自己的。在应用中可能出现这样一个方法,放在本类中也可以,放在其他类中叶可以。衡量准则如下:如果一个方法放在本类中,即不增加类间关系,也不会对本类产生负面影响,就放置在本类中。当然,如果该方法有多个类调用,则可放入工具类中。

4.慎用Serializable,如果在项目中,采用远程调用方法传递值对象,该对象就必须实现Serializable接口,也就是对网络传输的对象进行序列号,否则会出现异常。

最佳实践:迪米特法则的核心就是类间解耦,弱耦合,只有解耦后,复用率才可以提高。但是这样会导致产生大量的中转类或者跳转类,导致系统的复杂性提高,同时也给维护带来难度。在实际项目中,一个类跳转两次才能访问到另个一个类,就需要进行重构了。

 

六、开闭原则:Software entities like classes,modules and functions should be open for extension but close for modifications.(一个软件实体如类,模块和函数应该对扩展开放,对修改关闭)开闭原则要求尽量通过扩展软件实体的方法来适应变化,而不是通过修改已有的代码来完成变化。它是为软件实体的未来而制定的对现行开发设计进行约束的一个原则。

简单例子:以图书销售为例,图书有三个属性,价格,书名以及作者。小说书继承了图书接口。如果有一天图小说书打折,修改方案有三种:

(1)修改图书接口,在接口中增加获得打折价格方法。缺点是所有实现图书的接口都需要增加这种方法。

(2)直接修改小说类中获得价格的方法。缺点是:无论谁都看不到小说书的原价。

(3)新写一个打折小说类,继承小说类,覆写其中的价格方法。销售时,将打折小说类赋给图书接口。采购人员查看价格时,可以通过常见小说类实现。

变化分类:我们把变化分为三种。

(1)逻辑变化   只变化一个逻辑,不涉及其它模块。可以通过修改类中的方法来完成。前提条件是所有依赖或者关联的类都按照相同的逻辑处理。

(2)子模块变化  一个子模块变化,会引起高层的变化。因此通过扩展完成变化时,高层次的模块修改也是必然的。

(3)可见视图的变化

 如何应用开闭原则:

(1)抽象约束 通过接口或者抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放。包含三层和含义:第一,通过接口和抽象类约束扩展,多扩展进行边界限定,不允许出现在接口或者抽象中不存在的接口。第二,参数类型,引用对象尽量使用接口或者抽象类,而不是显现类第三,抽象层尽量保持稳定,一旦确定即不允许修改。如果因业务需要增加新的功能,可以写一个继承原有接口的接口。

(2)通过元数据控制模块行为  元数据用来描述环境和数据的数据,通俗的说就是配置参数,参数可以从文件中获得或者数据库中获得。

(3)封装变化  预测将来可能出现的变化,将相同的变化封装到一个接口或者抽象类中,将不同的变化封装到不同的接口后者抽象类中。

 

要把软件做得非常灵活又要便于维护是一个很困难的事情。灵活的软件他的结构就复杂,维护起来就困难。有得必有失,关键就在于如何处理这两者,使得大于失。软件的设计开发应遵循以下六大原则:

1.OCP

全称:Open-Closed Principle” 开放-封闭原则

说明:对扩展开放,对修改关闭

优点:按照OCP原则设计出来的系统,降低了程序各部分之间的耦合性,其适应性、灵活性、稳定性都比较好。当已有软件系统需要增加新的功能时,不需要对作为系统基础的抽象层进行修改,只需要在原有基础上附加新的模块就能实现所需要添加的功能。增加的新模块对原有的模块完全没有影响或影响很小,这样就无须为原有模块进行重新测试。

如何实现“开-闭”原则

在面向对象设计中,不允许更改的是系统的抽象层,而允许扩展的是系统的实现层。换言之,定义一个一劳永逸的抽象设计层,允许尽可能多的行为在实现层被实现。

解决问题关键在于抽象化,抽象化是面向对象设计的第一个核心本质。

对一个事物抽象化,实质上是在概括归纳总结它的本质。抽象让我们抓住最最重要的东西,从更高一层去思考。这降低了思考的复杂度,我们不用同时考虑那么多的东西。换言之,我们封装了事物的本质,看不到任何细节。

在面向对象编程中,通过抽象类及接口,规定了具体类的特征作为抽象层,相对稳定,不需更改,从而满足“对修改关闭”;而从抽象类导出的具体类可以改变系统的行为,从而满足“对扩展开放”。

对实体进行扩展时,不必改动软件的源代码或者二进制代码。关键在于抽象。

2. LSP

全称:“Liskov Substitution Principle” 里氏代换原则

说明:子类型必须能够替换它们的基类型。一个软件实体如果使用的是一个基类,那么当把这个基类替换成继承该基类的子类,程序的行为不会发生任何变化。软件实体察觉不出基类对象和子类对象的区别。

优点:可以很容易的实现同一父类下各个子类的互换,而客户端可以毫不察觉。

3. DIP

全称:“Dependence Inversion Principle”依赖倒置原则

说明:要依赖于抽象,不要依赖于具体。客户端依赖于抽象耦合。

抽象不应当依赖于细节;细节应当依赖于抽象;

要针对接口编程,不针对实现编程。

优点:使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。

怎样做到依赖倒置?

以抽象方式耦合是依赖倒转原则的关键。抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础。

在抽象层次上的耦合虽然有灵活性,但也带来了额外的复杂性,如果一个具体类发生变化的可能性非常小,那么抽象耦合能发挥的好处便十分有限,这时可以用具体耦合反而会更好。

层次化:所有结构良好的面向对象构架都具有清晰的层次定义,每个层次通过一个定义良好的、受控的接口向外提供一组内聚的服务。

依赖于抽象:建议不依赖于具体类,即程序中所有的依赖关系都应该终止于抽象类或者接口。尽量做到:

1、任何变量都不应该持有一个指向具体类的指针或者引用。

2、任何类都不应该从具体类派生。

3、任何方法都不应该覆写它的任何基类中的已经实现的方法。

4. ISP

全称:“Interface Segregation Principle” 接口隔离原则

说明:使用多个专一功能的接口比使用一个的总接口总要好。从一个客户类的角度来讲:一个类对另外一个类的依赖性应当是建立在最小接口上的。过于臃肿的接口是对接口的污染,不应该强迫客户依赖于它们不用的方法。

优点:会使一个软件系统功能扩展时,修改的压力不会传到别的对象那里。

如何实现接口隔离原则

不应该强迫用户依赖于他们不用的方法。

1、利用委托分离接口。

2、利用多继承分离接口。

5. CARP or CRP

全称:“Composite/AggregateReuse Principle” 合成/聚合复用原则 or “Composite Reuse Principle” 合成复用原则

说明:如果新对象的某些功能在别的已经创建好的对象里面已经实现,那么尽量使用别的对象提供的功能,使之成为新对象的一部分,而不要自己再重新创建。新对象通过向这些对象的委派达到复用已有功能的。

简而言之,要尽量使用合成/聚合,尽量不要使用继承。

优点:

1) 新对象存取成分对象的唯一方法是通过成分对象的接口。

2) 这种复用是黑箱复用,因为成分对象的内部细节是新对象所看不见的。

3) 这种复用支持包装。

4) 这种复用所需的依赖较少。

5) 每一个新的类可以将焦点集中在一个任务上。

6) 这种复用可以在运行时间内动态进行,新对象可以动态的引用与成分对象类型相同的对象。

7) 作为复用手段可以应用到几乎任何环境中去。

缺点:

就是系统中会有较多的对象需要管理。

6. LOD or LKP

全称:“Law of Demeter” 迪米特原则 or “Least Knowledge Principle” 最少知识原则

说明:对象与对象之间应该使用尽可能少的方法来关联,避免千丝万缕的关系。

如何实现迪米特法则

迪米特法则的主要用意是控制信息的过载,在将其运用到系统设计中应注意以下几点:

1) 在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。

2) 在类的结构设计上,每一个类都应当尽量降低成员的访问权限。一个类不应当public自己的属性,而应当提供取值和赋值的方法让外界间接访问自己的属性。

3) 在类的设计上,只要有可能,一个类应当设计成不变类。

4) 在对其它对象的引用上,一个类对其它对象的引用应该降到最低。


还有个单一职责原则:

SRP简介(SRP--Single-Responsibility Principle):

就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因。

所谓职责,我们可以理解他为功能,就是设计的这个类功能应该只有一个,而不是两个或更多。也可以理解为引用变化的原因,当你发现有两个变化会要求我们修改这个类,那么你就要考虑撤分这个类了。因为职责是变化的一个轴线,当需求变化时,该变化会反映类的职责的变化。

使用SRP注意点:

1、一个合理的类,应该仅有一个引起它变化的原因,即单一职责;
2、在没有变化征兆的情况下应用SRP或其他原则是不明智的;
3、在需求实际发生变化时就应该应用SRP等原则来重构代码;
4、使用测试驱动开发会迫使我们在设计出现臭味之前分离不合理代码;
5、如果测试不能迫使职责分离,僵化性和脆弱性的臭味会变得很强烈,那就应该用Facade或Proxy模式对代码重构;

SRP优点:

消除耦合,减小因需求变化引起代码僵化性臭味

你可能感兴趣的:(设计原则)