草庐IT

设计模式之概述篇

YuShixin 2023-03-28 原文

1、设计模式的本质

面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。

2、设计模式的目的

提高代码可读性、重用性、可靠性、可扩展性,实现“高内聚,低耦合”。

名词解释

  1. 可读性:按照规范编程,便于其他程序员阅读和理解
  2. 重用性:相同功能的代码,可以重复使用,无需多次编写
  3. 可靠性:增加功能时,对原有功能没有影响
  4. 可扩展性:增加功能时方便,可维护性强

3、设计模式的依据

经典的设计模式有23种,但是发展到今天还有很多叫不上名字来的设计模式,无一例外都遵循着“软件设计七大原则”。

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

3.1.1 解释

​ 单一职责就是一个类或者一个方法只负责一项职责

3.1.2 举例

​ 假设有一个IT部门,一个开发,一个测试,一个运维。我们把三个人的工作抽象为一个类

3.1.2.1 Demo1
public static void main(String[] args) {
    Employee.work("Developer");
    Employee.work("Tester");
    Employee.work("Operator");
}
// 员工类
static class Employee {
    public static void work(String name) {
        System.out.println(name + "正在写代码...");
    }
}

运行结果

Developer正在写代码...
Tester正在写代码...
Operator正在写代码...

​ 很明显,开发、测试、运维都在写代码,显然不合理;正常来说,开发写代码、测试写用例、运维写脚本,而Demo1中的work实现了三种不同职责,违背了单一职责原则

3.1.2.2 Demo2
public static void main(String[] args) {
    Developer.work("Developer");
    Tester.work("Tester");
    Operator.work("Operator");
}
// 员工类:开发
static class Developer {
    public static void work(String name) {
        System.out.println(name + "正在写代码...");
    }
}
// 员工类:测试
static class Tester {
    public static void work(String name) {
        System.out.println(name + "正在写用例...");
    }
}
// 员工类:运维
static class Operator {
    public static void work(String name) {
        System.out.println(name + "正在写脚本...");
    }
}

运行结果

Developer正在写代码...
Tester正在写用例...
Operator正在写脚本...

​ 看运行结果,已经符合了单一职责原则,但是看Demo2代码会发现,三种职责我们创建了三个类,把Employee分解为DeveloperTesterOperator,并且调用方main也做了修改,这样改动太大

3.1.2.3 Demo3
public static void main(String[] args) {
    Employee.workCode("Developer");
    Employee.workUseCase("Tester");
    Employee.workScript("Operator");
}
// 员工类
static class Employee {
    public static void workCode(String name) {
        System.out.println(name + "正在写代码...");
    }
    public static void workUseCase(String name) {
        System.out.println(name + "正在写用例...");
    }
    public static void workScript(String name) {
        System.out.println(name + "正在写脚本...");
    }
}

运行结果

Developer正在写代码...
Tester正在写用例...
Operator正在写脚本...

​ 在Demo3中把work一分为三,没有分解类,而是在方法级别上进行了拆分,也达到了预期的效果,并且调用者main中改动量也很小

3.1.3 总结

​ 1. 单一职责可以细化为类级别方法级别,最低限度是在方法级别保证单一职责原则;但是如果一个类中有几十个方法,那么就需要衡量下是否需要进行类分解

​ 2. 提高代码可读性,可维护性,可扩展性

​ 3. 降低类的复杂度

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

3.2.1 解释

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

3.2.2 举例

​ 就拿凉拌黄瓜和炒黄瓜的步骤来举例

  1. 凉拌黄瓜:洗菜 -> 切菜 -> 凉拌

  2. 炒 黄 瓜:洗菜 -> 切菜 -> 炒菜

