设计模式的七大原则

开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。

  • 开闭原则是编程中最基础最重要的原则
  • 一个软件实体如类,模块和函数应该对扩展(提供方方)开放对修改(使用方)关闭。用抽象构建框架,用实现扩展细节。
  • 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
  • 编程中遵循其他原则,及使用设计模式的目的就是遵循开闭原则。

单一职责原则(Single Responsibility Principle,SRP)

单一职责原则又称单一功能原则,这里的职责是指类变化的原因,单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。 该原则提出对象不应该承担太多职责,如果一个对象承担了太多的职责,至少存在以下两个缺点:

  • 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
  • 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费

一个类只负责一个职责,单一职责同样也适用于方法。一个方法应该尽可能做好一件事情。如果一个方法处理的事情太多,其颗粒度会变得很粗,不利于重用。
由于开发的时候职责的界定并不明确,可能一开始的设计是符合单一职责原则的,当职责扩散(职责扩散,就是因为某种原因,职责P被分化为粒度更细的职责P1和P2)时,原有代码便不符合单一职责原则

用一个类描述交通工具的运动

class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle + "在运动");
    }
}

运行结果:
摩托车在在运动 汽车在在运动
轮船在运动
飞机在运动

这样在以上要求看来没什么问题,但如果需求变化,需要描述出交通工具的运动地点(水、陆、空),上面代码就不符合单一职责原则了。

// 不符合单一职责原则
class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle + "在路上运动");
    }
}

运行结果:
摩托车在路上运动
汽车在路上运动
轮船在路上运动
飞机在路上运动

如果按照单一职责原则,需要将交通工具类修改成路上、水上、空中交通工具类。

class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle + "在路上运动");
    }
}

class VehicleWater{
    public void run(String vehicle){
        System.out.println(vehicle + "在水上运动");
    }
}

class VehicleAir{
    public void run(String vehicle){
        System.out.println(vehicle + "在空中运动");
    }
}

运行结果:
摩托车在路上运动
汽车在路上运动
轮船在水上运动
飞机在空中运动

如果需求再发生变化,要求将在水上运动划分为在海上运动和在淡水湖上运动,此时需要再加上一个交通工具类,但如果需求越来越多,那么修改的花销就会越来越大。
如果直接再原来的类上修改,虽然会减少花销,但又违反了单一职责原则。

class Vehicle{
// 不符合单一职责原则
    public void run(String vehicle){
        if ("摩托车".equals(vehicle)){
            System.out.println(vehicle + "在路上运动");
        }else if("轮船".equals(vehicle)){
            System.out.println(vehicle + "在水上运动");
        }else if("飞机".equals(vehicle)){
            System.out.println(vehicle + "在空中运动");
        }
    }
}

此时可以采用另一种修改方式

class Vehicle{
    public void run(String vehicle){
        System.out.println(vehicle + "在路上运动");
    }
    
    public void runAir(String vehicle){
        System.out.println(vehicle + "在空中运动");
    }

    public void runWater(String vehicle){
        System.out.println(vehicle + "在水上运动");
    }

    /**
    public void runWater1(String vehicle){
        System.out.println(vehicle + "在海上运动");
    }

    public void runWater2(String vehicle){
        System.out.println(vehicle + "在淡水湖上运动");
    }
    **/
}

这种修改方式没有改动原来的方法,而是在类中新加了一个方法,这样虽然也违背了类的单一职责原则,但在方法级别上却是符合单一职责原则的,因为它并没有动原来方法的代码。

接口隔离原则(Interface Segregation Principle)

接口隔离原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

类不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小接口上

接口Interface1有五个方法method1到method5,类A和C想调用Interface1接口中方法的不同实现,需要类B和D来实现接口并重写方法,类A调用B重写的方法,但只需要method1、method2、method3,类C调用D重写的方法,但只需要method1、method4、method5,但类B和D实现接口需要重写接口的所有方法。

类A通过接口Interface1依赖于类B,类C通过接口Interface1依赖于类D,此时接口Interface1对于类A和C来说不是最小接口,B和D必须实现接口中A和C不需要的方法

UML图

// 未使用接口隔离原则
class Segregation{
    public static void main(String[] args){
        A a = new A();
        Interface1 b = new B();
        C c = new C();
        Interface1 d = new D();
        a.depend1(b);
        c.depend1(d);
    }
}

interface Interface1{
    void method1();
    void method2();
    void method3();
    void method4();
    void method5();
}

class B implements Interface1{

