设计模式-分类与6大原则

发布于 2021-01-08 22:21

一、设计模式的分类

设计模式可以分为三大类:

创建型模式

  1. 抽象工厂模式
  2. 生成器模式
  3. 工厂方法模式
  4. 原型模式
  5. 单例模式

结构型模式

  1. 适配器模式
  2. 桥接模式
  3. 组合模式
  4. 装饰者模式
  5. 外观模式
  6. 享元模式
  7. 代理模式

行为模式

  1. 职责链模式
  2. 命令模式
  3. 解释器模式
  4. 迭代器模式
  5. 中介者模式
  6. 备忘录模式
  7. 观察者模式
  8. 状态模式
  9. 策略模式
  10. 模板方法模式
  11. 访问者模式
设计模式之间关系 《设计模式》

二、设计模式六大原则

单一职责原则

  • 定义不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。如果一个类包含多种职责,就应该把类拆分。

  • 场景如果类A有两个职责:d1,d2。当职责d1需要修改时,可能会导致原本运行正常的职责d2功能产生问题。

  • 方案如果一个类包含多种职责,就应该把类拆分。分别建立两个类A、B,让A负责d1,B负责d2。当需要修改某一职责,那么将不会对另外一个功能产生影响。

里氏替换原则

  • 定义这一原则与继承紧密相关。如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。所有引用基类的地方必须能透明地使用其子类的对象。

  • 场景有一功能 P1,由类 A 完成。现需要将功能 P1 进行扩展,扩展后的功能为 P ,其中 P 由原有功能 P1 与新功能 P2 组成。新功能 P 由类 A 的子类 B 来完成,则子类 B 在完成新功能 P2 的同时,有可能会导致原有功能 P1 发生故障。

    // 我们需要完成一个两数相减的功能,由类A来负责。
    class A{
     public int func1(int a, int b){
      return a-b;
     }
    }

    public class Client{
     public static void main(String[] args){
      A a = new A();
      System.out.println("100-50="+a.func1(10050));
      System.out.println("100-80="+a.func1(10080));
     }
    }
    运行结果:

    100-50=50

    100-80=20

    如果我们需要增加一个新的功能:完成两数相加,然后再与100求和,由类B来负责。

    那么类B则要完成两个功能:

    由于类A已经实现了第一个功能,所以类B继承类A后,只需要再完成第二个功能就可以了,代码如下:

    class B extends A{
     public int func1(int a, int b){
      return a+b;
     }

     public int func2(int a, int b){
      return func1(a,b)+100;
     }
    }

    public class Client{
     public static void main(String[] args){
      B b = new B();
      System.out.println("100-50="+b.func1(10050));
      System.out.println("100-80="+b.func1(10080));
      System.out.println("100+20+100="+b.func2(10020));
     }
    }
    类B完成后,运行结果:

    100-50=150

    100-80=180

    100+20+100=220
    • 两数相减
    • 两数相加,然后再加100
  • 方案当使用继承时,遵循里氏替换原则。子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

    • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    • 子类中可以增加自己特有的方法。
    • 当子类的方法重载父类的方法时,方法的形参要比父类方法的输入参数更宽松。
    • 当子类的方法实现父类的抽象方法时,方法的返回值要比父类更严格。

依赖倒置原则

  • 定义高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

  • 场景类A的方法依赖类B,如果需要A通过C来实现同样的功能,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

    class Book{
     public String getContent(){
      return "书中的故事";
     }
    }

    class Mother{
     public void narrate(Book book){
      System.out.println(book.getContent());
     }
    }

    public class Client{
     public static void main(String[] args){
      Mother mother = new Mother();
      mother.narrate(new Book());
     }
    }
    运行结果:

    书中的故事

    假如有一天,我们不是给书而是给一份报纸,让这位母亲读下报纸上的事。报纸代码如下:

    class Newspaper{
     public String getContent(){
      return "报纸上的新闻";
     }
    }
  • 方案通过面向接口编程,将方法的参数类型设置为接口。而之前的具体实现类来实现接口中的方法。

    class Book implements IReader{
     public String getContent(){
      return "书中的故事";
     }
    }

    class Newspaper implements IReader{
     public String getContent(){
      return "报纸上的新闻";
     }
    }

    class Mother{
     public void narrate(IReader reader){
      System.out.println(reader.getContent());
     }
    }

    public class Client{
     public static void main(String[] args){
      Mother mother = new Mother();
      mother.narrate(new Book());
      mother.narrate(new Newspaper());
     }
    }

