小侃设计模式(一)-设计模式七大原则

1.概述

设计模式(Design Pattern)是软件设计领域的一棵长青树,它是一套被反复使用、多人知晓、经过分类编目的、优秀代码设计经验的总结。使用设计模式是为了设计出易理解、可复用、可靠的代码,增强程序的可复用性。设计模式由Erich Gamma等人从建筑领域引入软件设计领域,成为了软件开发的指导思想,目前已成为高级软件工程师不可或缺的知识。本文将分系列讲述设计模式的原理以及使用场景,来帮助大家更好地理解和使用设计。第一系列主要讲述设计模式的七大设计原则(也有说六大设计原则,本文将合成复用原则算入)。

2.设计模式七大原则

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

定义:一个类或者模块应该有且只有一个改变的原因。如果一个类A负责两个不同的职责:职责1和职责2,当职责1需求变更而改变A时,可能会造成职责2执行错误,所以需要将类A拆分成A1和A2。
案例:有一个交通类,它有一个run方法,表示交通工具正在运行。

public class SingleResponsibility {
    public static void main(String[] args) {
        Vehicle vehicle = new Vehicle();
        vehicle.run("汽车");
        vehicle.run("摩托车");
        vehicle.run("卡车");
    }
}


class Vehicle {

    public void run(String name) {
        System.out.println(name + "在公路上跑!");
    }

}

此时出现一个新的问题,就是飞机和轮船这种交通工具,都不是在公路上跑的,按照单一职责进行修改,需要将Vehicle拆分成AirVehicle和RoadVehicle 和WaterVehicle,代码如下:

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

        AirVehicle airVehicle = new AirVehicle();
        airVehicle.run("飞机");

        WaterVehicle waterVehicle = new WaterVehicle();
        waterVehicle.run("轮船");
    }

}


class RoadVehicle {

    public void run(String name) {
        System.out.println(name + "在路上跑!");
    }

}

class AirVehicle {

    public void run(String name) {
        System.out.println(name + "在天空中飞!");
    }

}


class WaterVehicle {

    public void run(String name) {
        System.out.println(name + "在水里跑!");
    }

}

这种修改的方式在时间开发中会导致子类的激增,而且要修改大量地方。而如果直接修改Vehicle的run方法(添加if…else…语句进行判断),又违背了单一职责原则。因此可以考虑方法级别的单一职责原则(不修改原来的类,在方法层面进行区分)。修改代码如下:

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


class Vehicle {

    public void run(String name) {
        System.out.println(name + "在路上跑!");
    }

    public void runAir(String name) {
        System.out.println(name + "在天空中飞!");
    }

    public void runWater(String name) {
        System.out.println(name + "在水里跑!");
    }

}

这种方式一定程度上违背了单一职责,但是相对修改地方较少,而且在方法层面仍然遵守单一职责原则,日常开发中也使用较多。

单一职责的优势和缺陷如下:
优势: 降低了类的复杂度,提高了代码可读性,提高了系统的可维护性;
缺陷:若处理不当,可能会导致子类激增且需要修改大量地方。

2.2 接口隔离原则(Interface Segregation Principle,ISP)

定义:客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。
案例:类A通过抽象接口依赖类B,类C通过抽象接口依赖于类D,而类B的核心方法只需实现抽象接口中的operation1(params)、operation2(params);类D中的核心方法只需实现抽象接口中的operation3()和operation4()。由于抽象接口不是最小接口,因此类B和D必须去实现他们不需要的方法。

在这里插入图片描述
按照接口隔离原则改进:需要将抽象接口拆分成几个独立的接口,类A和类C分别与他们需要的接口建立关系,拆分后类图如下:
在这里插入图片描述
上图中将抽象接口拆分成了3个接口,类A通过抽象接口1与类B建立联系,类C通过抽象接口2与类D建立联系。

优势:在一定程度上补充了单一职责原则,降低了耦合度;
缺点:可能由于场景的复杂性导致拆分接口过多,导致接口数量爆增;

2.3 开闭原则(Open-Close Principle,OCP)

定义:一个软件实体(如类),模块和函数应该扩展对外开放,对修改关闭。用抽象构建框架,用实现扩展细节。当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。开闭原则是编程中最基础、最重要的设计原则
案例:有一个绘图工具类,根据传入的type来绘制不同的形状,代码如下:

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

}


class GraphicEditor {

