设计模式学习笔记--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. 有利于测试代码块
  2. 提高代码的复用性,粒度越小,复用性越高
  3. 提高维护性
  4. 面向对象开发的要求
    细节描述
    开闭原则对扩展开放,对修改关闭,并不意味着不做任何修改,底层模块的变更,必然要有更高模块进行耦合,否则就是一个孤独无意义的代码片段。
    • 逻辑变化
    • 子模块变化
    • 可见视图变化

如何应用

(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》 周立新等译
《算法分析与设计》 霍红卫译

0 0
原创粉丝点击