接口隔离原则

  • 定义实现类不应该依赖它不需要实现接口具体方法的接口。

  • 场景接口A需要实现方法a、b、c,具体实现类B实现方法a、b,实现类C实现a、c。所以B,C需要实现它们不需要的方法。

  • 方案将接口A继续拆分为独立的几个接口,类B和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

迪米特法则(最少知道原则)

  • 定义一个对象应该对其他对象保持最少的了解。也就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

  • 场景类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。

    // 总公司员工
    public class Employee {
    }

    // 子公司员工
    public class ChildEmployee {
    }


    public class ChildEmployeeService {
        // 输出所有子公司员工
        public void printAllChildEmployee() {
            List<ChildEmployee> list = new ArrayList<ChildEmployee>();
            for(int i=0; i<30; i++){
                ChildEmployee emp = new ChildEmployee();
                list.add(emp);
            }

            for (ChildEmployee childEmployee : list) {
                System.out.println(childEmployee);
            }
        }

    }


    public class EmployeeService {
        // 输出所有总公司员工
        public void printAllEmployee() {
            List<Employee> list = new ArrayList<Employee>();
            for (int i = 0; i < 30; i++) {
                Employee emp = new Employee();
                list.add(emp);
            }

            for (Employee employee : list) {
                System.out.println(employee);
            }
        }

        // 输出所有公司员工
        public void printEmployee() {
            // 输出所有子公司员工
            List<ChildEmployee> list = new ArrayList<ChildEmployee>();
            for (int i = 0; i < 30; i++) {
                ChildEmployee emp = new ChildEmployee();
                list.add(emp);
            }

            for (ChildEmployee childEmployee : list) {
                System.out.println(childEmployee);
            }

            // 输出所有总公司员工
            List<Employee> list2 = new ArrayList<Employee>();
            for (int i = 0; i < 30; i++) {
                Employee emp = new Employee();
                list2.add(emp);
            }

            for (Employee employee : list2) {
                System.out.println(employee);
            }
        }
    }

    // 测试用例
    public class Test {
        public static void main(String[] args) {
            EmployeeService e = new EmployeeService();
            e.printEmployee();
        }
    }

    现在这个设计的主要问题出在EmployeeService中,根据迪米特法则,只与直接的朋友发生通信,而ChildEmployee类和EmployeeService类并不应该有直接关系,从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,而和分公司员工的相关操作应该交给分公司来处理。

  • 方案软件编程的总的原则:低耦合,高内聚。尽量降低类与类之间的耦合。

    // 修改EmployeeService类printEmployee方法,让总公司调用子公司的业务方法来实习和子公司员工的解耦合
    public void printEmployee(ChildEmployeeService service) {
            // 输出所有子公司员工
            service.printAllChildEmployee();

            // 输出所有总公司员工
            List<Employee> list2 = new ArrayList<Employee>();
            for (int i = 0; i < 30; i++) {
                Employee emp = new Employee();
                list2.add(emp);
            }

            for (Employee employee : list2) {
                System.out.println(employee);
            }
     }

    public class Test {
        public static void main(String[] args) {
            EmployeeService e = new EmployeeService();
            e.printEmployee(new ChildEmployeeService());
        }
    }

开闭原则

  • 定义对扩展开放,对修改关闭。

  • 场景在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误。

  • 方案当软件需要变化时,不应该通过修改已有的代码来实现变化,而是尽量通过扩展原有代码。这是为了使程序的扩展性好,易于维护和升级。

本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。

相关素材