《设计模式》设计模式的基本原则

《设计模式》设计模式的基本原则
《设计模式》单例模式
《设计模式》工厂模式
《设计模式》原型模式
《设计模式》建造者模式
《设计模式》适配器模式
《设计模式》桥接模式
《设计模式》装饰者模式
《设计模式》组合模式
《设计模式》外观模式
《设计模式》享元模式
《设计模式》代理模式
《设计模式》模板方法模式
《设计模式》命令模式

设计模式的基本原则就是设计模式设计的依据所在,是设计模式的基础。开发人员在编码时需要遵守这些基本原则,才能使得编写的代码可维护、可扩展、可重用、灵活性强,主要有六个基本原则:单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则和迪米特法则。

1. 单一职责原则

定义一个类只负责一项职责,如类 A 负责两个不同职责:职责1 和职责 2. 当职责1 需求变更而改变类 A 时,可能造成职责2 执行错误,因此需要将类 A 的粒度分解为:A1, A2.

应用案例

1、有一个交通工具类 Vehicle, 其包含实例方法 run(),不同的交通工具(摩托车、汽车、飞机等)通过调用 run() 方法“启动”。

public class singleresponsibility {
    
    
    public static void main(String[] args) {
    
    
        Vehicle vehicle = new Vehicle();
        vehicle.run("摩托车");
        vehicle.run("汽车");
        vehicle.run("飞机");
        vehicle.run("轮船");
    }
}

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

在案例1中的 run() 方法,类 Vehicle 负责了多项职责,即摩托车、汽车和飞机的运行,这就违反了单一职责原则。

2、为了遵守单一职责原则,应该让 Vehicle 类只负责一项职责,可以将 Vehicle 类分解为多个不同的类,根据不同的交通工具调用不同类的 run() 方法。

public class singleresponsibility {
    
    
    public static void main(String[] args) {
    
    
        RoadVehicle roadVehicle = new RoadVehicle();
        roadVehicle.run("摩托车");
        roadVehicle.run("汽车");

        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飞机");
		
        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("轮船");
    }
}

class RoadVehicle {
    
    
    public void run(String vehicle) {
    
    
        System.out.println(vehicle+"公路运行");
    }
}
class AirVehicle {
    
    
    public void run(String vehicle) {
    
    
        System.out.println(vehicle+"天空运行");
    }
}
class WaterVehicle {
    
    
    public void run(String vehicle) {
    
    
        System.out.println(vehicle+"水中运行");
    }
}

案例2中将类进行了分解,使得每个类只负责一项职责,遵守了单一职责原则。但是,可以发现将类进行分解之后,不仅需要增加新的类,而且还要改动客户端调用的代码。

3、为了减少代码的改动,并且还要遵守单一职责原则,可以选择在类中增加方法,达到这样的目的。

public class singleresponsibility {
    
    
    public static void main(String[] args) {
    
    
        Vehicle vehicle = new Vehicle();
        vehicle.run("摩托车");
        vehicle.run("汽车");
        vehicle.runAir("飞机");
        vehicle.runWater("轮船");
    }
}

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+"在水中上运行...");
    }
}

单一职责原则注意事项

  • 降低类的复杂度,一个类只负责一项职责。
  • 提高类的可读性和可维护性。
  • 降低变更引起的风险。
  • 通常情况下,我们应当遵守单一职责原则,但是当逻辑非常简单时,可以在代码级别违反单一职责原则。只有类中方法数量足够少时,才可以在方法级别遵守单一职责原则。

2. 接口隔离原则

定义一个类对另一个类的依赖应该建立在最小的接口,客户端不应该依赖它不需要的接口。

应用案例

1、类 A 通过接口 Interface1 依赖类 B,但是只会使用接口的1,2,3方法,类 C 通过接口 Interface1 依赖类 D,但只会使用接口的1,4,5方法,其 UML 图和实现代码如下所示:

在这里插入图片描述