3.2.2.1 Demo1
private static final String name = "黄瓜";
public static void main(String[] args) {
    CookingCold cookingCold = new CookingCold();
    ColdMixCucumber coldMixCucumber = new ColdMixCucumber();
    coldMixCucumber.wash(cookingCold);
    coldMixCucumber.cut(cookingCold);
    coldMixCucumber.coldMix(cookingCold);
    System.out.println();
    CookingHot cookingHot = new CookingHot();
    FryCucumber fryCucumber = new FryCucumber();
    fryCucumber.wash(cookingHot);
    fryCucumber.cut(cookingHot);
    fryCucumber.fry(cookingHot);;
}
// 做菜接口
interface Cooking {
    // 洗菜
    void wash(String name);
    // 切菜
    void cut(String name);
    // 凉拌
    void coldMix(String name);
    // 炒菜
    void fry(String name);
}
// 做凉菜
static class CookingCold implements Cooking {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void coldMix(String name) {
        System.out.println("凉拌" + name);
    }
    @Override
    public void fry(String name) {}
}
// 做热菜
static class CookingHot implements Cooking {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void coldMix(String name) {}
    @Override
    public void fry(String name) {
        System.out.println("炒" + name);
    }
}
// 凉拌黄瓜
static class ColdMixCucumber {
    // 洗黄瓜
    public void wash(Cooking cooking) {
        cooking.wash(name);
    }
    // 切黄瓜
    public void cut(Cooking cooking) {
        cooking.cut(name);
    }
    // 凉拌黄瓜
    public void coldMix(Cooking cooking) {
        cooking.coldMix(name);
    }
}
// 炒黄瓜
static class FryCucumber {
    // 洗黄瓜
    public void wash(Cooking cooking) {
        cooking.wash(name);
    }
    // 切黄瓜
    public void cut(Cooking cooking) {
        cooking.cut(name);
    }
    // 炒黄瓜
    public void fry(Cooking cooking) {
        cooking.fry(name);
    }
}

​ 下图为Demo1的UML类图,据此分析CookingColdCookingHot均实现了Cooking接口并实现其所有方法,但在ColdMixCucumberFryCucumber中仅使用了其中的三个方法;虽然运行结果没有问题,但是明显Cooking接口的设计不合理,不符合接口隔离原则

3.2.2.2 Demo2

​ 我们注意到Cooking接口中,washcut是都会用到的,而coldMixfry并不会全部用到;根据接口隔离原则,我们把Cooking接口分解为三个接口,UML类图如下所示:

private static final String name = "黄瓜";
public static void main(String[] args) {
    CookingCold cookingCold = new CookingCold();
    ColdMixCucumber coldMixCucumber = new ColdMixCucumber();
    coldMixCucumber.wash(cookingCold);
    coldMixCucumber.cut(cookingCold);
    coldMixCucumber.coldMix(cookingCold);
    System.out.println();
    CookingHot cookingHot = new CookingHot();
    FryCucumber fryCucumber = new FryCucumber();
    fryCucumber.wash(cookingHot);
    fryCucumber.cut(cookingHot);
    fryCucumber.fry(cookingHot);;
}
// 做菜接口_01
interface Cooking_01 {
    // 洗菜
    void wash(String name);
    // 切菜
    void cut(String name);
}
// 做菜接口_02
interface Cooking_02 {
    // 凉拌
    void coldMix(String name);
}
// 做菜接口_03
interface Cooking_03 {
    // 炒菜
    void fry(String name);
}
// 做凉菜
static class CookingCold implements Cooking_01, Cooking_02 {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void coldMix(String name) {
        System.out.println("凉拌" + name);
    }
}
// 做热菜
static class CookingHot implements Cooking_01, Cooking_03 {
    @Override
    public void wash(String name) {
        System.out.println("洗" + name);
    }
    @Override
    public void cut(String name) {
        System.out.println("切" + name);
    }
    @Override
    public void fry(String name) {
        System.out.println("炒" + name);
    }
}
// 凉拌黄瓜
static class ColdMixCucumber {
    // 洗黄瓜
    public void wash(Cooking_01 cooking) {
        cooking.wash(name);
    }
    // 切黄瓜
    public void cut(Cooking_01 cooking) {
        cooking.cut(name);
    }
    // 凉拌黄瓜
    public void coldMix(Cooking_02 cooking) {
        cooking.coldMix(name);
    }
}
// 炒黄瓜
static class FryCucumber {
    // 洗黄瓜
    public void wash(Cooking_01 cooking) {
        cooking.wash(name);
    }
    // 切黄瓜
    public void cut(Cooking_01 cooking) {
        cooking.cut(name);
    }
    // 炒黄瓜
    public void fry(Cooking_03 cooking) {
        cooking.fry(name);
    }
}

3.2.3 总结

​ 1.提高代码可读性,可重用性,可维护性

​ 3.降低类的复杂度,降低耦合性

3.3 依赖倒置 / 倒转原则(Dependency Inversion Principle,DIP)

