? 面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
? 提高代码可读性、重用性、可靠性、可扩展性,实现“高内聚,低耦合”。
? 常见的设计模式有23种,但是发展到今天还有很多叫不上名字来的设计模式,无一例外都遵循着“软件设计七大原则”。
? 单一职责就是一个类或者一个方法只负责一项职责。
? 假设有一个IT部门,一个开发,一个测试,一个运维。我们把三个人的工作抽象为一个类
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实现了三种不同职责,违背了单一职责原则
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也做了修改,这样改动太大
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中改动量也很小
? 1. 单一职责可以细化为类级别和方法级别,最低限度是在方法级别保证单一职责原则;但是如果一个类中有几十个方法,那么就需要衡量下是否需要进行类分解
? 2. 提高代码可读性,可维护性,可扩展性
? 3. 降低类的复杂度
? 一个类对另一个类的依赖应该建立在最小的接口上,即一个类不应该依赖它不需要的接口
? 就拿凉拌黄瓜和炒黄瓜的步骤来举例
凉拌黄瓜:洗菜 -> 切菜 -> 凉拌
炒 黄 瓜:洗菜 -> 切菜 -> 炒菜
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接口的设计不合理,不符合接口隔离原则

? 我们注意到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);;}// 做菜接口_01interface Cooking_01 { // 洗菜 void wash(String name); // 切菜 void cut(String name);}// 做菜接口_02interface Cooking_02 { // 凉拌 void coldMix(String name);}// 做菜接口_03interface 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); }}? 1.提高代码可读性,可重用性,可维护性
? 3.降低类的复杂度,降低耦合性
? 1. 依赖倒置/倒转的核心是面向接口编程
? 2. 相对于细节的多变性,抽象的东西相对稳定得多;以抽象为基础的架构比以细节为基础的架构稳定得多;而在Java中抽象是指抽象类和接口,细节是指抽象类和接口的具体实现
? 3. 抽象类和接口可以理解为制定规范,但不涉及任何具体操作,把展现细节的工作交给他们具体的实现类完成,以此来提高系统的可靠性和可维护性
? 以付款场景为例

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 "通过 -银行卡- 付款"; }}
? 依赖关系传递的三种方式:接口传递、构造方法传递、Setter方法传递
// 依赖关系传递-通过接口传递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(); }}// 依赖关系传递-通过构造方法传递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(); }}// 依赖关系传递-通过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; }}? 里式替换原则规范了继承的使用:在子类中尽量不要覆写父类的方法。继承会使两个类的耦合性增强,可以改用组合,聚合,依赖的方式

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,原先的继承关系去掉,使用依赖,聚合,组合等关系代替。

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)); }}? 迪米特法则又叫最少知识原则(Least Knowledge Principle,LKP),即一个类对自己依赖的类知道的越少越好。比如A类中有个B类的成员变量,那么我们就说A类依赖了B类,对B类来说,不管逻辑有多复杂,都应该尽量将逻辑封装在B类的内部(除了允许对外访问的方法);还有另外一种更为直接的定义方式:只与直接的朋友通信。
? 直接的朋友:出现在类的成员变量,方法参数,方法返回值中的类为直接的朋友,出现在局部变量中的类不是直接的朋友。也就是说,陌生类尽量不要以局部变量的形式出现在类的内部。
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); } }}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的直接的朋友,降低了类之间耦合性。
? 迪米特法则核心其实就是降低类之间的耦合
? 合成复用原则是说尽量使用合成 / 聚合的方式,避免使用继承,因为继承相对于合成或者聚合是一种强依赖(如果父类需要修改,那么就需要考虑是否会对其所有的子类产生影响)
? 我们以B类对A类中的test方法做增强为例,分别以继承、组合、聚合、依赖的方式实现

