本文最后更新于 2025-03-23,文章超过7天没更新,应该是已完结了~

设计模式七大原则:

  1. 单一职责原则

  2. 接口隔离原则

  3. 依赖倒置原则

  4. 里氏替换原则

  5. 开闭原则

  6. 迪米特法原则

  7. 合成复用原则

  1. 单一职责原则:

对类来说,一个类应该只负责一项职责。但如果类A负责两个不同职责,则当职责1需求变更而改变A类,可能造成职责2执行错误,所以需要将类A的粒度分解为A1,A2。

  1. 接口隔离原则

一个类对另一个类的依赖应该建立在最小接口上。

从图中可以看出,类 A 依赖于 接口 I 中的方法 1,2,3 ,类 B 是对类 A 的具体实现。类 C 依赖接口 I 中的方法 1,4,5,类 D 是对类 C 的具体实现。对于类B和类D来说,虽然他们都存在着用不到的方法(也就是图中红色字体标记的方法),但由于实现了接口I,所以也必须要实现这些用不到的方法。

如果想符合接口隔离原则,就必须对接口 I 如下图进行拆分:

  1. 依赖倒装原则

依赖倒转的中心思想是面向接口编程

依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类。

使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

  1. 里氏替换原则

  • 里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

  • 里氏代换原则告诉我们,在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。

  • 里氏代换原则是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,因此在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

含义:

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、在系统设计时,遵循里氏替换原则,尽量避免子类重写父类的方法,可以有效降低代码出错的可能性

  1. 开闭原则

即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码。

  1. 迪米特法则

迪米特法则(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等访问权限。

总结:迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。

  1. 合成复用原则

类A有2个方法,类B刚好需要调用这两个方法,我们第一可能会想到直接继承,这样“多快好省“,但随着业务进展,功能越来越复杂,A类需要增加其他方法,比如Method3 ,与B类毫无关联,将会大大增加耦合性,合用复用原则的核心就是使用关联,我们可以通过依赖、聚合、合成等关联方法,降低耦合,提高可维护性和降低维护成本。