    public void drawShape(Shape shape) {
        if (shape.type == 1) {
            drawCircle();
        } else if (shape.type == 2) {
            drawRectangle();
        }
    }

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

    public void drawRectangle() {
        System.out.println("绘制三角形");
    }
}

class Shape {
    int type;
}

class Circle extends Shape {
    public Circle() {
        super.type = 1;
    }
}

class Rectangle extends Shape {
    public Rectangle() {
        super.type = 2;
    }
}

上述代码的问题在于,当需要添加一个绘制正方形的方法时,需要实现Shape类,并修改GraphicEditor 类中的drawShape方法。如下:

class Square extends Shape {
    public Square() {
        super.type = 3;
    }
}

class GraphicEditor {

    public void drawShape(Shape shape) {
        if (shape.type == 1) {
            drawCircle();
        } else if (shape.type == 2) {
            drawRectangle();
        } else if (shape.type == 3) {
            drawSquare();
        }
    }

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

    public void drawRectangle() {
        System.out.println("绘制三角形");
    }

    public void drawSquare() {
        System.out.println("绘制正方形");
    }
}

以此类推,如果再新增其它的形状,需要不断进行扩充和修改,严重违背开闭原则。修改思路如下:将Shape类变成一个抽象类或者接口,子类去继承或实现Shape中的方法,当新增图形时,只需继承或实现Shape类即可,满足开闭原则。修改结果如下:

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

}

class GraphicEditor {

    public void drawShape(Shape shape) {
        shape.draw();
    }

}


abstract class Shape {
    int type;

    public void draw() {
    }
}


class Circle extends Shape {

    @Override
    public void draw() {
        System.out.println("绘制圆形!");
    }
}

class Triangle extends Shape {

    @Override
    public void draw() {
        System.out.println("绘制三角形!");
    }
}

优势:使系统具有良好的可扩展性,可维护性;
缺点:如果使用不好可能会导致类数量的递增。

2.4 里氏替换原则(Liskov Substitution Principle,LSP)

定义: 里氏替换原则主要使用来解决继承所带来的耦合性,一个类被另一个类继承,则就与其它类产生耦合,一但修改了父类,则可能影响其大多数子类。比如有一个类A,内部有一个方法method1()实现了一个功能,现在A的子类B要对该功能进行扩展,于是再method1()方法内添加了部分功能,此时新的method1就可能会影响原来A的method1()方法。
案例:有一个类A,内部有一个方法add(),完成两数相加,其子类B不小心重新了add()方法,变成了两数相减,B内部另一个方法addTen()实现两数相加,再加10。

public class A {
    protected int add(int a, int b) {
        return a + b;
    }
}

class B extends A {
    public int add(int a, int b) {
        return a - b;
    }

    public int addTen(int a, int b) {
        return add(a, b) + 10;
    }
}

当测试B的addTen()方法时出现了异常,原本两数相加后再加10,变成了两数相减后再加10。

里氏替换原则的本质强调的是:子类可以扩展父类的功能,但不能改变父类的功能。

2.5 依赖倒置原则(Dependence Inversion Principle,DIP)

定义:高层模块不应该依赖于低层模块,二者都应该依赖于其抽象;抽象不应该依赖于细节,细节应该依赖于抽象;依赖倒置的核心思想是面向接口编程
案例:有一个Person类,能够接收邮件信息并读取邮件信息。代码如下:

public class DIP {
    public static void main(String[] args) {
        Email email = new Email();
        Person person = new Person();
        person.read(email);
    }
}

class Email {
    public String getInfo() {
        return "发送邮件信息:hello world";
    }
}

class Person {
    public void read(Email email) {
        String info = email.getInfo();
        System.out.println(info);
    }
}

如果此时需要接收微信信息,则需要修改代码如下:

public class DIP {
    public static void main(String[] args) {
        Email email = new Email();
        Person person = new Person();
        person.read(email);
        final WeiXin weiXin = new WeiXin();
        person.readWeiXin(weiXin);
    }
}

class Email {
    public String getInfo() {
        return "发送邮件信息:hello world";
    }
}

class WeiXin {
    public String getInfo() {
        return "发送微信信息:baby";
    }
}

class Person {
    public void read(Email email) {
        String info = email.getInfo();
        System.out.println(info);
    }

    public void readWeiXin(WeiXin weiXin) {
        String info = weiXin.getInfo();
        System.out.println(info);
    }

}

