设计模式学习笔记--6大设计原则
来源:互联网 发布:东莞淘宝美工专业培训 编辑:程序博客网 时间:2024/06/05 13:30
第一原则:单一职责原则(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》 周立新等译
《算法分析与设计》 霍红卫译
- 设计模式学习笔记--6大设计原则
- 设计模式笔记---6大设计原则
- 大话设计模式笔记----6大原则
- 设计模式.笔记.6大原则
- 设计模式——6大设计原则笔记
- 设计模式之禅笔记-6大设计原则(上)
- 设计模式之禅笔记-6大设计原则(下)
- 设计模式6大原则
- 6大设计模式原则
- 设计模式6大原则
- 设计模式6大原则
- 设计模式6大原则
- 设计模式6大原则
- 设计模式6大原则
- 设计模式--6大原则
- 设计模式6大原则
- 设计模式6大原则
- 设计模式6大原则
- Android中内容观察者的使用---- ContentObserver类详解
- test
- SQL分组求每组最大值问题的解决方法收集
- linux python3.4获取股票脚本
- LeetCode题解——Convert Sorted List to Binary Search Tree
- 设计模式学习笔记--6大设计原则
- Bitmap详解之获取Bitmap的方法
- BZOJ 3876: [Ahoi2014]支线剧情(有源有汇有下界的费用流)
- C语言中“指针”和“指针变量”区别及定义
- Python模拟java输入输出流
- linux c 九宫格
- Eclipse 调试技巧
- (LeetCode) Divide Two Integers (Java)思路讲解及实现
- poj2481(单点更新)