# DesignPatterns **Repository Path**: han-fei1375/design-patterns ## Basic Information - **Project Name**: DesignPatterns - **Description**: 设计模式学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-11-18 - **Last Updated**: 2024-03-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README > * 参考视频:[001_尚硅谷_设计模式面试题(1)_哔哩哔哩_bilibili](https://www.bilibili.com/video/BV1G4411c7N4?p=1&vd_source=0febe7936d183ed5cc2f12c7474dcc58) # 设计模式 [toc] ## 一、设计模式内容介绍 ### 1.经典面试题 > * **原型设计模式问题** > 1. 有请使用UML类图画出原型模式核心角色 > 2. 原型设计模式的深拷贝和浅拷贝是什么,并写出深拷贝的两种方式的源码(重写clone方法实现深拷贝、使用序列化来实现深拷贝) > 3. 在Spring框架中哪里使用到原型设计模式,并对源码进行分析 ````xml ```` > * **设计模式的七大原则是什么** > 1. 七大原则核心思想 > 2. 能够以类图来说明设计原则 > 3. 在项目实际开发中,在哪里使用到了ocp原则 > * **金融借贷平台项目** > * 借贷平台的订单,有 审核-发布-抢单 等等步骤,随着操作的不同,会改变订单的状态,项目中的这个模式实现就会使用到状态模式,请你使用状态模式进行设计,并完成实际代码 > * **问题分析** > * 这类代码难以应对变化,在添加一种状态时,我们需要手动添加 if/else,在添加一种功能时,需要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的BUG,难以维护。 > * **解释器设计模式** > 1. 介绍解释器设计模式是什么 > 2. 画出解释器设计模式的UML类图,分析设计模式中的各个角色是什么 > 3. 请说明Spring的框架中,哪里使用到了解释器设计模式,并做源码级别的分析 ### 2.何时使用设计模式 > * **设计模式的重要性** > 1. 软件工程中,**设计模式**(design pattern)是对**软件设计中普遍存在(反复出现)**的各种问题,所提出的**解决方案**。设个术语是由埃里希·伽玛等人在1990年代冲动建筑设计领域引入到计算机科学的。 > 2. 大厦 vs 简易房 image-20231118233441431 > * 承上⬆️ > 3. 拿实际工作经历来说,当一个项目开发完后,如果客户提出新增功能,怎么办? > 4. 如果项目开发完成后,原来程序员离职,你接手维护该项目怎么办?(**维护性|可读性|规范性**) > 5. 目前程序员门槛越来越高,一线IT公司(大厂),都会问你在实际项目中使用过什么设计模式,怎样使用的,解决了什么问题? > 6. 设计模式在软件中哪里?**面向对象(oo)=> 功能模块[设计模式+算法(数据结构)] => 框架[使用到多种设计模式] => 架构[服务器集群]** > 7. 如果想成为合格软件工程师,那就花时间来研究下设计模式是非常必要的。 ### 3.内容和授课方式 > 1. 课程深入,非蜻蜓点水 > 2. 课程成体系,非星星点灯 > 3. 高效而愉快的学习,设计模式很有用,其实也很好玩,很像小时候搭积木,怎么样搭建更加稳定,坚固 > 4. 设计模式很重要,因为包含许多编程思想,还是有一定难度的,我们努力做到通俗易懂 > 5. 采用 应用场景->设计模式->剖析原理->分析实现步骤(图解)->代码实现->框架或项目源码分析(找到使用的地方)的步骤讲解【比如:建造者模式】 > 6. 课程目标:让大家掌握本质,能在工作中灵活运用解决实际问题和优化程序结构的目的 ## 二、七大设计原则 > * **设计模式的目的** > * 编写软件过程中,程序员面临着来自 耦合性、内聚性以及可维护性、可扩展性、重用性、灵活性等多方面的挑战,设计模式是为了让**程序(软件)**,具有更好 > 1. 代码重用性(即:相同功能的代码,不用多次编写) > 2. 可读性(即:编程规范性,便于其他程序员的阅读和理解) > 3. 可扩展性(即:当需要增加新的功能时,非常的方便,称为可维护性) > 4. 可靠性(当我们增加新的功能后,对原来的功能没有影响) > 5. 使程序呈现高内聚,低耦合的特性 > * 设计模式包含了面向对象的精髓。“懂了设计模式,你就懂了面向对象分析和设计(OOA/D)的精要” > * Scott Mayers在其巨著《Effective C++》就曾经说过,C++老手和C++新手的区别就是前者手背上有很多伤疤 > * **设计模式的七大原则** > * 设计模式原则,其实就是程序员在编程时,应当遵守的原则,也是各种设计模式的基础(即:设计模式为社么这样设计的依据) > 1. 单一职责原则 > 2. 接口隔离原则 > 3. 依赖倒转(倒置)原则 > 4. 里氏替换原则 > 5. 开闭原则 > 6. 迪米特法则 > 7. **合成复用原则** ### 1.单一职责原则 #### 1.1 基本介绍 > * 对类来说的,即一个类应该只负责一项职责,如类A负责两个不同职责:职责1,职责2. > * 当职责1需求变更而改变A时,可能会造成职责2执行错误,所以需要将类A的粒度分解为A1、A2 #### 1.2 应用实例 > 1. 以交通工具案例讲解 > 2. 看老师代码演示 ##### 方案1【分析说明】 ```java /** * 单一职责原则-方案1 * * @author hanfei * @date 2023/11/19 */ public class SingleResponsibility01 { public static void main(String[] args) { Vehicle vehicle = new Vehicle(); vehicle.run("摩托车"); vehicle.run("汽车"); vehicle.run("飞机"); } } //交通工具类 //方式1 // 1.在方式1 的run方法中,违反了单一职责原则 // 2.解决的方案非常的简单,根据建通工具的不同,分解成不同的类 class Vehicle { public void run(String vehicle) { System.out.println(vehicle + "在公路上运行...."); } } ``` ##### 方案2【分析说明】 ```java /** * 单一职责原则-方案2 * * @author hanfei * @date 2023/11/19 */ public class SingleResponsibility02 { public static void main(String[] args) { RoadVehicle roadVehicle = new RoadVehicle(); roadVehicle.run("摩托车"); roadVehicle.run("汽车"); AirVehicle airVehicle = new AirVehicle(); airVehicle.run("飞机"); } } //方案2的分析 //1.遵守单一职责原则 //2.但是这样做的改动很大,即将类分解,同时修改客户端 //3.改进:直接修改Vehicle类,改动的代码会比较少 => 方案3 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 + "水中运行"); } } ``` ##### 方案3【分析说明】 ```java /** * 单一职责原则-方案3 * * @author hanfei * @date 2023/11/19 */ public class SingleResponsibility03 { public static void main(String[] args) { Vehicle2 vehicle2 = new Vehicle2(); vehicle2.runRoad("汽车"); vehicle2.runWater("轮船"); vehicle2.runAir("飞机"); } } //方式3 //1.这种修改方法没有对原来的类做大的修改,只是增加方法 //2.这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责原则 class Vehicle2 { public void runRoad(String vehicle) { System.out.println(vehicle + "公路运行"); } public void runAir(String vehicle) { System.out.println(vehicle + "空中运行"); } public void runWater(String vehicle) { System.out.println(vehicle + "水中运行"); } } ``` #### 1.3 注意事项和细节 > 1. 降低类的复杂度,一个类只负责一项职责 > 2. 提高类的可读性,可维护性 > 3. 降低变更引起的风险 > 4. 通常情况下,**我们应当遵守单一职责原则**,只有逻辑足够简单,才可以在代码级违反单一职责原则: > * 只有类中方法数量足够少,可以在方法级别保持单一职责原则 ### 2.接口隔离原则 > * 英译:**Interface Segregation Principle** #### 2.1 基本介绍 > 1. 客户端不应该依赖它不需要的接口,即一个类对另一类的依赖应该建立在最小的接口上 > 2. 先看一张图: > * 类A通过接口Interface1依赖B,类C通过接口Interface1依赖D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现他们不需要的方法 > * 按隔离原则应当这样处理:将接口Interface1拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。 ![image-20231120222844599](./assets/image-20231120222844599.png) #### 2.2 应用实例 > 1. 类A通过接口interface1依赖B > > 类C通过接口interface1依赖D > > 请编写代码完成此应用实例 > 2. 请看代码,没有使用接口隔离原则 ```java package segregation; public class Segregation1 { public static void main(String[] args) { } } //接口 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类,但是只会用到1,2,3方法 public void depend1(Interface1 interface1) { interface1.operation1(); } public void depend2(Interface1 interface1) { interface1.operation2(); } public void depend3(Interface1 interface1) { interface1.operation3(); } } class C { //C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法 public void depend1(Interface1 interface1) { interface1.operation1(); } public void depend4(Interface1 interface1) { interface1.operation4(); } public void depend5(Interface1 interface1) { interface1.operation5(); } } ``` #### 2.3 应传统方法的问题和使用接口隔离原则改进 > 1. 类A通过接口Interface1依赖B, > > 类C通过接口Interface1依赖D, > > 如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现不需要的方法 > 2. 将接口Interface1拆分为独立的几个接口,类A和类C分别与它们需要的接口建立依赖关系。也就是采用接口隔离原则 > 3. 接口Interface1中实现的方法,根据实际情况拆分为三个接口 > 4. 代码实现 ![image-20231122214427005](./assets/image-20231122214427005.png) ```java package segregation.improve; public class Segregation1 { public static void main(String[] args) { A a = new A(); a.depend1(new B()); a.depend2(new B()); a.depend2(new B()); C c = new C(); c.depend1(new 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类,但是只会用到1,2,3方法 public void depend1(Interface1 interface1) { interface1.operation1(); } public void depend2(Interface2 interface2) { interface2.operation2(); } public void depend3(Interface2 interface2) { interface2.operation3(); } } class C { //C类通过接口Interface1依赖(使用)D类,但是只会用到1,4,5方法 public void depend1(Interface1 interface1) { interface1.operation1(); } public void depend4(Interface3 interface3) { interface3.operation4(); } public void depend5(Interface3 interface3) { interface3.operation5(); } } ``` ### 3.依赖倒转原则 > * 英译:**Dependence Inversion Principal** #### 3.1 基本介绍 > * 依赖倒转原则是指: > 1. 高层模块不应该依赖底层模块,二者都应该依赖其抽象 > 2. **抽象不应该依赖细节,细节应该依赖抽象** > 3. 依赖倒转(倒置)的中心思想是**面向接口编程** > 4. 依赖倒转原则是基于这样的设计理念,相对于细节的多变性,抽象的东西要稳定的多,以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象指的是接口或抽象类,细节就是具体的实现类 > 5. 使用**接口或抽象类**的目的是**制定好规范**,而不涉及任何 体的操作,把**展现细节的任务交给他们的实现类去完成** #### 3.2 应用实例 > 1. 请编程完成Person发送消息的功能 ##### 方案1 ```java package inversion; /** * 依赖倒转 * * @author hanfei * @date 2023/11/27 */ 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"; } } //完成Person接收消息的功能 //方式1分析 //1.简单,比较容易想到 //2.如果我们获取的对象是微信,短信等等,则新增类,同时Person也要增加相应的接收方法 //3.解决思路:引入一个抽象的接口IReceiver,表示接收者,这样Person类与接口IReceiver发生依赖 // 因为Email,WeiXin等等属于接收的范围,他们各自实现IReceiver接口就OK,这样我们就符合依赖倒转原则 //方式1 class Person { public void receive(Email email) { System.out.println(email.getInfo()); } } ``` ##### 方案2 ```java package inversion.improve; /** * 依赖倒转优化 * * @author hanfei * @date 2023/11/27 */ public class DependencyInversion { public static void main(String[] args) { //客户端不需要改变 Person person = new Person(); person.receive(new Email()); person.receive(new WeiXin()); } } //定义接口 interface IReceiver { public String getInfo(); } //电子邮件 class Email implements IReceiver { public String getInfo() { return "电子邮件信息:hello world"; } } class WeiXin implements IReceiver { public String getInfo() { return "微信信息:hello ok"; } } //方式2 class Person { //这里是对接口的依赖 public void receive(IReceiver iReceiver) { System.out.println(iReceiver.getInfo()); } } ``` #### 3.3 依赖关系传递的三种方式 ##### 接口传递 ```java package inversion.threeway.first; /** * 依赖关系传递的三种方式-接口传递 * * @author hanfei * @date 2023/11/27 */ public class DependencyInversion { public static void main(String[] args) { ChangHong changHong = new ChangHong(); OpenAndClose openAndClose = new OpenAndClose(); openAndClose.open(changHong); } } //方式1:通过接口传递实现依赖 interface IOpenAndClose { public void open(ITV itv); } interface ITV { //ITV接口 public void open(); } class ChangHong implements ITV { @Override public void open() { System.out.println("长虹电视机打开了!"); } } //实现接口 class OpenAndClose implements IOpenAndClose { @Override public void open(ITV itv) { itv.open(); } } ``` ##### 构造方法传递 ```java package inversion.threeway.second; /** * 依赖关系传递的三种方式-构造方法传递 * * @author hanfei * @date 2023/11/27 */ public class DependencyInversion { public static void main(String[] args) { //通过构造器实现依赖的传递 OpenAndClose openAndClose = new OpenAndClose(new ChangHong()); openAndClose.open(); } } interface IOpenAndClose { public void open(); //抽象方法 } interface ITV { public void open(); //ITV接口 } class ChangHong implements ITV { @Override public void open() { System.out.println("长虹电视机打开了!"); } } class OpenAndClose implements IOpenAndClose { private ITV tv; //成员 public OpenAndClose(ITV tv) { this.tv = tv; } @Override public void open() { tv.open(); } } ``` ##### setter方式传递 ```java package inversion.threeway.third; public class DependencyInversion { public static void main(String[] args) { OpenAndClose openAndClose = new OpenAndClose(); openAndClose.setTv(new ChangHong()); openAndClose.open(); } } interface IOpenAndClose { public void open(); public void setTv(ITV itv); } interface ITV { public void open(); } class ChangHong implements ITV { @Override public void open() { System.out.println("长虹电视机打开了!"); } } class OpenAndClose implements IOpenAndClose { private ITV itv; @Override public void open() { this.itv.open(); } @Override public void setTv(ITV itv) { this.itv = itv; } } ``` #### 3.4 注意事项和细节 > 1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好 > 2. 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化 > 3. 继承时遵循里氏替换原则 ### 4.里氏替换原则 #### 4.1 OO中的继承性的思考和说明 > 1. 继承包含这样一层含义:父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有的子类必须遵循这些契约,但是如果子类对这些已经实现的方法任意修改,就会对整个继承体系造成破坏 > 2. 继承在给程序设计带来便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低。增加对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能产生故障。 > 3. 问题提出:在编程中,如何正确的使用继承?=> **里氏替换原则** #### 4.2 基本介绍 > 1. 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的一位姓里的女士**提出**的 > 2. 如果对每个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都代换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说,**所有引用基类的地方必须能透明地使用其子类的对象**。 > 3. `在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法` > 4. 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,**在适当的情况下,可以通过聚合,组合,依赖来解决问题** #### 4.3 一个程序引出的问题和思考 ```java package liskov; public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.fun1(11, 3)); System.out.println("1-8=" + a.fun1(1, 8)); B b = new B(); System.out.println("11-3=" + b.fun2(11, 3)); System.out.println("1-8=" + b.fun2(1, 8)); System.out.println("11+3+9=" + b.fun2(11, 3)); } } //A类 class A { //返回两个数的差 public int fun1(int num1, int num2) { return num1 - num2; } } //B类继承了A类 //增加一个新功能:完成两个数相加,然后和9求和 class B extends A { //这里,重写了A类似的方法,可能是无意识 @Override public int fun1(int a, int b) { return a + b; } public int fun2(int a, int b) { return fun1(a, b) + 9; } } ``` #### 4.4 解决方法 > 1. 我们发现原来运行正常的相减功能发生了错误。原因就是类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。 > 2. 通用的做法是:原来的父类和子类都继承一个更通俗的基类,**原有的继承关系去掉,采用依赖、聚合、组合等关系代替**。 image-20231128221435731 ```java package liskov.improve; public class Liskov { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.fun1(11, 3)); System.out.println("1-8=" + a.fun1(1, 8)); B b = new B(); //因为B类不再继承A类,因此调用者,不会在fun1使用减法, //调用完成的功能就会很明确 System.out.println("11+3=" + b.fun1(11, 3)); System.out.println("1+8=" + b.fun1(1, 8)); System.out.println("11+3+9=" + b.fun2(11, 3)); //使用组合仍然可以使用到A类相关方法 System.out.println("11-3=" + b.fun3(11, 3)); } } //创建一个更加基础的基类 class Base { //把更加基础的方法和成员写道Base类 } //A类 class A extends Base { //返回两个数的差 public int fun1(int num1, int num2) { return num1 - num2; } } //B类继承了A类 //增加一个新功能:完成两个数相加,然后和9求和 class B extends Base { //如果A类需要使用B类的方法,使用组合关系 private A a = new A(); //这里,重写了A类似的方法,可能是无意识 public int fun1(int a, int b) { return a + b; } public int fun2(int a, int b) { return fun1(a, b) + 9; } //我们仍然想要使用A类的方法 public int fun3(int num1, int num2) { return a.fun1(num1, num2); } } ``` ### 5.开闭原则 #### 5.1 基本介绍 > 1. 开闭原则(Open Closed Principle)是编程中**最基础、最重要**的设计原则 > 2. 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。**用抽象构建框架,用实现扩展细节** > 3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。 > 4. 编程中遵循其他原则,以及使用设计模式的目的就是遵循开闭原则 #### 5.2 看下面一段代码 ```java package ocp; /** * 开闭原则 * * @author hanfei * @date 2023/12/02 */ public class Ocp { public static void main(String[] args) { //使用看看存在的问题 GraphEditor graphEditor = new GraphEditor(); graphEditor.drawShape(new Rectangle()); graphEditor.drawShape(new Circle()); graphEditor.drawShape(new Triangle()); } } //这是一个用于绘图的类【使用方】 class GraphEditor { //接收Shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape shape) { if (shape.m_type == 1) { drawRectangle(shape); } else if (shape.m_type == 2) { drawCircle(shape); } else if (shape.m_type == 3) { drawTriangle(shape); } } //绘制矩形 public void drawRectangle(Shape shape) { System.out.println("绘制矩形"); } //绘制圆形 public void drawCircle(Shape shape) { System.out.println("绘制圆形"); } //绘制圆形 public void drawTriangle(Shape shape) { System.out.println("绘制三角形"); } } //Shape类,基类 class Shape { int m_type; } class Rectangle extends Shape { Rectangle() { m_type = 1; } } class Circle extends Shape { Circle() { m_type = 1; } } class Triangle extends Shape { Triangle() { m_type = 3; } } ``` #### 5.3 方式一的缺点 > 1. 优点是比较好理解,简单易操作 > 2. 缺点是违反了设计模式的ocp原则,`都对扩展开放(提供方),对修改关闭(使用方)`。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码 > 3. 比如我们这时要新增加一个图形种类,我们做如下修改,修改的地方比较多 > 4. 代码演示 #### 5.4 改进的思路 > * 把创建Shape类做成抽象,并提供一个抽象的draw方法,让子类去实现即可, > * 这样我们有新的图形种类时,只需要让新的图形类继承Shape,并实现draw方法即可,使用方的代码就不需要->满足了开闭原则 > * 改进后的代码如下: ```java package ocp.improve; /** * 开闭原则 * * @author hanfei * @date 2023/12/02 */ public class Ocp { public static void main(String[] args) { //使用看看存在的问题 GraphEditor graphEditor = new GraphEditor(); graphEditor.drawShape(new Rectangle()); graphEditor.drawShape(new Circle()); graphEditor.drawShape(new Triangle()); graphEditor.drawShape(new OtherGraphic()); } } //这是一个用于绘图的类【使用方】 class GraphEditor { //接收Shape对象,然后根据type,来绘制不同的图形 public void drawShape(Shape shape) { shape.draw(); } } //Shape类,基类 abstract class Shape { int m_type; public abstract void draw(); //抽象方法 } class Rectangle extends Shape { Rectangle() { m_type = 1; } @Override public void draw() { System.out.println("绘制矩形"); } } class Circle extends Shape { Circle() { m_type = 1; } @Override public void draw() { System.out.println("绘制圆形"); } } class Triangle extends Shape { Triangle() { m_type = 3; } @Override public void draw() { System.out.println("绘制三角形"); } } //新增一个图形 class OtherGraphic extends Shape { OtherGraphic() { m_type = 4; } @Override public void draw() { System.out.println("绘制其他图形"); } } ``` ### 6.迪米特法则 #### 6.1 基本介绍 > 1. 一个对象应该对其他对象保持最少的了解 > 2. 类与类关系越密切,耦合度越大 > 3. 迪米特法则(Demeter Principle)又叫**最少知道法则**,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的public方法,不对外泄露任何关系 > 4. 迪米特法则还有个更简单的定义:只与直接的朋友通信 > 5. `直接的朋友`:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现**成员变量,方法参数,方法返回值**中的类为直接的朋友,而出现在局部类变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。 #### 6.2 应用案例 > 1. 有一个学校,下属各个学院和总部,现要求打印出学校总部员工ID的学院员工的ID > 2. 编程实现上面的功能,看代码演示: ```java package demeter; import java.util.ArrayList; import java.util.List; /** * 迪米特法则 * * @author hanfei * @date 2023/12/03 */ //客户端 public class Demeter1 { public static void main(String[] args) { //创建了一个SchoolManager对象 SchoolManager schoolManager = new SchoolManager(); //输出学院员工的id和学校总部员工的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 getAllEmployee() { List list = new ArrayList<>(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到list CollegeEmployee collegeEmployee = new CollegeEmployee(); collegeEmployee.setId("学院员工id=" + i); list.add(collegeEmployee); } return list; } } //学校管理类 //分析SchoolManager的直接朋友类有哪些(成员变量、方法参数、方法返回值)Employee,CollegeManager、 //CollegeEmployee不是直接朋友,违反了迪米特法则 class SchoolManager { //返回学校总部的员工 public List getAllEmployee() { List list = new ArrayList<>(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到list Employee employee = new Employee(); employee.setId("学校总部员工id=" + i); list.add(employee); } return list; } //该方法完成输出学校总部和学院员工信息id void printAllEmployee(CollegeManager manager) { //分析问题: //1.这里的CollegeEmployee不是SchoolManager的直接朋友 //2.ollegeEmployee是以局部变量的形式出现在SchoolManager //3.违反了迪米特法则 //获取学院员工 List list1 = manager.getAllEmployee(); System.out.println("----------------------分公司员工----------------------"); for (CollegeEmployee collegeEmployee : list1) { System.out.println(collegeEmployee.getId()); } //获取学校总部员工 List list2 = this.getAllEmployee(); System.out.println("----------------------学校总部员工----------------------"); for (Employee employee : list2) { System.out.println(employee.getId()); } } } ``` #### 6.3 应用实例改进 > 1. 前面设计的问题在于SchoolManager中,C**ollegeEmployee类并不是SchoolManager类的直接朋友**(分析) > 2. 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合 > 3. 对代码按照迪米特法则进行改进 > 4. 代码演示: ```java package demeter.improve; import java.util.ArrayList; import java.util.List; /** * 迪米特法则 * * @author hanfei * @date 2023/12/03 */ //客户端 public class Demeter1 { public static void main(String[] args) { System.out.println("使用迪米特法则的改进"); //创建了一个SchoolManager对象 SchoolManager schoolManager = new SchoolManager(); //输出学院员工的id和学校总部员工的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 getAllEmployee() { List list = new ArrayList<>(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到list CollegeEmployee collegeEmployee = new CollegeEmployee(); collegeEmployee.setId("学院员工id=" + i); list.add(collegeEmployee); } return list; } //输出学院员工的信息 void printAllEmployee() { //获取学院员工 List list1 = this.getAllEmployee(); System.out.println("----------------------分公司员工----------------------"); for (CollegeEmployee collegeEmployee : list1) { System.out.println(collegeEmployee.getId()); } } } //学校管理类 //分析SchoolManager的直接朋友类有哪些(成员变量、方法参数、方法返回值)Employee,CollegeManager、 //CollegeEmployee不是直接朋友,违反了迪米特法则 class SchoolManager { //返回学校总部的员工 public List getAllEmployee() { List list = new ArrayList<>(); for (int i = 0; i < 10; i++) { //这里我们增加了10个员工到list Employee employee = new Employee(); employee.setId("学校总部员工id=" + i); list.add(employee); } return list; } //该方法完成输出学校总部和学院员工信息id void printAllEmployee(CollegeManager manager) { //分析问题: //1.将输出学院的员工方法,封装到CollegeManager中 //获取学院员工 manager.printAllEmployee(); //获取学校总部员工 List list2 = this.getAllEmployee(); System.out.println("----------------------学校总部员工----------------------"); for (Employee employee : list2) { System.out.println(employee.getId()); } } } ``` #### 6.4 迪米特法则注意事项和细节 > 1. 迪米特法则的核心是降低类之间的耦合 > 2. 但是注意,由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系,并不是要求完全没有依赖关系。 ### 7.合成复用原则 #### 7.1 基本介绍 > * 合成复用原则(Composite Reuse Principle)是尽量使用合成/聚合的方式,而不是使用继承 > * 原型 ![image-20231204230919738](./assets/image-20231204230919738.png) > * 依赖 ![image-20231204231027171](./assets/image-20231204231027171.png) > * 聚合 ![image-20231204231040917](./assets/image-20231204231040917.png) > * 组合 ![image-20231204231057617](./assets/image-20231204231057617.png) ### 8.设计原则核心思想 > 1. 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起 > 2. 针对接口编程,而不是针对实现编程 > 3. 为了交互对象之间的松耦合设计而努力 ## 三、UML类图 > 1. UML--Unified modeling language UML(统一建模语言),是一种用于软件系统分析和设计的语言工具,它用于帮助软件开发人员进行思考和记录思路的结果 > 2. UML本身是一套符号的规定,就像数学符号和化学符号一样,这些符号用于描述软件模型中的各个元素和他们之间的关系,比如类、接口、实现、泛化、依赖、组合、聚合等,如下图: > 3. 使用UML来建模,常用的工具有Rational Rose,也可以使用一些插件来建模。 ### 1.PlantUML语法 > * 参考网址:[17.plantUML画类图的语法、组合关系和聚合关系之间的区别_plantuml 聚合-CSDN博客](https://blog.csdn.net/Sai_BAN/article/details/130820456) > * [彻底搞清类的依赖关系、关联关系、聚合关系、组合关系 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/350793664) #### 2.1 泛化关系【extends】 > * `Generalization`:用带空心三角形的实线表示,从子类指向父类,可以用`extends`关键字标注。例如:`class01 <|-- class02 : extends` ![diagram-14201539998260591598](./assets/diagram-14201539998260591598.png) #### 2.2 实现关系【implements】 > * `Realization`:用带空心三角形的虚线表示,从实现类指向接口,可以用`implements`关键字标注。例如:`class03 <|.. class04:implements` ![diagram-6904518665760792703](./assets/diagram-6904518665760792703.png) #### 2.3 依赖关系 > * `Dependency`:用带箭头的虚线表示,从使用类指向被依赖的类,可以用`:`后接标签文字来说明。例如:`class05 ..> class06 : uses` ![diagram-3936778970378780428](./assets/diagram-3936778970378780428.png) #### 2.4 关联关系 > * `Association`:用带箭头的实线表示,从使用类指向被关联的类,可以用`:`后接标签文字来说明。可以是单向或双向。例如:`class07 -- class08 : knows` ![diagram-17988449544612586338](./assets/diagram-17988449544612586338.png) #### 2.5 聚合关系 > * `Aggregation`:用带空心棱形的视线表示,从部分指向整体,可以用`:`后接标签文字来说明。例如:`class09 o-- class10 : has` ![diagram-6704381308458218139](./assets/diagram-6704381308458218139.png) #### 2.6 组合关系 > * `Composite`:用带实心棱形的实线表示,从部分指向整体,可以用`:`后接标签文字来说明。例如:`class11 *-- class12 : contains` ![diagram-9125977651290843134](./assets/diagram-9125977651290843134.png) ### 2.基本介绍 > * 画UML图与写文章差不多,都是把自己的思想描述给别人看,关键在于思路和条理,UML图分类: > 1. 用例图(use case) > 2. 静态结构图:类图、对象图、包图、组件图、部署图 > 3. 动态行为图:交互图(时序图与协作图)、状态图、活动图 > * 说明: > 1. 类图是描述类与类之间关系的,是UML图中最核心的 > 2. 在讲解设计模式时,我们必然会使用类图,为了让学员们能够把设计模式学到位,需要先给大家讲解类图 > 3. `温馨提示:`如果已经掌握UML类图的学员,可以直接听设计模式的章节 ### 3.类图关系 > 1. 用于描述系统中的类(对象)本身的组成和类(对象)之间的各种静态关系 > 2. 类之间的关系:`依赖、泛化(继承)、实现、关联、聚合与组合` > 3. 类图简单举例: ```java public class Person{ private Integer id; private String name; public void setName(String name){ this.name = name; } public String getName(){ return name; } } ``` ![diagram-10784159654349662916](./assets/diagram-10784159654349662916.png) #### 3.1 类图-依赖关系(Dependency) > * 只要是在`类中用到了对方`,那么他们之间就存在依赖关系。如果没有对方,连编译都通过不了。 ![diagram-3477931231128679689](./assets/diagram-3477931231128679689.png) ```java public class PersonServiceBean { private PersonDao personDao; public void save(Person person) {} public IDCard getIDCard(Integer personId) { return null; } public void modify() { Department department = new Department(); } } public class Department { } public class IDCard { } public class Person { } public class PersonDao { } ``` > * 小结: > 1. 类中用到了对方 > 2. 如果是类的成员属性 > 3. 如果是方法的返回类型 > 4. 如果是方法的接收参数类型 > 5. 方法中使用到了类 #### 3.2 类图-泛化关系(Generalization) > * 泛化关系实际上就是继承关系,它是`依赖关系的特例` ![diagram-6600983242397358226](./assets/diagram-6600983242397358226.png) ```java public abstract class DaoSupport { public void save(Object entity){} public void delete(Object id){} } public class PersonServiceBean extends DaoSupport{ } ``` > * 小结: > 1. 泛化关系实际上就是继承关系 > 2. 如果A类继承了B类,我们就说A类和B类存在泛化关系 #### 3.3 类图-实现关系(Implementation) > * 实现关系实际上就是A类实现B类,它是`依赖关系的特里` ![diagram-13080370652901292906](./assets/diagram-13080370652901292906.png) ```java public interface PersonService { public void delete(Integer id); } public class PersonServiceBean implements PersonService{ @Override public void delete(Integer id) { } } ``` #### 3.4 类图-关联关系(Association) > * 关联关系实际上就是**类与类之间的联系,他是依赖关系的特例** > * 关联具有导航性,即双向关系或单向关系 > * 关系具有多重性,如`1`(表示有且仅有一个),`0...`(表示0个或者多个),`0,1`(表示0个或者一个),`n,m`(表示n到m个都可以),`m...`(表示至少m个) > * **单向一对一关系** ```java public class Person{ private IDCard card; } public class IDCard{} ``` > * **双向一对一关系** ```java public class Person{ private IDCard card; } public class IDCard{ private Person person; } ``` #### 3.5 类图-聚合关系(Aggregation) > * 聚合关系(Aggregation)表示的是整体和部分的关系,`整体与部分可以分开。聚合关系是关联关系的特例`。所以它具有关联的导航性与多重性。 > * 如:一台电脑由键盘(keyboard)、显示器(monitor),鼠标(mouse)等组成;组成电脑的各个配件是可以从电脑上分离出来的,使用带棱形的实线来表示: ![diagram-13104922904670772371](./assets/diagram-13104922904670772371.png) ```java public class Computer { private Monitor monitor; private KeyBoard keyBoard; private Mouse mouse; //鼠标可以和Computer分离 public void setMonitor(Monitor monitor) { this.monitor = monitor; } public void setKeyBoard(KeyBoard keyBoard) { this.keyBoard = keyBoard; } public void setMouse(Mouse mouse) { this.mouse = mouse; } } ``` #### 3.6 类图-组合关系(Composition) > * 如果我们认为Mouse、Monitor和Computer是不可分离的,则**升级为组合关系** ![diagram-14298779443933584015](./assets/diagram-14298779443933584015.png) ```java public class Computer { private Monitor monitor = new Monitor(); private KeyBoard keyBoard = new KeyBoard(); private Mouse mouse = new Mouse(); } public class Client{ public static void main(String[] args){ Computer computer = new Computer(); } } ``` > * 组合关系:也是整体与部分的关系,但是`整体和部分不可以分开` > * 再看一个案例:在程序中我们定义实体:Person 与 IDCard、Head,那么 Head 和 Person 是组合,IDCard 和 Person 就是聚合。 > * 但是如果在程序中Person实体中定义了对IDCard进行`级联删除`,即删除Person时连同IDCard一起删除,那么Person和IDCard就是组合了 ![diagram-7143698190106847401](./assets/diagram-7143698190106847401.png) ```java public class Person{ private IDCard card; private Head head = new Head(); } public class IDCard{} public class Head{} ``` > * 小结: > * 如何区别组合和聚合: > * 组合就是不可分割的,两个类同生同死,就像是人和头部,这二者是同时出现的 > * 聚合就是可以拆分的,就像一个人持有一张卡,这个卡是可以拆出去的 ## 四、设计模式概述与分类 ### 1.掌握设计模式的层次 > 1. 第一层:刚开始学习编程不久,听说过什么是设计模式 > 2. 第二层:有很长时间的编程经验,自己写了很多代码,其中用到了设计模式,但是自己却不知道 > 3. 第三层:学习过了设计模式,发现自己已经在使用了,并且发现一些新的模式挺好用的 > 4. 第四层:阅读了很多别人写的源码=和框架,在其中看到别人设计模式,并且能够领会`设计模式的精妙和带来的好处` > 5. 第五层:代码写着写着,自己都没有意识到使用了设计模式,并且熟练的写了出来 ### 2.设计模式介绍 > 1. 设计模式是程序员在面对同类软件工程设计问题所总结出来的有用的经验,模式不是代码,而是某类问题的通用解决方案,设计模式(Design pattern)代表了最佳的实践。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的 > 2. 设计模式的本质提高软件的维护性,通用性和扩展性,并降低软件的复杂度 > 3. 《设计模式》是经典的书,作者是 Eric Gamma、Richard Helm、Ralph Johnson 和 John Vlissides Design(俗称"四人组GOF") > 4. 设计模式并不局限于某种语言,java、php、c++都有设计模式 ### 3.设计模式类型 > * 设计模式分为三种类型,共23种: > 1. 创建型模式: > * `单例模式` > * 抽象工厂模式 > * 原型模式 > * 建造者模式 > * `工厂模式` > 2. 结构型模式: > * 适配器模式 > * 桥接模式 > * `装饰模式` > * 组合模式 > * 外观模式 > * 享元模式 > * `代理模式` > 3. 行为型模式: > * 模板方法模式 > * 命令模式 > * 访问者模式 > * 迭代器模式 > * `观察者模式` > * 中介者模式 > * 备忘录模式 > * 解释器模式(Interpreter模式) > * 状态模式 > * 策略模式 > * 职责链模式(责任链模式) > * 注意:不同的书籍上对分类名和名称略有差别