您的位置:首页 > 其它

设计模式学习笔记--6大设计原则

2015-09-30 15:51 204 查看

第一原则:单一职责原则(SRP)

简要介绍

单一职责原则的英文名为Single Responsibility Principle,简称SRP。

定义:应该有且仅有一个原因引起类的变更。以接口为例,一个接口实现一个职责,进行职责的划分,再用一个类实现多个接口。

具体来看,如果接口的单一职责做的好,一个接口的修改只对相应的类有影响,对其它接口都没有影响。

优点

1、类的复杂性降低,实现什么职责都有清晰明确的定义;

2、可读性提高,复杂性降低;

3、可维护性提高,可读性提高;

4、变更引起的风险降低;

应用场所

接口、类以及方法

第二原则:里氏替换原则(LSP)

简要介绍

定义:所有引用基类的地方必须能透明地使用其子类的对象。具体来说,只要父类出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或者异常,使用者不需要知道是父类还是子类。

具体含义

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

在类中调用其它类时务必使用父类或接口,如果不能使用父类或者接口,则说明类的设计已经违背了LSP原则

2、子类可以有自己的个性

如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承

3、覆盖或实现父类的方法时输入参数可以被放大,子类中方法的前置条件(输入参数)必须与超类中被覆写的方法的前置条件相同或者更加宽松。

调用父类的方法,除了覆盖外,还可以是重载方法,前提是要扩大这个前置条件,即参数的范围

4、覆写或实现父类的方法时输出的结果可以被缩小

父类方法的返回值是类型A,子类的返回值是类型B,要么B和A是同一个类型,要么B是A的子类

具体分两种情况:

覆写(overwriting),父类和子类的同名方法的输入参数是相同的,两个方法的范围值B小于A。

重载(overloading),父类和子类的同名方法的输入参数类型或数量不同,子类要宽于父类

第三原则:依赖倒置原则(DIP)

简要介绍

定义:高层模块不应该依赖于底层模块,两者应该依赖其抽象;抽象不应该依赖于细节;细节应该依赖于抽象。本质就是,通过抽象(接口或抽象类)使各个类或者模块实现彼此独立,不相互影响,实现模块间的松耦合。核心为面向接口编程。

对于Java来说:

- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类实现的;

- 接口或抽象类不依赖于实现类

- 实现类依赖接口或抽象类

注意:两个类之间有依赖关系,只要制定出两者之间的接口(或抽象类)就可以独立的开发了。

//司机接口
public interface IDriver{
public void drive(ICar car);
}
//司机类实现
public class Driver implements IDriver{
public void drive(ICar car){
car.run();
}
}

//汽车接口以及两个类实现
public interface ICar{
public void run();
}
public class Benz implements ICar{
public void run(){
System.out.prinln("奔驰汽车跑");
}
}
public class BMW implements ICar{
public void run(){
System.out.prinln("宝马汽车跑");
}
}


依赖的三种写法

1、构造函数传递依赖对象

//司机接口
public interface IDriver{
public void drive();
}
//司机类实现
public class Driver implements IDriver{
private ICar car;
//构造函数注入
public Driver(ICar _car){
this._car= _car;
}
public void drive(){
this.car.run();
}
}


2、Setter方法传递依赖对象

//司机接口
public interface IDriver{
//车辆型号
public void setCar(ICar car);
public void drive();
}
//司机类实现
public class Driver implements IDriver{
private ICar car;

public void setCar(ICar car){
this.car=car;
}
public void drive(){
this.car.run();
}
}


3、接口声明依赖对象

遵循的原则

- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备

- 变量的表面类型尽量是接口或者抽象类

- 任何类都不应该从具体类中派生

- 尽量不要覆写基类的方法

- 结合里氏替换原则使用

第四原则:接口隔离原则

简要介绍

定义:建立单一接口,不要建立臃肿庞大的接口,接口尽量细化,同时接口中的方法尽量的少。

规范约束:

1、接口要尽量小,根据接口隔离原则拆分接口时,首先必须满足单一职责原则。

2、接口要高内聚,提高接口、类、模块的处理能力,减少对外的交互。

3、定值服务,单独为一个个体提供优良的服务。

4、接口设计是有限度的,粒度划分是有一定限制的。

遵循的规则

- 一个接口只服务于一个子模块和业务模块

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

- 已经被污染了的接口,要尽量去修改,可采用适配器模式进行转化

- 了解环境拒绝盲从。

第五原则:迪米特法则(LKP)

简要介绍

定义:一个对象应该对其它对象有最少的了解,即一个类应该对自己需要耦合或调用的类知道的最少。

具体含义

(1) 只和朋友交流

一个类只能和朋友交流,不与陌生人交流,不要出现
getA().getB().getC()
这种情况,类与类之间的关系是简历在类间的,而不是方法间。

(2) 朋友间也是有距离的

举例说明,安装软件的过程分为三次,first, second, third,下面程序有一个致命的缺点,即Wizard类把太多的方法暴漏给InstallSoftware类,两者的朋友关系太亲密了,耦合关系也变得的异常牢固。

