单一职责原则
一个类或者模块应该有且只有一个改变的原因。
优点
- 降低类的复杂度
- 提供类的可读性,提高系统的可维护性
- 变更引起的风险降低
- 降低耦合度
实现
问题由来: 类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
解决方案: 遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。
开闭原则
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
。
优点
- 降低程序各部分的耦合度
- 提高代码的可复用性
- 提高软件的可维护性
实现
- 问题由来: 在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
- 解决方案: 当软件需要变化时,尽量通过扩展实体类的行为来实现变化,而不是通过修改已有的代码来实现变化
解决问题关键在于抽象化,抽象化是面向对象设计的第一个核心本质。
在面向对象中,通过抽象类及接口,规定了具体类的特征作为抽象层,相对稳定,从而满足"对修改关闭";从抽象类导出的具体类可以改变系统的行为,从而满足"对扩展开放"
实例
开闭原则可能是设计模式六项原则中定义最模糊的一个了,它只告诉我们对扩展开放,对修改关闭,可是到底如何才能做到对扩展开放,对修改关闭。
用抽象构建框架,用实现扩展细节。
接口隔离原则
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
接口隔离原则的含义是:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。
实例
未遵循接口隔离原则的设计
遵循接口隔离原则的设计
依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。
其核心思想是:要面向接口编程,不要面向实现编程。
这里的抽象指的是接口或者抽象类,而细节是指具体的实现类。
优点
- 降低类之间的耦合性
- 提高系统的稳定性
- 降低修改程序造成的风险
实现
- 问题由来: 类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
- 解决方案: 将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。 以抽象方式耦合是依赖倒转原则的关键。抽象耦合关系总要涉及具体类从抽象类继承,并且需要保证在任何引用到基类的地方都可以改换成其子类,因此,里氏代换原则是依赖倒转原则的基础。
代码
场景:母亲给孩子讲故事,只要给她一本书,她就可以照着书给孩子讲故事了。
class Book {
public String getContent() {
return "很久很久以前......";
}
}
class Monther {
public void narrate(Book book) {
System.out.println("妈妈开始讲故事");
System.out.println(book.getContent());
}
}
public class DIPClient {
public static void main(String[] args) {
Monther monther = new Monther();
monther.narrate(new Book());
}
}
输出结果:
妈妈开始讲故事
很久很久以前......
如果此时需要讲报纸上的内容,就需要再新建一个类Newspaper
class Newspaper {
public String getContent() {
return "金融风暴卷土而来......";
}
}
class Book {
public String getContent() {
return "很久很久以前......";
}
}
class Monther {
public void narrate(Book book) {
System.out.println("妈妈开始讲书上的故事");
System.out.println(book.getContent());
}
// 修改Mother类。
public void narrate(Newspaper newspaper) {
System.out.println("妈妈开始讲报纸上的内容");
System.out.println(newspaper.getContent());
}
}
public class DIPClient {
public static void main(String[] args) {
Monther monther = new Monther();
monther.narrate(new Book());
monther.narrate(new Newspaper());
}
}
输出结果:
妈妈开始讲书上的故事
很久很久以前......
妈妈开始讲报纸上的内容
金融风暴卷土而来......
如果再来个讲头条的内容,又要新建一个类,然后改动Monther类,这样做不太合理。此时新建接口类,使其依赖于接口,而不是具体实现类,达到Monther类不用修改的目的。
interface IReader {
String getContent();
}
class Newspaper implements IReader {
@Override
public String getContent() {
System.out.println("妈妈开始讲报纸上的内容");
return "金融风暴卷土而来......";
}
}
class Book implements IReader {
@Override
public String getContent() {
System.out.println("妈妈开始讲书上的故事");
return "很久很久以前......";
}
}
class Monther {
public void narrate(IReader reader) {
System.out.println(reader.getContent());
}
}
public class DIPClient {
public static void main(String[] args) {
Monther monther = new Monther();
monther.narrate(new Book());
monther.narrate(new Newspaper());
}
}
输出结果:
妈妈开始讲书上的故事
很久很久以前......
妈妈开始讲报纸上的内容
金融风暴卷土而来......
里氏替换原则
所有引用基类(父类)的地方必须能透明地使用其子类的对象。通俗讲:子类可以扩展父类的功能,但不能改变父类原有的功能。
实现
- 问题由来: 有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
- 解决方案: 当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
实例
举例说明两个数相减,由A类负责
class A {
public int func1(int a, int b) {
return a-b;
}
}
public class LSPClient {
public static void main(String[] args) {
A a = new A();
System.out.println("5-3=" + a.func1(5, 3));
System.out.println("100-10=" + a.func1(100, 10));
}
}
输出结果:
5 - 3 = 2
100 - 10 = 90
此时需要新加一个功能:两数相加,然后再与100求和,由B类负责
class A {
public int func1(int a, int b) {
return a - b;
}
}
class B extends A {
@Override
public int func1(int a, int b) {
return a + b;
}
public int func2(int a, int b) {
return func1(a, b) + 100;
}
}
public class LSPClient {
public static void main(String[] args) {
B b = new B();
System.out.println("5-3=" + b.func1(5, 3));
System.out.println("100-10=" + b.func1(100, 10));
System.out.println("50+20+100=" + b.func2(50, 20));
}
}
输出结果:
5-3=8
100-10=110
50+20+100=170
迪米特法则
迪米特法则(Law of Demeter,LoD)又叫作最少知识原则。
迪米特法则的定义是:只与你的直接朋友交谈,不跟“陌生人”说话。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。 其目的是降低类之间的耦合度,提高模块的相对独立性。
优点
- 降低类之间的耦合度,提高了模块的相对独立性
- 耦合度降低,从而提高了类的可重用率和系统的扩展性
缺点
- 过度使用迪米特原则,会产生大量的中介类,导致系统的复杂度提高。在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰
实现
- 问题由来: 类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大
- 解决方案: 尽量降低类与类之间的耦合
-
需强调
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
-
需注意
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
- 谨慎使用序列化(Serializable)功能。
总结
- 找出应用中可能需要变化之处,把他们独立出来,不要和哪些不需要变化的代码混在一起。