1、设计模式的本质
面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
2、设计模式的目的
提高代码可读性、重用性、可靠性、可扩展性,实现“高内聚,低耦合”。
名词解释
- 可读性:按照规范编程,便于其他程序员阅读和理解
- 重用性:相同功能的代码,可以重复使用,无需多次编写
- 可靠性:增加功能时,对原有功能没有影响
- 可扩展性:增加功能时方便,可维护性强
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
分解为Developer
,Tester
,Operator
,并且调用方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 举例
就拿凉拌黄瓜和炒黄瓜的步骤来举例
凉拌黄瓜:洗菜 -> 切菜 -> 凉拌
炒 黄 瓜:洗菜 -> 切菜 -> 炒菜
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类图,据此分析CookingCold
和CookingHot
均实现了Cooking
接口并实现其所有方法,但在ColdMixCucumber
和FryCucumber
中仅使用了其中的三个方法;虽然运行结果没有问题,但是明显Cooking
接口的设计不合理,不符合接口隔离原则
3.2.2.2 Demo2
我们注意到Cooking
接口中,wash
和cut
是都会用到的,而coldMix
和fry
并不会全部用到;根据接口隔离原则,我们把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
覆写了父类Parent
的subtract
变为乘法运算了,但是对于调用者main
来说,这一过程是透明的,这就导致了程序的错误。要解决这个问题,我们可以把有冲突的方法再封装到另一个父类Base
中,让Son
和Parent
继承这个更基础的父类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 总结
- 克服子类覆写父类方法导致重用性降低的问题
- 保证程序的正确性,对类的扩展不会给变动已有系统,降低了代码出错的可能
- 加强程序的健壮性,变更的同时也可以兼顾到兼容性
- 提高程序的可维护性、可扩展性
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
中的Student
,Teacher
局部变量修改为方法返回值的形式,并且作为printAllStudent
,printAllTeacher
的方法参数进行后续的打印操作,使得Student
,Teacher
成为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 总结
- 维持类的封装性
- 降低类之间耦合度
- 提高可复用性
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 总结
- 对软件测试来说,如果遵守开闭原则,那么只需要测试扩展的部分代码,原有代码无需测试
- 提高代码可维护性、可复用性
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类图中用矩形表示,矩形的第一层是类名,第二层是成员变量,第三层是成员方法,成员变量和成员方法前的访问修饰符都是由符号表示:
- “+” 表示 “public”
- “#” 表示 “protected”
- “-” 表示 “private”
- 不加符号 表示 “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(); } }
类中用到了另一个类包括以上几种情况:
- 类的成员变量
- 类中方法的参数
- 类中方法的返回值
- 类的方法中使用到(局部变量)
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类图中用带双箭头的实线或者不带箭头的双实线表示双向关联,用带单箭头的实线表示单向关联)
关联具有导航性:单向关联 和 双向关联
关联具有多重性:一对一,多对一,多对多
- 数字:精确的数量
*
或者0..*
:表示0到多个0..1
:表示0或者1个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中设计模式:
创建型模式:
- 单例模式
- 工厂模式
- 抽象工厂模式
- 原型模式
- 建造者模式
结构型模式:
- 适配器模式
- 桥接模式
- 装饰模式
- 组合模式
- 外观模式
- 享元模式
- 代理模式
行为型模式:
- 模板方法模式
- 命令模式
- 访问者模式
- 迭代器模式
- 观察者模式
- 中介者模式
- 备忘录模式
- 解释器模式(Interpreter模式)
- 状态模式
- 策略模式
- 职责链模式(责任链模式)
6、相关源码
本篇章完整代码:https://github.com/yushixin-1024/DesignPattern
PS:代码不涉及具体业务逻辑,仅仅是为了举例,方便理解。