3.3.1 解释

​ 1. 依赖倒置/倒转的核心是面向接口编程

​ 2. 相对于细节的多变性,抽象的东西相对稳定得多;以抽象为基础的架构比以细节为基础的架构稳定得多;而在Java中抽象是指抽象类和接口细节是指抽象类和接口的具体实现

​ 3. 抽象类和接口可以理解为制定规范,但不涉及任何具体操作,把展现细节的工作交给他们具体的实现类完成,以此来提高系统的可靠性和可维护性

3.3.2 举例

​ 以付款场景为例

3.3.2.1 Demo1
public static void main(String[] args) {
    Person person = new Person();
    person.payment( new Ali() );
    person.payment( new WeChat() );
    person.payment( new BankCard() );
}
static class Person {
    // 使用支付宝付款
    public void payment(Ali type) {
        System.out.println( type.pay() );
    }
    // 使用微信付款
    public void payment(WeChat type) {
        System.out.println( type.pay() );
    }
    // 使用银行卡付款
    public void payment(BankCard type) {
        System.out.println( type.pay() );
    }
}
static class Ali {
    public String pay() {
        return "通过 -支付宝- 付款";
    }
}
static class WeChat {
    public String pay() {
        return "通过 -微信- 付款";
    }
}
static class BankCard {
    public String pay() {
        return "通过 -银行卡- 付款";
    }
}
3.3.2.2 Demo2

3.3.3 总结

​ 依赖关系传递的三种方式:接口传递、构造方法传递、Setter方法传递

3.3.3.1 接口传递
// 依赖关系传递-通过接口传递
public static void main(String[] args) {
    IComputer computer = new ShineLon();
    ISwitch s = new Switch();
    s.turnOn(computer);
}
// 电脑接口
interface IComputer {
    // 运行
    void play();
}
// 神舟(炫龙)笔记本
static class ShineLon implements IComputer {
    @Override
    public void play() {
        System.out.println("神舟(炫龙)笔记本,正在运行...");
    }
}
// 开关接口
interface ISwitch {
    // 打开【通过接口传递】
    void turnOn(IComputer computer);
}
// 开关实现类
static class Switch implements ISwitch {
    @Override
    public void turnOn(IComputer computer) {
        computer.play();
    }
}
3.3.3.2 构造方法传递
// 依赖关系传递-通过构造方法传递
public static void main(String[] args) {
    IComputer computer = new ShineLon();
    ISwitch s = new Switch(computer);
    s.turnOn();
}
// 电脑接口
interface IComputer {
    // 运行
    void play();
}
// 神舟(炫龙)笔记本
static class ShineLon implements IComputer {
    @Override
    public void play() {
        System.out.println("神舟(炫龙)笔记本,正在运行...");
    }
}
// 开关接口
interface ISwitch {
    // 打开
    void turnOn();
}
// 开关实现类
static class Switch implements ISwitch {
    private final IComputer computer;
    // 【通过构造方法传递】
    Switch(IComputer computer) {
        this.computer = computer;
    }
    @Override
    public void turnOn() {
        this.computer.play();
    }
}
3.3.3.3 Setter方法传递
// 依赖关系传递-通过Setter方法传递
public static void main(String[] args) {
    IComputer computer = new ShineLon();
    Switch s = new Switch();
    s.setComputer(computer);
    s.turnOn();
}
// 电脑接口
interface IComputer {
    // 运行
    void play();
}
// 神舟(炫龙)笔记本
static class ShineLon implements IComputer {
    @Override
    public void play() {
        System.out.println("神舟(炫龙)笔记本,正在运行...");
    }
}
// 开关接口
interface ISwitch {
    // 打开
    void turnOn();
}
// 开关实现类
static class Switch implements ISwitch {
    private IComputer computer;
    @Override
    public void turnOn() {
        this.computer.play();
    }
    // 【通过Setter方法传递】
    public void setComputer(IComputer computer) {
        this.computer = computer;
    }
}

3.4 里式替换原则(Liskov Substitution Principle,LSP)

3.4.1 解释

​ 里式替换原则规范了继承的使用:在子类中尽量不要覆写父类的方法。继承会使两个类的耦合性增强,可以改用组合聚合依赖的方式

3.4.2 举例