    @Override
    public void method1(){
        System.out.println("类B重写了method1");
    }
    @Override
    public void method2(){
            System.out.println("类B重写了method2");
    }
    @Override
    public void method3(){
        System.out.println("类B重写了method3");
    }
    @Override
    public void method4(){
        System.out.println("类B重写了method4");
    }
    @Override
    public void method5(){
        System.out.println("类B重写了method5");
    }
}

class D implements Interface1{

    @Override
    public void method1(){
        System.out.println("类D重写了method1");
    }
    @Override
    public void method2(){
        System.out.println("类D重写了method2");
    }
    @Override
    public void method3(){
        System.out.println("类D重写了method3");
    }
    @Override
    public void method4(){
        System.out.println("类D重写了method4");
    }
    @Override
    public void method5(){
        System.out.println("类D重写了method5");
    }
}
   
class A{
    public void depend1(Interface1 b){
        b.method1();
    }
    public void depend2(Interface1 b){
        b.method2();
    }
    public void depend3(Interface1 b){
        b.method3();
    }
}

class C{
    public void depend1(Interface1 d){
        d.method1();
    }
    public void depend4(Interface1 d){
        d.method4();
    }
    public void depend5(Interface1 d){
        d.method5();
    }
}

使用接口隔离原则: 将Interface1接口拆分成几个独立的接口,A和C分别与他们需要的接口建立依赖关系

UML图

class Segregation{
    public static void main(String[] args){
        A a = new A();
        Interface1 b1 = new B();
        Interface2 b2 = new B();
        C c = new C();
        Interface1 d1 = new D();
        Interface3 d3 = new D();
        a.depend1(b1);
        c.depend1(d1);
        a.depend2(b2);
        c.depend4(d3);
    }
}

interface Interface1{
    void method1();
}

interface Interface2{
    void method2();
    void method3();
}

interface Interface3{
    void method4();
    void method5();
}

class B implements Interface1,Interface2{

    @Override
    public void method1(){
        System.out.println("类B重写了method1");
    }
    @Override
    public void method2(){
            System.out.println("类B重写了method2");
    }
    @Override
    public void method3(){
        System.out.println("类B重写了method3");
    }
}

class D implements Interface1,Interface3{

    @Override
    public void method1(){
        System.out.println("类D重写了method1");
    }
    @Override
    public void method4(){
        System.out.println("类D重写了method4");
    }
    @Override
    public void method5(){
        System.out.println("类D重写了method5");
    }
}
   
class A{
    public void depend1(Interface1 b){
        b.method1();
    }
    public void depend2(Interface2 b){
         b.method2();
    }
    public void depend3(Interface2 b){
        b.method3();
    }
}

class C{
    public void depend1(Interface1 d){
        d.method1();
    }
    public void depend4(Interface3 d){
        d.method4();
    }
    public void depend5(Interface3 d){
        d.method5();
    }
}

依赖倒置原则(Dependence Inversion Principle)

依赖倒置原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  • 抽象不应该依赖细节,细节应该依赖抽象
  • 依赖倒置的中心思想是面向接口编程,而不是面向实现类编程
  • 设计理念:相比于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定 的多。在java中,抽象指定是接口或抽象类,细节指的是具体的实现类
  • 使用接口和抽象类的目的是制定好规范,而不涉及任何具体的操作,把展示细节的任务交给他们具体的实现类去完成。

注意事项和原则:

  • 低层模块尽量有接口或抽象类,或两者都有,程序稳定性更好
  • 变量的声明类型尽量是抽象类或接口,这样变量引用和实际对象间,就存在一个缓冲层。利于程序扩展和优化。
  • 继承时遵循里氏替换原则

里氏替换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。

任何基类可以出现的地方,子类一定可以出现。里氏替换原则是继承复用的基石,只有当衍生类可以替换基类,软件单位的功能不受到影响时,即基类随便怎么改动子类都不受此影响,那么基类才能真正被复用 因为继承带来的侵入性,增加了耦合性,也降低了代码灵活性,父类修改代码,子类也会受到影响,此时就需要里氏替换原则。

  • 子类必须实现父类的抽象方法,但不得重写父类的非抽象方法。
  • 子类中可以增加自己特有的方法。
  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

迪米特法则(Demeter Principle)

迪米特法则,又称最少知道原则,是指一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

  • 一个对象应该对其他对象保持最少的了解
  • 类与类关系越密切,耦合度越大
  • 迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何信息
  • 迪米特法则还有个更简单的定义:只与直接的朋友通信
  • 直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。

注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系

合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

设计原则核心思想

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。
  • 针对接口编程,而不是针对实现编程。
  • 为了交互对象之间的松耦合设计而努力