interface Interface1 {
    
    
    void operation1();
    void operation2();
    void operation3();
    void operation4();
    void operation5();
}
class B implements Interface1 {
    
    
    @Override
    public void operation1() {
    
    
        System.out.println("B 实现了 operation1");
    }
    @Override
    public void operation2() {
    
    
        System.out.println("B 实现了 operation2");
    }
    @Override
    public void operation3() {
    
    
        System.out.println("B 实现了 operation3");
    }@Override
    public void operation4() {
    
    
        System.out.println("B 实现了 operation4");
    }
    @Override
    public void operation5() {
    
    
        System.out.println("B 实现了 operation5");
    }
}
class D implements Interface1 {
    
    
    @Override
    public void operation1() {
    
    
        System.out.println("D 实现了 operation1");
    }
    @Override
    public void operation2() {
    
    
        System.out.println("D 实现了 operation2");
    }
    @Override
    public void operation3() {
    
    
        System.out.println("D 实现了 operation3");
    }@Override
    public void operation4() {
    
    
        System.out.println("D 实现了 operation4");
    }
    @Override
    public void operation5() {
    
    
        System.out.println("D 实现了 operation5");
    }
}
class A {
    
      //A类通过接口Interface1依赖(使用)B类,但是只会使用到operation1,2,3
    public void depend1(Interface1 i) {
    
    
        i.operation1();
    }
    public void depend2(Interface1 i) {
    
    
        i.operation2();
    }
    public void depend3(Interface1 i) {
    
    
        i.operation3();
    }
}
class C {
    
     //C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
    public void depend1(Interface1 i) {
    
    
        i.operation1();
    }
    public void depend4(Interface1 i) {
    
    
        i.operation4();
    }
    public void depend5(Interface1 i) {
    
    
        i.operation5();
    }
}

在案例1中,类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,而接口 Interface1 对于类 A 和类 C 来说不是最小接口,那么类 B 和类 D 必须实现它们不需要的方法,这就违反了接口隔离原则。

2、为了让类 B 和类 D 不用实现它们不需要的方法,可以将接口 Interface1 拆分为三个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系,其 UML 图和实现代码如下所示:

在这里插入图片描述

public class Segregation1 {
    
    
    public static void main(String[] args) {
    
    
        A a = new A();
        a.depend1(new B()); //A类通过接口去依赖(使用)B类
        a.depend2(new B());
        a.depend3(new B());

        C c = new C();
        c.depend1(new D()); //C类通过接口去依赖(使用)D类
        c.depend4(new D());
        c.depend5(new D());
    }
}
//接口
interface Interface1 {
    
    
    void operation1();
}
interface Interface2 {
    
    
    void operation2();
    void operation3();
}
interface Interface3 {
    
    
    void operation4();
    void operation5();
}
class B implements Interface1, Interface2 {
    
    
    @Override
    public void operation1() {
    
    
        System.out.println("B 实现了 operation1");
    }
    @Override
    public void operation2() {
    
    
        System.out.println("B 实现了 operation2");
    }
    @Override
    public void operation3() {
    
    
        System.out.println("B 实现了 operation3");
    }
}
class D implements Interface1, Interface3 {
    
    
    @Override
    public void operation1() {
    
    
        System.out.println("D 实现了 operation1");
    }
    @Override
    public void operation4() {
    
    
        System.out.println("D 实现了 operation4");
    }
    @Override
    public void operation5() {
    
    
        System.out.println("D 实现了 operation5");
    }
}
class A {
    
      //A类通过接口Interface1依赖(使用)B类,但是只会使用到operation1,2,3
    public void depend1(Interface1 i) {
    
    
        i.operation1();
    }
    public void depend2(Interface2 i) {
    
    
        i.operation2();
    }
    public void depend3(Interface2 i) {
    
    
        i.operation3();
    }
}
class C {
    
     //C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法
    public void depend1(Interface1 i) {
    
    
        i.operation1();
    }
    public void depend4(Interface3 i) {
    
    
        i.operation4();
    }
    public void depend5(Interface3 i) {
    
    
        i.operation5();
    }
}

3. 依赖倒转原则

定义

  • 高层模块不应该依赖底层模块,两者都应该依赖其抽象。
  • 抽象不应该依赖细节,细节应该依赖抽象。
  • 依赖倒转的核心思想是面向接口编程:相对于细节的多变性,抽象要稳定的多,以抽象为基础搭建的架构比以细节为基础搭建的架构稳定的多。在面向对象编程语言中,这里的抽象就是指接口或者抽象类,细节就是指具体的实现类。
  • 使用接口或抽象类是制定好规范,不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

应用案例

1、有一个 Person 类,通过调用它的实例方法 receive() 来接收信息。

public class DependencyInversion {
    
    
    public static void main(String[] args) {
    
    
        Person person = new Person();
        person.receive(new Email());
    }
}
class Email {
    
    
    public String getInfo() {
    
    
        return "电子邮件信息:hello,world";
    }
}
class Person {
    
    
    public void receive(Email email) {
    
    
        System.out.println(email.getInfo());
    }
}

