设计模式七大原则详解
本文最后更新于 2025-03-23,文章超过7天没更新,应该是已完结了~
设计模式七大原则:
单一职责原则
接口隔离原则
依赖倒置原则
里氏替换原则
开闭原则
迪米特法原则
合成复用原则
单一职责原则:
对类来说,一个类应该只负责一项职责。但如果类A负责两个不同职责,则当职责1需求变更而改变A类,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2。
接口隔离原则
一个类对另一个类的依赖应该建立在最小接口上。
从图中可以看出,类 A 依赖于 接口 I 中的方法 1,2,3 ,类 B 是对类 A 的具体实现。类 C 依赖接口 I 中的方法 1,4,5,类 D 是对类 C 的具体实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。
如果想符合接口隔离原则,就必须对接口 I 如下图进行拆分:
依赖倒装原则
依赖倒转的中心思想是面向接口编程。
依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类。
使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。
里氏替换原则
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。
里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。
含义:
1、子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法(重写)
2、子类中可以增加自己特有的方法
3、当子类重载父类的方法时,至少子类方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
正确示范:
public class A {
public void fun(HashMap map){
System.out.println("父类被执行...");
}
}
public class B extends A{
public void fun(Map map){
System.out.println("子类被执行...");
}
}
public class demo {
public static void main(String[] args){
System.out.print("父类的运行结果:");
A a=new A();
HashMap map=new HashMap();
a.fun(map);
//父类存在的地方,都可以用子类替代
//子类B替代父类A
System.out.print("子类替代父类后的运行结果:");
A b=new B(); //引用父类的地方可以使用子类生成的对象
b.fun(map);
}
}
结果:
子类方法的参数Map比父类方法的参数HashMap的范围要大,所以当参数输入为HashMap类型时,只会执行父类的方法,不会执行父类的重载方法。这符合里氏替换原则。
4、当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
public class LSP1 {
abstract class A {
public abstract Map fun();
}
class B extends A{
@Override
public HashMap fun(){
HashMap b=new HashMap();
b.put("b","子类被执行...");
return b;
}
}
public static void main(String[] args){
LSP1 lsp =new LSP1();
LSP1.A a=lsp.new B();
System.out.println(a.fun());
}
}
若在继承时,子类的方法返回值类型范围比父类的方法返回值类型范围大,在子类重写该方法时编译器会报错。
总结:
1、我们在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口,并实现在父类中声明的方法,运行时,子类实例替换父类实例,我们可以很方便地扩展系统的功能,同时无须修改原有子类的代码,增加新的功能可以通过增加一个新的子类来实现。里氏代换原则是开闭原则的具体实现手段之一。
2、在系统设计时,遵循里氏替换原则,尽量避免子类重写父类的方法,可以有效降低代码出错的可能性
开闭原则
即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。
迪米特法则
迪米特法则(Law of Demeter,LoD)也称为最少知识原则(Least Knowledge Principle,LKP)。
一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的public方法,我就调用这么多,其他的一概不关心。
只和朋友交流
朋友类的定义是这样的:出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
public class Teacher {
public void commond(GroupLeader groupLeader) {
List<Girl> listGirls = new ArrayList<Girl>();
for (int i = 0; i < 20; i++) {
listGirls.add(new Girl());
}
groupLeader.countGirls(listGirls);
}
}
方法是类的一个行为,类竟然不知道自己的行为与其他类产生了依赖关系,这是不允许的。
正确的做法是:
public class Teacher {
public void commond(GroupLeader groupLeader) {
groupLeader.countGirls();
}
}
public class GroupLeader {
private List<Girl> listGirls;
public GroupLeader(List<Girl> _listGirls) {
this.listGirls = _listGirls;
}
public void countGirls() {
System.out.println("女生数量是:" + listGirls.size());
}
}
注意:类与类之间的关系是建立在类间的,而不是方法间,因此一个方法尽量不引入一个类中不存在的对象,当然,JDK API提供的类除外。
朋友间也是有距离的
一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private、package-private(包类型,在类、方法、变量前不加访问权限,则默认为包类型)、protected等访问权限,是否可以加上final关键字等。
注意:迪米特法则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、package-private、protected等访问权限。
总结:迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。
合成复用原则
类A有2个方法,类B刚好需要调用这两个方法,我们第一可能会想到直接继承,这样“多快好省“,但随着业务进展,功能越来越复杂,A类需要增加其他方法,比如Method3 ,与B类毫无关联,将会大大增加耦合性,合用复用原则的核心就是使用关联,我们可以通过依赖、聚合、合成等关联方法,降低耦合,提高可维护性和降低维护成本。
- 感谢你赐予我前进的力量