六大设计原则学习笔记
单一职责原则
应该有且仅有一个原因引起累的变更。
tips:当有一个类有多个职责可以实现多个接口。
里氏替换原则
继承的特点:
- 代码共享,提高代码的重用性,
- 提高代码的可扩展性
- 侵入性(子类必须有父类所以的属性和方法),降低了灵活性
- 增强了耦合性
里氏替换原则的定义:
所有引用基类的地方必须能透明地使用其子类的对象
- 子类必须完全实现父类的方法
- 子类可以有自己的个性
- 覆盖或实现父类的方法时输入参数可以被放大
- 覆写或实现弗雷德方法时输出结果可以被缩小
依赖倒置原则
依赖倒置原则的含义:
- 高层模块不应该依赖底层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
在java中的表现:
- 模块间的依赖通过抽象发生,实现类之间发生直接的依赖关系,,其依赖关系通过接口或抽象类产生
- 接口或抽象类不依赖于实现类
- 实现类依赖接口或抽象类
例:以人或者司机开汽车为例。
//司机接口
public interface IDriver {
public void driver(ICar car);
}
//司机实现
public class Driver implements IDriver{
@Override
public void drive(ICar car) {
car.run();
}
}
//汽车接口
public interface ICar {
public void run();
}
//奔驰车实现
public class Benz implements ICar{
@Override
public void run() {
System.out.println("Benz车正在运行...");
}
}
//宝马车实现
public class Bmw implements ICar{
@Override
public void run() {
System.out.println("Bmw车正在运行...");
}
}
/**
* 张三开奔驰车
*/
public class Client1 {
public static void main(String[] args) {
IDriver ZhangSan = new Driver();
ICar benz = new Benz();
ZhangSan.drive(benz);
}
}
/**
* 张三开宝马车
*/
public class Client2 {
public static void main(String[] args) {
IDriver ZhangSan = new Driver();
ICar bmw = new Bmw();
ZhangSan.drive(bmw);
}
}
从上面的列子我们可以看出,高层模块,如client和driver,不依赖底层模块的,只是依赖接口,如,driver中的car对象只是以ICar声明,通过接口产生;client中也是通过IDriver和ICar来声明对象。
依赖的三种写法:
-
构造函数传递依赖对象
public interface IDriver { public void driver(); } public class Driver implements IDriver{ private ICar car; //构造函数注入 public Driver(ICar _car){ this.car = _car } @Override public void driver() { this.car.run(); } }
-
Setter方法传递依赖对象
public interface IDriver { public void setCar(ICar car); public void driver(); } public class Driver implements IDriver{ private ICar car; //Setter方法 public setCar(ICar _car){ this.car = _car } @Override public void driver() { this.car.run(); } }
-
接口声明依赖对象
//这段的第一段很长的程序,就是这种情况 public interface IDriver { public void driver(ICar car); } public class Driver implements IDriver{ @Override public void drive(ICar car) { car.run(); } }
接口隔离原则
定义:
- 客户端不应该依赖它不需要的接口
- 类间的依赖关系应该建立在最小的接口上
原则:
-
接口要尽量小
这是接口隔离原则的核心定义,不出现臃肿的接口(Fat Interface),但是“小”是有限度的,首先就是不能违反单一职责原则。根据接口隔离原则拆分接口时,首先必须满足单一职责原则。
-
接口要高内聚
高内聚就是要提高接口、类、模块的处理能力,减少对外的交互。具体到接口隔离原则就是,要求在接口中尽量少公布public方法,接口是对外的承诺,承诺地越少对系统开发越有利,变更的风险也就越少,同时也有利于降低成本。
-
定制服务
定制服务就是单独为一个个体提供优良的服务。
-
接口设计是有限度的
接口的设计粒度越小,系统越灵活,这是不争的事实。但是,灵活的同时也带来了结构的复杂化,开发难度增加,可维护性降低,这不是一个项目或产品所期望看到的,所以接口设计一定要注意适度,这个度只能根据经验和常识判断,没有一个固化或可测量的标准。
迪米特法则LOD(最少知识原则LKP)
一个对象应该对其他对象有最少的了解
一个类应该对自己需求耦合或调用的类知道的最少,你(被耦合或调用的类)的内部是多么的复杂都和我没有关系
低耦合
开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
在设计一个模块时,应当使这个模块可以在不被修改的前提下被扩展,换言之,应当可以在不必修改源代码的情况下改变这个模块的行为;因为所有软件系统中 有一个共同的特性,即它们的需求都会随时间的推移而发生变化,在软件系统面临新的需 求时,满足开-闭原则的软件中,系统已有模块(特别是最重要的抽象层)不能再修改,而 通过扩展已有的模块(特别是最重要的抽象层),可以提供新的行为,以满足需求。
开-闭原则如果从另一个角度讲述,就是所谓可变性封装原则(Principle of Encapsulation of Variation,略写作EVP),找到系统的可变因素,将之封装起来。 也就是:考虑你的设计中有什么可能发生变化,允许这些变化而不让这些变化 导致重新设计。可变性封装原则意味着:
一种可变性不应当散落在代码的很多角落,而应当被封装到一个对象中,同一种可变性 的不同表现可以体现在子类中,继承应当被看做是封装变化的方法,而不仅仅看做是从父类派生子类
一种可变性不应当与另一种可变性混合在一起,所以一个设计模中,类图的继承层次 不会超过两层,不然就意味着将两种可变性混在一起
做到开闭原则不是件容易的事,但也很多规律可循,这些规律也同样以设计原则的身份 出现,它们都是开-闭原则的手段和工具,是附属于开-闭原则的。
例:模拟书店销售书籍为例
public interface IBook {
public String getName();
public int getPrice();
public String getAuthor();
}
public class NovelBook implements IBook {
private String name;
private int price;
private String author;
@Override
public String getName() {
return this.name;
}
@Override
public int getPrice() {
return this.price;
}
@Override
public String getAuthor() {
return this.author;
}
public NovelBook (String _name, int _price, String _author){
this.name = _name;
this.price = _price;
this.author = _author;
}
}
public class BookStore {
private final static ArrayList<IBook> booklist = new ArrayList<IBook>();
static{
booklist.add(new NovelBook("天龙八部",32,"金庸"));
booklist.add(new NovelBook("巴黎圣母院",40,"雨果"));
booklist.add(new NovelBook("悲惨世界",43,"雨果"));
}
public static void main(String[] args) {
System.out.println("------ 模拟书店卖出去的书记录如下 ------");
for (IBook book:booklist) {
System.out.print("--- 卖出书:"+book.getName());
System.out.print("---价格:"+book.getPrice());
System.out.println("---作者:"+book.getAuthor());
}
}
}
然而,现在由于需要而需将书打折出售,原价大于40元的9折,原价小于40的八折。
有三种方法解决这类问题
- 修改接口
- 修改实现类(有时候不适用)
- 通过扩展实现变化
对于上面的例子,解决办法如下:
- 对于第一种,可以修改iBook接口,添加getOffPrice()方法
- 对于第二种,可以修改NovelBook中的getPrice方法
- 对于第三种,,添加一个OffNovelBook实现iBook接口