在案例1中,如果获取的对象是微信,短信等,则新增类 Wechat 同时 Person 也要增加相应的接收方法,这就使得高层模块依赖了底层模块,而不是依赖其抽象。

2、引入一个抽象的接口 IReceiver 表示接收者,EemailWechat 各自实现接口 IReceiver,之后 Person 类与接口 IReceiver 发生依赖,符合依赖倒转原则。

public class DependencyInversion {
    
    
    public static void main(String[] args) {
    
    
        //客户端无需改变
        Person person = new Person();
        person.receive(new Email());
        person.receive(new Wechat());
    }
}
interface IReceiver {
    
    
    String getInfo();
}
class Email implements IReceiver{
    
    
    @Override
    public String getInfo() {
    
    
        return "电子邮件信息:hello,world";
    }
}
class Wechat implements IReceiver {
    
    
    @Override
    public String getInfo() {
    
    
        return "微信消息:hello,world";
    }
}
class Person {
    
    
    public void receive(IReceiver receiver) {
    
    
        System.out.println(receiver.getInfo());
    }
}

依赖关系传递的三种方式

1、接口传递

public class DependencyPass {
    
    
    public static void main(String[] args) {
    
    
        OpenAndClose openAndClose = new OpenAndClose();
        openAndClose.open(new HUAWEI());
    }
}
interface IOpenAndClose {
    
    
    public void open(ITV tv);
}
interface ITV {
    
    
    public void play();
}
class HUAWEI implements ITV {
    
    

    @Override
    public void play() {
    
    
        System.out.println("打开华为电视机");
    }
}
class OpenAndClose implements IOpenAndClose {
    
    

    @Override
    public void open(ITV tv) {
    
    
        tv.play();
    }
}

2、构造器传递

public class DependencyPass {
    
    
    public static void main(String[] args) {
    
    
        OpenAndClose openAndClose = new OpenAndClose(new HUAWEI());
        openAndClose.open();
    }
}
interface IOpenAndClose {
    
    
    void open();
}
interface ITV {
    
    
    void play();
}
class HUAWEI implements ITV {
    
    

    @Override
    public void play() {
    
    
        System.out.println("打开华为电视机");
    }
}
class OpenAndClose implements IOpenAndClose {
    
    
    
    public ITV tv;
    public OpenAndClose(ITV tv) {
    
    
        this.tv = tv;
    }

    @Override
    public void open() {
    
    
        tv.play();
    }
}

3、通过 setter 方式传递

public class DependencyPass {
    
    
    public static void main(String[] args) {
    
    
        OpenAndClose openAndClose = new OpenAndClose();
        openAndClose.setTv(new HUAWEI());
        openAndClose.open();
    }
}
interface IOpenAndClose {
    
    
    void open();
    void setTv(ITV tv);
}
interface ITV {
    
    
    void play();
}
class HUAWEI implements ITV {
    
    

    @Override
    public void play() {
    
    
        System.out.println("打开华为电视机");
    }
}
class OpenAndClose implements IOpenAndClose {
    
    

    private ITV tv;

    @Override
    public void open() {
    
    
        tv.play();
    }

    @Override
    public void setTv(ITV tv) {
    
    
        this.tv = tv;
    }
}

依赖倒转原则的注意事项

  • 低层模块尽量都要有抽象类或接口,或两者都有,这样程序的稳定性才更好。
  • 变量的声明类型尽量是抽象类或接口,这样变量的引用和实际对象间存在一个缓冲,有利于程序扩展和优化。

4. 里氏替换原则

定义:如果对每个类型为 T1 的对象 o1, 都有类型为 T2 的对象 o2, 使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换而言之,所有引用基类的地方必须能够透明地使用其子类的对象。

应用案例

1、类 A 作为基类,类 B 继承类 A 并重写了类 A 中的 func() 方法。

public class Liskov {
    
    
    public static void main(String[] args) {
    
    
        A a = new A();
        System.out.println("11-3="+a.func(11, 3));
        
        System.out.println("--------------------");

        B b = new B();
        System.out.println("11-3="+b.func(11, 3));//本意是求出11-3,却变成11+3
    }
}
//A类
class A {
    
    
	//增加新的功能,这里重写了A类的方法,可能是无意识的
    public int func(int num1, int num2) {
    
    
        return num1 - num2;
    }
}
class B extends A {
    
    
	//增加新的功能,无意识地重写了A类的方法
    public int func(int a, int b) {
    
    
        return a + b;
    }
}
  • 在案例1中,类 B 重写了基类 A 的 func1() 方法,即使可能是无意识的重写,原本行为是实现两数相加却变成了两数相减,改变了程序的行为这就违背了里氏替换原则。
  • 在实际场景中,通过重写父类的方法完成新的功能这种方式实现起来确实简单,但是将会导致整个继承体系的复用性会比较差,特别是在多态使用频繁的时候。