3.4.2.1 Demo1
public static void main(String[] args) {
    Parent parent1 = new Parent();
    parent1.add(1, 2);
    parent1.subtract(1, 2);
    Son son = new Son();
    son.add(1, 2);
    son.subtract(1, 2);
}
static class Parent {
    // 加法运算
    public void add(int a, int b) {
        System.out.println("a + b = " + (a + b));
    }
    // 减法运算
    public void subtract(int a, int b) {
        System.out.println("a - b = " + (a - b));
    }
}
static class Son extends Parent {
    // 减法运算
    @Override
    public void subtract(int a, int b) {
        System.out.println("a * b = " + (a * b));
    }
}

​ 以上代码中子类Son覆写了父类Parentsubtract变为乘法运算了,但是对于调用者main来说,这一过程是透明的,这就导致了程序的错误。要解决这个问题,我们可以把有冲突的方法再封装到另一个父类Base中,让SonParent继承这个更基础的父类Base,原先的继承关系去掉,使用依赖聚合组合等关系代替。

3.4.2.2 Demo2
public static void main(String[] args) {
    Parent parent1 = new Parent();
    parent1.add(1, 2);
    parent1.subtract(1, 2);
    Son son = new Son();
    son.add(1, 2);
    // 减法运算:Parent
    son.subtract1(1, 2);
    // 减法运算:Son
    son.subtract2(1, 2);
    son.multiply(1, 2);
}
// 更基础的类
static class Base {
    // 减法运算
    public void subtract(int a, int b) {
        System.out.println("a - b = " + (a - b));
    }
}
static class Parent extends Base {
    // 加法运算
    public void add(int a, int b) {
        System.out.println("a + b = " + (a + b));
    }
}
static class Son extends Base {
    private final Parent parent = new Parent();
    // 减法运算:Parent
    public void subtract1(int a, int b) {
        // 调用Parent类的subtract方法
        parent.subtract(a, b);
    }
    // 减法运算:Son
    public void subtract2(int a, int b) {
        System.out.println("a - b - 1 = " + (a - b - 1));
    }
    // 加法运算
    public void add(int a, int b) {
        // 调用Parent类的add方法
        parent.subtract(a, b);
    }
    // 乘法运算
    public void multiply(int a, int b) {
        System.out.println("a * b = " + (a * b));
    }
}

3.4.3 总结

  1. 克服子类覆写父类方法导致重用性降低的问题
  2. 保证程序的正确性,对类的扩展不会给变动已有系统,降低了代码出错的可能
  3. 加强程序的健壮性,变更的同时也可以兼顾到兼容性
  4. 提高程序的可维护性、可扩展性

3.5 迪米特法则(Law of Demeter,LOD)

3.5.1 解释

​ 迪米特法则又叫最少知识原则(Least Knowledge Principle,LKP),即一个类对自己依赖的类知道的越少越好。比如A类中有个B类的成员变量,那么我们就说A类依赖了B类,对B类来说,不管逻辑有多复杂,都应该尽量将逻辑封装在B类的内部(除了允许对外访问的方法);还有另外一种更为直接的定义方式:只与直接的朋友通信

直接的朋友:出现在类的成员变量方法参数方法返回值中的类为直接的朋友,出现在局部变量中的类不是直接的朋友。也就是说,陌生类尽量不要以局部变量的形式出现在类的内部。

3.5.2 举例

3.5.2.1 Demo1
public static void main(String[] args) {
    School school = new School();
    school.selectAllStudent();
    System.out.println("---------------");
    school.selectAllTeacher();
}
// 学校类
static class School {
    // 查询所有学生
    public void selectAllStudent() {
        //TODO Student以局部变量形式出现,不是直接的朋友
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            students.add( new Student(String.valueOf(i)) );
        }
        for (Student student : students) {
            System.out.println(student);
        }
    }
    // 查询所有老师
    public void selectAllTeacher() {
        //TODO Teacher以局部变量形式出现,不是直接的朋友
        List<Teacher> teachers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            teachers.add( new Teacher(String.valueOf(i)) );
        }
        for (Teacher teacher : teachers) {
            System.out.println(teacher);
        }
    }
}
3.5.2.2 Demo2
public static void main(String[] args) {
    School school = new School();
    school.printAllStudent( school.selectAllStudent() );
    System.out.println("---------------");
    school.printAllTeacher( school.selectAllTeacher() );
}
// 学校类
static class School {
    // 查询所有学生
    public List<Student> selectAllStudent() {
        // Student以方法返回值形式出现,是直接的朋友
        List<Student> students = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            students.add( new Student(String.valueOf(i)) );
        }
        return students;
    }
    // 打印所有学生
    public void printAllStudent(List<Student> students) {
        // Student方法参数形式出现,是直接的朋友
        for (Student student : students) {
            System.out.println(student);
        }
    }
    // 查询所有老师
    public List<Teacher> selectAllTeacher() {
        // Teacher以方法返回值形式出现,是直接的朋友
        List<Teacher> teachers = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            teachers.add( new Teacher(String.valueOf(i)) );
        }
        return teachers;
    }
    // 打印所有老师
    public void printAllTeacher(List<Teacher> teachers) {
        // Teacher方法参数形式出现,是直接的朋友
        for (Teacher teacher : teachers) {
            System.out.println(teacher);
        }
    }
}