//导向类
public class Wizard{
private Random rand = new Random(System.currentTimeMillis());
//安装第一步
public int first(){
System.out.println("执行第一个方法");
return rand.nextInt(100);
}
//安装第二步
public int second(){
System.out.println("执行第二个方法");
return rand.nextInt(100);
}
//安装第三步
public int third(){
System.out.println("执行第三个方法");
return rand.nextInt(100);
}
}
//InstallSoftware类
public class InstallSoftware{
public void installWizard(Wizard wizard){
int first= wizard.first();
if(first>50){
int second= wizard.second();
if(second>50){
int third= wizard.third();
if(third>50)
wizard.first();
}
}
}
}


修改后的程序如下所示,Wizard 类的三个方法权限改为private,InstallSoftware类的installWizard方法移到Wizard 类中,通过这样重构后,Wizard 类就只对外公布了一个public方法,显示了类的高内聚特性。

//修改后的导向类
public class Wizard{
private Random rand = new Random(System.currentTimeMillis());
//安装第一步
private int first(){
System.out.println("执行第一个方法");
return rand.nextInt(100);
}
//安装第二步
private int second(){
System.out.println("执行第二个方法");
return rand.nextInt(100);
}
//安装第三步
private int third(){
System.out.println("执行第三个方法");
return rand.nextInt(100);
}
//软件安装过程
public void installWizard(){
int first= this.first();
if(first>50){
int second= this.second();
if(second>50){
int third= this.third();
if(third>50)
this.first();
}
}
}
}
//修改后的InstallSoftware类
public class InstallSoftware{
public void installWizard(Wizard wizard){

wizard.installWizard();//直接进行调用公开的方法
}


使用规范 :

迪米特法则要求类“羞涩”一些,尽量不要对外公布太多的public方法和非静态的public方法变量,尽量内敛,多使用private、package-private、protected、final等访问权限。

(3) 是自己的就是自己的

如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。

(4)谨慎使用Serializable

核心规则

迪米特法则的核心观念就是类间的解耦,弱耦合,只有弱耦合之后,类的复用率才可以提高,带来的弊端就是产生了大量的中转或者跳转类,导致系统的复杂性提高,在使用时需要反复考虑,做到结构清晰的同时,又要做到高内聚低耦合。

第六原则:开闭原则(Java最基础的原则)

简要介绍

定义:一个软件实体如类、模块和函数应该对扩展开放,对修改封闭。即一个软件实体应该通过扩展来实现变化,而不是修改已有的代码实现变化。

软件实体:带有一定逻辑规则的模块;抽象和类;方法。

以买书为例,IBook定义了三个属性,名称,价格和作者,如果有新的需求,比如打折,则直接在继承NoveIBook类,并覆盖掉getPrice()方法即可,具体接口实现如下所示。



优点

有利于测试代码块

提高代码的复用性,粒度越小,复用性越高

提高维护性

面向对象开发的要求

细节描述

开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有更高模块进行耦合,否则就是一个孤独无意义的代码片段。

逻辑变化

子模块变化

可见视图变化

如何应用

(1)抽象约束

- 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现接口或抽象类中不存在的public方法

- 参数类型、引用对象尽量使用接口或抽象类,而不是实现类

- 抽象层尽量表示稳定,一旦确定即不允许修改

还是以买书为例,现在增加了计算机书籍,除了价格,名字还有作者外,计算机还有一个独特的属性,其所包含的领域,于是增加了一个IComputer接口以及一个实现类



//计算机书籍接口
public interface IComputerBook extends IBook{
public String getScope();
}
//计算机书籍类
public class ComputerBook implements IComputerBook{
private String name;
private String scope;
private String author;
private int price;
public ComputerBook(String _name, String _author, String _scope, int _price){
this.name= _name;
this.price= _price;
this.scope= _scope;
this.author=_author;
}
public String getScope(){
return this.scope;
}
public String getAuthor(){
return this.author;
}
public String getName(){
return this.name;
}
public String getPrice(){
return this.price;
}
}


(2)元数据(metadata)控制模块行为

元数据:用来描述环境和数据的数据,一般可以利用配置参数来表示,最典型的就是Spring容器中的控制反转,通过扩展一个子类,修改配置文件,完成了业务变化。

(3)制定项目章程

(4)封装变化

具体含义

- 将相同的变化封装到一个接口或抽象类中

- 将不同的变化封装到不同的接口或者抽象类中,不应该有两个的变化出现在同一个接口或者抽象类中。

小结

设计模式不管对于大型程序开发还是项目实施都是比较重要的,在面试的时候,面试官也多次提到,而且会设定某一情景,来回答采用哪些设计模式,所以想借十一假期好好对设计模式进行总结一下。(未完待续)

参考文献

《设计模式之禅》 秦小波著

《Java核心技术 卷I》 周立新等译

《算法分析与设计》 霍红卫译
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  设计模式 设计