2、为了遵守里氏替换原则,可以将原有的继承关系去掉,让原来的父子类都继承一个基类,采用依赖、聚合和组合关系替代继承关系,其 UML 图和实现代码如下所示:

在这里插入图片描述

public class Liskov {
    
    
    public static void main(String[] args) {
    
    
        A a = new A();
        System.out.println("11-3="+a.func1(11, 3));

        System.out.println("--------------------");

        B b = new B();
        System.out.println("11+3="+b.func1(11, 3));//这里本意是求出11+3
        System.out.println("11-3="+b.func2(11, 3)); //11-3
    }
}
//创建一个更加基础的基类
class Base {
    
    
    //把更加基础的方法和成员写到Base类
}
class A extends Base{
    
    
    public int func1(int num1, int num2) {
    
    
        //返回两个数的差
        return num1 - num2;
    }
}
class B extends Base {
    
    
    //如果B类需要使用A类的方法,使用组合关系
    private A a = new A();
   
    public int func1(int a, int b) {
    
    
        return a + b;
    }
    //仍然想使用A的方法
    public int func2(int a, int b) {
    
    
        return a.func1(a,b);
    }
}

里氏替换原则的注意事项

  • 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
  • 继承让两个类的耦合性变得更强,为了降低类间的耦合性,适当情况下可以使用聚合、组合和依赖来解决问题。

5. 开闭原则

定义

  • 一个软件实体类,模块和函数应该对扩展对扩展开放(对服务提供方开放),对修改关闭(对服务使用方),用抽象构建框架用实体扩展细节。
  • 当软件需求发生变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码实现变化。

应用案例

1、有一个画图类 Graphical , 通过调用方法 drawShape(Shape s) 进行画图,根据传入的 s.shape 可以画出不同的图形,其 UML 图和实现代码如下所示:

在这里插入图片描述

public class OCP {
    
    
    public static void main(String[] args) {
    
    
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
    }
}
class GraphicEditor {
    
    
    public void drawShape(Shape s) {
    
    
        if (s.m_type == 1) {
    
    
            drawRectangle(s);
        } else if (s.m_type == 2) {
    
    
            drawCircle(s);
        }
    }

    public void drawRectangle(Shape r) {
    
    
        System.out.println("绘制矩形");
    }

    public void drawCircle(Shape r) {
    
    
        System.out.println("绘制圆形");
    }
}

class Shape {
    
    
    int m_type;
}

class Rectangle extends Shape {
    
    
    Rectangle() {
    
    
        super.m_type = 1;
    }
}

class Circle extends Shape {
    
    
    Circle() {
    
    
        super.m_type = 2;
    }
}

在案例1中,如果需要新增一个可以画三角形的功能,不仅需要新增类 Triangle 继承 Shape,与此同时还要修改类 GraphicEditor ,在 drawShape(Shape s) 新加一个分支,并且还要增加一个 drawTriangle(Shape s) 方法。这一系列行为都是对使用方的修改,违背了开闭原则。

2、为了遵守开闭原则,可以考虑将 Shape 作为抽象类并提供抽象方法 draw(),让子类实现抽象类 Shape,每当有新的图形种类增加时,可以直接继承 Shape 并实现 draw() 方法,无须对使用方进行修改。

public class OCP {
    
    
    public static void main(String[] args) {
    
    
        GraphicEditor graphicEditor = new GraphicEditor();
        graphicEditor.drawShape(new Rectangle());
        graphicEditor.drawShape(new Circle());
        graphicEditor.drawShape(new Triangle());
        graphicEditor.drawShape(new OtherGraphic());
    }
}

class GraphicEditor {
    
    
    public void drawShape(Shape s) {
    
    
        s.draw();
    }
}
abstract class Shape {
    
    
    public abstract void draw();
}

class Rectangle extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("绘制矩形");
    }
}

class Circle extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("绘制圆形");
    }
}

class Triangle extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("绘制三角形");
    }
}
class OtherGraphic extends Shape {
    
    
    @Override
    public void draw() {
    
    
        System.out.println("绘制其他图形");
    }
}