​ 我们把Demo1中的StudentTeacher局部变量修改为方法返回值的形式,并且作为printAllStudentprintAllTeacher的方法参数进行后续的打印操作,使得StudentTeacher成为School直接的朋友,降低了类之间耦合性。

3.5.3 总结

​ 迪米特法则核心其实就是降低类之间的耦合

3.6 合成 / 聚合复用原则(Composite / Aggregate Reuse Principle,C / ARP)

3.6.1 解释

​ 合成复用原则是说尽量使用合成 / 聚合的方式,避免使用继承,因为继承相对于合成或者聚合是一种强依赖(如果父类需要修改,那么就需要考虑是否会对其所有的子类产生影响)

3.6.2 举例

​ 我们以B类对A类中的test方法做增强为例,分别以继承组合聚合依赖的方式实现

3.6.2.1 Demo1
// 继承关系
public static void main(String[] args) {
    B b = new B();
    b.test();
}
static class A {
    public void test() {
        System.out.println("A 类的 test 方法...");
    }
}
static class B extends A {
    @Override
    public void test() {
        // A类方法
        super.test();
        // B类对A类方法进行增强
        System.out.println("B 类增强后的 test 方法...");
    }
}
3.6.2.2 Demo2
// 组合关系
public static void main(String[] args) {
    B b = new B();
    b.test();
}
static class A {
    public void test() {
        System.out.println("A 类的 test 方法...");
    }
}
static class B {
    private A a = new A();
    public void test() {
        // A类方法
        a.test();
        // B类对A类方法进行增强
        System.out.println("B 类增强后的 test 方法...");
    }
}
3.6.2.3 Demo3
// 聚合关系
public static void main(String[] args) {
    B b = new B();
    b.setA( new A() );
    b.test();
}
static class A {
    public void test() {
        System.out.println("A 类的 test 方法...");
    }
}
static class B {
    private A a;
    public void test() {
        // A类方法
        a.test();
        // B类对A类方法进行增强
        System.out.println("B 类增强后的 test 方法...");
    }
    public void setA(A a) {
        this.a = a;
    }
}
3.6.2.4 Demo4
// 依赖关系
public static void main(String[] args) {
    B b = new B();
    b.test( new A() );
}
static class A {
    public void test() {
        System.out.println("A 类的 test 方法...");
    }
}
static class B {
    public void test(A a) {
        // A类方法
        a.test();
        // B类对A类方法进行增强
        System.out.println("B 类增强后的 test 方法...");
    }
}

3.6.3 总结

  1. 维持类的封装性
  2. 降低类之间耦合度
  3. 提高可复用性

3.7 开闭原则(Open Closed Principle,OCP)

3.7.1 解释

​ 把开闭原则放到最后讲,是因为开闭原则是七大原则中最基础,也是最重要的设计原则,其他六种设计原则均是为了实现开闭原则。

​ 一个软件实体(类,模块,函数)应该保证:对扩展开放(提供方),对修改关闭(使用方),用抽象构建框架,用实现扩展细节。当软件需要变化时,尽量通过扩展软件实体的方式来实现变化,而不是通过修改已有的代码来实现变化。

3.7.2 举例