这种改法很明显违背单一职责原则和开闭原则,结合依赖倒置原则,修改代码如下:

public class DIP {
    public static void main(String[] args) {
        Email email = new Email();
        Person person = new Person();
        person.read(email);
        person.read(new WeiXin());
    }
}

interface ReadInfo {
    String getInfo();
}


class Email implements ReadInfo {
    public String getInfo() {
        return "发送邮件信息:hello world";
    }
}

class WeiXin implements ReadInfo {
    public String getInfo() {
        return "发送微信信息:baby";
    }
}

class Person {
    public void read(ReadInfo readInfo) {
        String info = readInfo.getInfo();
        System.out.println(info);
    }
}

这样修改之后,所有要被读取的信息类型(比如QQ、钉钉等),形成子类继承ReadInfo接口即可,然后传入Person类的read方法中即可被读取。这里Person代表的则是高层模块,完成主要的业务逻辑,一旦对其修改,则可能引入巨大风险,所以遵循依赖倒置会降低类之间的耦合度,减少风险。

2.6 迪米特法则(Demeter Principle)

定义:一个对象应该对其它对象保持最少的了解,类与类的关系越密切,则耦合度越大。迪米特法则又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。一个类被依赖的类不管多复杂,都尽量将自己的逻辑封装在类的内部,对外除了提供public方法,不对外泄漏任何信息。
案例:有一个学院管理类CollegeManager,内部拥有打印员工信息的方法,同时有一个学校管理类SchoolManager,内部拥有打印学校员工信息和学院员工信息的方法,代码如下:

public class SchoolManager {

    public List<Employee> getAllEmployees() {
        List<Employee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Employee employee = new Employee();
            employee.setId(Long.valueOf(i));
            employee.setName(getName());
            employees.add(employee);
        }
        return employees;
    }


    public void printAllEmployee(CollegeManager sub) {
    	//此处违反迪米特法则,具体原因如下:
    	//1.这里的CollegeEmployee不是SchoolManager 的直接朋友
    	//2.CollegeEmployee是以局部变量方式出现在SchoolManager 中
        List<CollegeEmployee> allEmployees = sub.getAllEmployees();
        System.out.println("打印学院人员信息---------");
        allEmployees.forEach(employee -> {
            System.out.println(employee.getId() + ":" + employee.getName());
        });
        System.out.println("打印学校人员信息---------");
        List<Employee> allEmployees1 = this.getAllEmployees();
        allEmployees1.forEach(employee -> {
            System.out.println(employee.getId() + ":" + employee.getName());
        });
    }

    public String getName() {
        String str = "abcdefghijklmnopqrstuvwxyz";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        sb.append("college-");
        for (int j = 0; j < 5; j++) {
            int i = random.nextInt(str.length());
            char c = str.charAt(i);
            sb.append(c);
        }
        return sb.toString();
    }
}

class CollegeEmployee {
    
    private Long id;

    private String name;


    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

class Employee {

    private Long id;

    private String name;


    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


class CollegeManager {
    public List<CollegeEmployee> getAllEmployees() {
        List<CollegeEmployee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee employee = new CollegeEmployee();
            employee.setId(Long.valueOf(i));
            employee.setName(getName());
            employees.add(employee);
        }
        return employees;
    }

    public String getName() {
        String str = "abcdefghijklmnopqrstuvwxyz";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        sb.append("school-");
        for (int j = 0; j < 5; j++) {
            int i = random.nextInt(str.length());
            char c = str.charAt(i);
            sb.append(c);
        }
        return sb.toString();
    }

}

按照迪米特法则(最少知道原则),需要将打印员工的方法封装在CollegeManager 中,只对外提供public类型的打印方法,修改代码如下:

   public void printAllEmployee(CollegeManager sub) {
        System.out.println("打印学院人员信息---------");
        sub.printEmployees();
        System.out.println("打印学校人员信息---------");
        List<Employee> allEmployees1 = this.getAllEmployees();
        allEmployees1.forEach(employee -> {
            System.out.println(employee.getId() + ":" + employee.getName());
        });
    }

class CollegeManager {
    public List<CollegeEmployee> getAllEmployees() {
        List<CollegeEmployee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            CollegeEmployee employee = new CollegeEmployee();
            employee.setId(Long.valueOf(i));
            employee.setName(getName());
            employees.add(employee);
        }
        return employees;
    }