6. 迪米特法则

定义

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

应用案例:

1、有一个学校,下面有总部和各个学院,要求打印出学校总部员工和各个学院员工的 ID.

public class Demeter {
    
    
    public static void main(String[] args) {
    
    
        //创建了一个SchoolManager对象
        SchoolManager schoolManager = new SchoolManager();
        //输出学院员工和学校总部员工ID
        schoolManager.printAllEmployee(new CollegeManager());
    }
}
//学校总部员工
class Employee {
    
    
    private String id;

    public void setId(String id) {
    
    
        this.id = id;
    }

    public String getId() {
    
    
        return id;
    }
}
class CollegeEmployee {
    
    
    private String id;
    
    public void setId(String id) {
    
    
        this.id = id;
    }
    
    public String getId() {
    
    
        return id;
    }
}
class CollegeManager {
    
    
    //返回学院所有员工
    public List<CollegeEmployee> getAllEmployee() {
    
    
        ArrayList<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("学院员工id="+i);
            list.add(emp);
        }
        return list;
    }
}

class SchoolManager {
    
    
    //返回学校总部所有员工
    public List<Employee> getAllEmployee() {
    
    
        ArrayList<Employee> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
    
    
            Employee emp = new Employee();
            emp.setId("学校总部员工id="+i);
            list.add(emp);
        }
        return list;
    }
    //输出学校总部和学院员工信息
    public void printAllEmployee(CollegeManager sub) {
    
     //CollegeManager作为printAllEmployee局部变量
        List<CollegeEmployee> list1 = sub.getAllEmployee();
        System.out.println("--------------分公司员工-------------");
        for (CollegeEmployee e : list1) {
    
    
            System.out.println(e.getId());
        }
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("-------------学校总部员工-------------");
        for (Employee e : list2) {
    
    
            System.out.println(e.getId());
        }
    }
}

在案例1中,CollegeEmployee 以局部变量的形式出现在 SchoolManager 类中,作为 SchoolManager 的陌生类,并不是它的直接朋友,这就违背了迪米特法则。

2、为了遵守迪米特法则,在类中应该避免这种非直接朋友关系的耦合,将 printEmployee() 方法封装到类 CollegeManager 中,而将类 CollegeManager 作为类 SchoolManager 的直接朋友。

public class Demeter1 {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("使用迪米特法则的改进");
        //创建了一个SchoolManager对象
        SchoolManager schoolManager = new SchoolManager();
        //输出学院员工id 和 学校总部员工信息
        schoolManager.printAllEmployee(new CollegeManager());
    }
}

//学校总部员工
class Employee {
    
    
    private String id;

    public void setId(String id) {
    
    
        this.id = id;
    }

    public String getId() {
    
    
        return id;
    }
}

class CollegeEmployee {
    
    
    private String id;
    
    public void setId(String id) {
    
    
        this.id = id;
    }
    public String getId() {
    
    
        return id;
    }
}

class CollegeManager {
    
    
    //返回学院所有员工
    public List<CollegeEmployee> getAllEmployee() {
    
    
        ArrayList<CollegeEmployee> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
    
    
            CollegeEmployee emp = new CollegeEmployee();
            emp.setId("学院员工id="+i);
            list.add(emp);
        }
        return list;
    }
    public void printEmployee() {
    
    
        List<CollegeEmployee> list1 = this.getAllEmployee();
        System.out.println("--------------分公司员工-------------");
        for (CollegeEmployee e : list1) {
    
    
            System.out.println(e.getId());
        }
    }
}

class SchoolManager {
    
    
    //返回学校总部所有员工
    public List<Employee> getAllEmployee() {
    
    
        ArrayList<Employee> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
    
    
            Employee emp = new Employee();
            emp.setId("学校总部员工id="+i);
            list.add(emp);
        }
        return list;
    }
    //输出学校总部和学院员工信息
    public void printAllEmployee(CollegeManager sub) {
    
    
        //将获取学院员工的方法封装到CollegeManager类里面
        sub.printEmployee();
        List<Employee> list2 = this.getAllEmployee();
        System.out.println("-------------学校总部员工-------------");
        for (Employee e : list2) {
    
    
            System.out.println(e.getId());
        }
    }
}

迪米特法则的注意事项

  • 核心思想是降低类之间的耦合。
  • 对每个类减少不必要的依赖,降低类之间的耦合关系,并不是严格要求完全没有依赖关系。

猜你喜欢

转载自blog.csdn.net/weixin_43252521/article/details/127187936