// 继承关系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 方法..."); }}
// 组合关系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 方法..."); }}
// 聚合关系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; }}
// 依赖关系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 方法..."); }}? 把开闭原则放到最后讲,是因为开闭原则是七大原则中最基础,也是最重要的设计原则,其他六种设计原则均是为了实现开闭原则。
? 一个软件实体(类,模块,函数)应该保证:对扩展开放(提供方),对修改关闭(使用方),用抽象构建框架,用实现扩展细节。当软件需要变化时,尽量通过扩展软件实体的方式来实现变化,而不是通过修改已有的代码来实现变化。
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作为使用方也需要做修改,违反了开闭原则
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代码无需修改
? 提起设计模式,很多人感觉无从下手,因为类与类、类与接口之间的继承、实现等关系特别复杂,看着看着就被绕进去了,我们借助一种具象化的工具:UML图,来帮助我们理解复杂的关系
统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言,是非专利的第三代建模和规约语言。UML是面向对象设计的建模工具,独立于任何具体程序设计语言。
? ---- 百度百科
? UML图本质就是用一系列的符号来表示软件模型中各个元素及其之间的关系(比如Java中的类,接口,依赖,实现,泛化,组合,聚合等元素或关系),UML图类型众多,这里只介绍UML类图:
? 下面使用Java代码与UML类图对照的方式展示
// 人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类图中用矩形表示,矩形的第一层是类名,第二层是成员变量,第三层是成员方法,成员变量和成员方法前的访问修饰符都是由符号表示:
? 方法参数表示为:
? method(变量名1:变量类型1, 变量名2:变量类型2, ... , 变量名n:变量类型n):返回值类型

// 接收方public abstract class Receiver { // 消息 private String message; // 获取接收类型 protected abstract String getType(); public String getMessage() { return message; }}? 抽象类与具体类基本相同,只是矩形第一层的抽象类名是斜体的,成员方法中的抽象方法也是斜体的

// 形状public interface Shape { // 获取尺寸 int getSize();}? 接口在矩形的第一层中第一行为 <<Interface>>*做接口标识,第二行为接口名

? 简单来说,只要类中用到了另一个类,他们之间就存在了依赖关系(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(); }}? 类中用到了另一个类包括以上几种情况:
? 泛化关系实际上就是继承关系,是依赖关系的特例(在UML类图中用带空心三角的实线表示)

public class User extends Person {}? 实现关系是指类A实现了接口B,是依赖关系的特例(在UML类图中用带空心三角的虚线表示)

public class UserServiceImpl implements IUserService{}? 关联关系是类与类之间存在的联系,是依赖关系的特例(在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; }}? 聚合关系是指整体与部分的关系,整体和部分可以分开(比如电脑和鼠标,鼠标是电脑的一部分,可以分开),是关联关系的特例,所以同样具有导航性和多重性(在UML类图中用空心菱形加实线箭头表示,空心菱形在整体一方,箭头指向部分一方,表示把部分聚合到整体中来)

// 聚合关系public class Aggregation_01 { // 人 static class Person { private IDCard idCard; } // 身份证 static class IDCard { }}? 组合关系是指整体与部分的关系,整体和部分不可以分开(比如人的身体和头,头是身体的一部分,不可以分开),是关联关系的特例,所以同样具有导航性和多重性(在UML类图中用实心菱形加实线箭头表示,实心菱形在整体一方,箭头指向部分一方,表示把部分组合到整体中来)

// 组合关系public class Composition_01 { // 人 static class Person { private IDCard idCard = new IDCard(); } // 身份证 static class IDCard { }}? 我使用的是draw.io(在线版本、PC版都有),支持多种语言
? 下载链接:https://github.com/jgraph/drawio-desktop/releases
? 主界面如下:

? 此处插眼,具体的设计模式后续按篇章更新,敬请期待 . . .
? 本篇章完整代码及后续代码均托管在GitHub上:https://github.com/yushixin-1024/DesignPattern
? PS:代码不涉及具体业务逻辑,仅仅是为了举例,方便理解。
本文来自博客园,作者:飒沓流星,转载请注明原文链接:https://www.cnblogs.com/yushixin1024/p/16426603.html