    public void printEmployees() {
        List<CollegeEmployee> allEmployees = this.getAllEmployees();
        allEmployees.forEach(collegeEmployee -> {
            System.out.println(collegeEmployee.getId() + ":" + collegeEmployee.getName());
        });
    }

    public String getName() {
        String str = "abcdefghijklmnopqrstuvwxyz";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        sb.append("school-");
        for (int j = 0; j < 5; j++) {
            int i = random.nextInt(str.length());
            char c = str.charAt(i);
            sb.append(c);
        }
        return sb.toString();
    }

}

迪米特法则中强调直接朋友:每个对象都会与其它对象产生耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系,耦合的方式有很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类为非直接朋友,也就是说,陌生的类不要以局部变量的形式出现在类的内部。

2.7 合成复用原则(Composite Reuse Principle,CRP)

定义:尽量使用合成/聚合的方式,而不是使用继承。
案例:A要使用B的方法,尽量不要使用继承的方式,优先使用合成、聚合方式。代码如下:

public class CompositeReuse {
    public static void main(String[] args) {
        System.out.println("------依赖------");
        B b = new B();
        b.Operation1(new A());

        System.out.println("------聚合------");
        b.setA(new A());
        b.Operation2();

        System.out.println("------组合------");
        b.Operation3();
    }
}

class A {
    void Operation1() {
        System.out.println("A Operation1");
    }

    void Operation2() {
        System.out.println("A Operation2");
    }

    void Operation3() {
        System.out.println("A Operation3");
    }
}

//如果只是需要用到 A类的方法,尽量不要使用继承。而是使用:依赖,聚合,组合的方式
class B {
    //通过参数传递形式,将A注入到B的某个方法中(依赖)
    void Operation1(A a) {
        a.Operation1();
        a.Operation2();
        a.Operation3();
    }

    //----------------------------------------------------------------------
    //通过set注入方式,将A变成B的属性成员(聚合)
    A a;

    public void setA(A a) {
        this.a = a;
    }

    void Operation2() {
        a.Operation1();
        a.Operation2();
        a.Operation3();
    }

    //----------------------------------------------------------------------
    //通过对象创建方式,将A变成B的属性(组合)
    A a1 = new A();

    void Operation3() {
        a1.Operation1();
        a1.Operation2();
        a1.Operation3();
    }
}

设计模式的核心思想:
(1) 将应用中可能出现的变化之处进行独立,不要和那些不需要变化的代码混合在一起;
(2) 针对接口编程,而不是针对实现编程;
(3)为了交互对象之间的松耦合设计而努力。

2.8 类之间的关系

类之间的关系包括:依赖、泛化(继承)、实现、关联、聚合与组合。

2.8.1 依赖

一个使用了另一个类,那么他们之间就存在依赖关系。如果没有对方,就连编译都无法通过。类之间依赖包括以下几种情形: (1)在类中使用到了对方;
(2)一个类作为另一个类的成员属性;
(3)方法接收的参数类型;
(4)方法的返回类型;
(5)一个类在方法中使用到了另一个类。

类图如下:
在这里插入图片描述

2.8.2 泛化(继承)

泛化其实就是继承关系,是依赖关系的一种特例。
在这里插入图片描述

2.8.3 实现

实现关系实际上就是 一个类实现一个接口,依赖关系的特例。

在这里插入图片描述

2.8.4 关联

类与类之间的关系,依赖关系的特例,关联包括单向或者双向。

在这里插入图片描述在这里插入图片描述

2.8.5 聚合

聚合表示的是整体和部分的关系,整体和部分的关系,也是关联关系的一种特例,因此它具有关联关系的导航性与多重性。
在这里插入图片描述

2.8.6 组合

组合也是整体和部分的关系,但是整体和部分不可以分开,关联关系的特例。

在这里插入图片描述

3.小结

1.设计模式的本质就是要降低程序耦合程度,提高可复用性;
2.设计模式的原则是设计模式的根本指导思想,本质就算指导设计出可复用更强的程序;
3.类之间的关系共有六种:依赖、泛化(继承)、实现、关联、聚合与组合。

4.参考文献

1.《设计模式-可复用面向对象软件的基础》-Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides
2.《可复用物联网Web3D框架的设计与实现》-程亮(知网)
3.https://www.bilibili.com/video/BV1G4411c7N4-尚硅谷设计模式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值