3.7.2.1 Demo1
public static void main(String[] args) {
    PaintBrush brush = new PaintBrush();
    brush.draw( new Triangle() );
    brush.draw( new Rectangle() );
}
static class PaintBrush {
    // 使用方(对修改关闭)
    public void draw(Shape shape) {
        switch (shape.type) {
            case 1:
                drawTriangle();
                break;
            case 2:
                drawRectangle();
                break;
                //TODO 扩展分支
        }
    }
    private void drawTriangle() {
        System.out.println("画三角形");
    }
    private void drawRectangle() {
        System.out.println("画长方形");
    }
    //TODO 扩展方法
}
// 提供方(对扩展开放)
static class Shape {
    public int type;
}
static class Triangle extends Shape {
    public Triangle() {
        super.type = 1;
    }
}
static class Rectangle extends Shape {
    public Rectangle() {
        super.type = 2;
    }
}
//TODO 扩展类

​ 以上代码为例,如果我们想要再新加一个类型,就需要在注释//TODO的地方做扩展,PaintBrush作为使用方也需要做修改,违反了开闭原则

3.7.2.2 Demo2
public static void main(String[] args) {
    PaintBrush brush = new PaintBrush();
    brush.draw( new Triangle() );
    brush.draw( new Rectangle() );
}
static class PaintBrush {
    // 使用方(对修改关闭)
    public void draw(Shape shape) {
        shape.draw();
    }
}
// 提供方(对扩展开放)
static abstract class Shape {
    // 抽象方法,由子类实现细节
    public abstract void draw();
}
static class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("画三角形");
    }
}
static class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("画长方形");
    }
}
//TODO 扩展类

​ 代码根据开闭原则优化后,在新增类型时,只需要在提供方Shape新增子类即可,使用方PaintBrush代码无需修改

3.7.3 总结

  1. 对软件测试来说,如果遵守开闭原则,那么只需要测试扩展的部分代码,原有代码无需测试
  2. 提高代码可维护性、可复用性

4、设计模式的工具

​ 提起设计模式,很多人感觉无从下手,因为类与类、类与接口之间的继承、实现等关系特别复杂,看着看着就被绕进去了,我们借助一种具象化的工具:UML图,来帮助我们理解复杂的关系

统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言,是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具,独立于任何具体程序设计语言。

​ ---- 百度百科

​ UML图本质就是用一系列的符号来表示软件模型中各个元素及其之间的关系(比如Java中的类,接口,依赖,实现,泛化,组合,聚合等元素或关系),UML图类型众多,这里只介绍UML类图

4.1 UML类图表示方法

​ 下面使用Java代码UML类图对照的方式展示

4.1.1 具体类

// 人
public class Person {
    // 性别
    public String sex;
    // 生日
    protected String birth;
    // 工作
    String work;
    // 年龄
    private short age;
    public String getSex() {
        return sex;
    }
    protected String getBirth() {
        return birth;
    }
    String getWork() {
        return work;
    }
    private short getAge() {
        return age;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    protected void setBirth(String birth) {
        this.birth = birth;
    }
    void setWork(String work) {
        this.work = work;
    }
    private void setAge(short age) {
        this.age = age;
    }
}

​ 具体类在UML类图中用矩形表示,矩形的第一层是类名第二层是成员变量第三层是成员方法,成员变量和成员方法前的访问修饰符都是由符号表示

  1. “+” 表示 “public”
  2. “#” 表示 “protected”
  3. “-” 表示 “private”
  4. 不加符号 表示 “default”

​ 方法参数表示为:

method(变量名1:变量类型1, 变量名2:变量类型2, ... , 变量名n:变量类型n):返回值类型

4.1.2 抽象类

// 接收方
public abstract class Receiver {
    // 消息
    private String message;
    // 获取接收类型
    protected abstract String getType();
    public String getMessage() {
        return message;
    }
}

​ 抽象类与具体类基本相同,只是矩形第一层的抽象类名是斜体的,成员方法中的抽象方法也是斜体的

4.1.3 接口

// 形状
public interface Shape {
    // 获取尺寸
    int getSize();
}

​ 接口在矩形的第一层中第一行为 <<Interface>>*做接口标识第二行为接口名

4.2 UML类图表示关系

4.2.1 依赖关系(Dependency)

​ 简单来说,只要类中用到了另一个类,他们之间就存在了依赖关系(UML类图中依赖关系用带虚线的箭头表示)

public class UserService {
    // 成员变量
    private UserDao userDao;
    // 方法参数
    public void insert(User user) {}
    // 方法返回值
    public Role getRole(Long id) {
        return null;
    }
    // 方法局部变量
    public void update() {
        Dept dept = new Dept();
    }
}

类中用到了另一个类包括以上几种情况:

  1. 类的成员变量
  2. 类中方法的参数
  3. 类中方法的返回值
  4. 类的方法中使用到(局部变量)

4.2.2 泛化关系(Generalization)

​ 泛化关系实际上就是继承关系,是依赖关系的特例(在UML类图中用带空心三角的实线表示)

public class User extends Person {}

4.2.3 实现关系(Realization / Implementation)

​ 实现关系是指类A实现了接口B,是依赖关系的特例(在UML类图中用带空心三角的虚线表示)

public class UserServiceImpl implements IUserService{}

4.2.4 关联关系(Association)

​ 关联关系是类与类之间存在的联系,是依赖关系的特例(在UML类图中用带双箭头的实线或者不带箭头的双实线表示双向关联,用带单箭头的实线表示单向关联

​ 关联具有导航性单向关联双向关联

​ 关联具有多重性一对一多对一多对多

  1. 数字:精确的数量
  2. *或者0..*:表示0到多个
  3. 0..1:表示0或者1个
  4. 1..*:表示1到多个

// 单向一对一关联
public class Association_01 {
    // 人
    static class Person {
        private IDCard idCard;
    }
    // 身份证
    static class IDCard {
    }
}

// 双向一对一关联
public class Association_02 {
    // 人
    static class Person {
        private IDCard idCard;
    }
    // 身份证
    static class IDCard {
        private Person person;
    }
}

// 单向多对一关联
public class Association_03 {
    // 人
    static class Person {
        private List<BankCard> bankCardList;
    }
    // 银行卡
    static class BankCard {
    }
}

// 双向多对一关联
public class Association_04 {
    // 人
    static class Person {
        private List<BankCard> bankCardList;
    }
    // 银行卡
    static class BankCard {
        private Person person;
    }
}

// 多对多关联
public class Association_05 {
    // 用户
    static class User {
        private List<Role> roleList;
    }
    // 角色
    static class Role {
        private List<User> userList;
    }
}

4.2.5 聚合关系(Aggregation)

​ 聚合关系是指整体与部分的关系,整体和部分可以分开(比如电脑和鼠标,鼠标是电脑的一部分,可以分开),是关联关系的特例,所以同样具有导航性多重性(在UML类图中用空心菱形加实线箭头表示空心菱形在整体一方箭头指向部分一方,表示把部分聚合到整体中来)

// 聚合关系
public class Aggregation_01 {
    // 人
    static class Person {
        private IDCard idCard;
    }
    // 身份证
    static class IDCard {
    }
}

4.2.6 组合关系(Composition)

​ 组合关系是指整体与部分的关系,整体和部分不可以分开(比如人的身体和头,头是身体的一部分,不可以分开),是关联关系的特例,所以同样具有导航性多重性(在UML类图中用实心菱形加实线箭头表示,实心菱形在整体一方,箭头指向部分一方,表示把部分组合到整体中来)

// 组合关系
public class Composition_01 {
    // 人
    static class Person {
        private IDCard idCard = new IDCard();
    }
    // 身份证
    static class IDCard {
    }
}

4.3 UML类图画图软件

​ 我使用的是draw.io(在线版本、PC版都有),支持多种语言

​ 下载链接:https://github.com/jgraph/drawio-desktop/releases

​ 主界面如下:

5、设计模式的类型

​ 以下罗列了常见的23中设计模式:

创建型模式:

  1. 单例模式
  2. 工厂模式
  3. 抽象工厂模式
  4. 原型模式
  5. 建造者模式

结构型模式:

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

行为型模式:

  1. 模板方法模式
  2. 命令模式
  3. 访问者模式
  4. 迭代器模式
  5. 观察者模式
  6. 中介者模式
  7. 备忘录模式
  8. 解释器模式(Interpreter模式)
  9. 状态模式
  10. 策略模式
  11. 职责链模式(责任链模式)

6、相关源码

​ 本篇章完整代码:https://github.com/yushixin-1024/DesignPattern

PS:代码不涉及具体业务逻辑,仅仅是为了举例,方便理解。

有关设计模式之概述篇的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  6. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  7. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  8. 阿里云RDS——产品系列概述 - 2

    基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

  9. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

  10. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

随机推荐