设计模式

  • 前言
    • 第一章、设计模式七大原则
      • 1、单一职责原则
        • 1.1、基本介绍
        • 1.2、代码分析
        • 1.3、单一职责原则注意事项和细节
      • 2、接口隔离原则
        • 2.1、基本介绍
        • 2.2、代码分析
      • 3、依赖倒转原则
        • 3.1、基本介绍
        • 3.2、代码分析
        • 3.3、依赖关系传递的三种方式和应用案例
        • 3.4、依赖倒转原则的注意事项和细节
      • 4、里氏替换原则
        • 4.1、基本介绍
        • 4.2、问题及思考
        • 4.3、解决方案
      • 5、开闭原则
        • 5.1、基本介绍
        • 5.2、代码分析
      • 6、迪米特法则
        • 6.1、基本介绍
        • 6.2、应用实例
        • 6.3、应用实例改进
        • 6.4、迪米特法则注意事项和细节
      • 7、合成复用原则
        • 7.1、基本介绍
        • 7.2、代码分析
        • 7.3、合成复用原则的实现方法
    • 第二章、23种设计模式
      • 1.设计模式——代理模式
        • 1.1.代理模式的基本介绍
        • 1.2.静态代理
        • 1.3.动态代理
        • 1.4.Cglib代理
      • 2.命令模式
        • 2.1.智能生活项目需求
        • 2.2.命令模式基本介绍
        • 2.3.命令模式的原理类图
        • 2.4.代码示例
        • 2.5.代码解释说明
        • 2.6.命令模式的注意事项和细节
      • 3.章职责链模式
        • 3.1.基本介绍
        • 3.2.职责链模式的原理类图
        • 3.3.职责链模式解决 OA 系统采购审批
        • 3.4.职责链模式的注意事项和细节

前言

1、设计模式得诞生

软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。这个术语是由埃里希·伽玛(Erich Gamma)等人在 1990 年代从建筑设计领域引入到计算机科学的。

2、设计模式好处

  • 大厦 VS 简易房
  • 拿实际工作经历来说, 当一个项目开发完后,如果客户提出增新功能,怎么办?。(可扩展性,使用设计模式,软件具有很好的扩展性)
  • 如果项目开发完后,原来程序员离职,你接手维护该项目怎么办? (维护性[可读性、规范性])
  • 目前程序员门槛越来越高,一线 IT 公司(大厂),都会问你在实际项目中使用过什么设计模式,怎样使用的,解决了什么问题。
  • 如果想成为合格软件工程师,那就花时间来研究下设计模式是非常必要的

第一章、设计模式七大原则

七大原则包括:
1、单一职责原则
2、接口隔离原则
3、依赖倒转(倒置)原则
4、里氏替换原则
5、开闭原则
6、迪米特法则
7、合成复用原则

1、单一职责原则

1.1、基本介绍

对类来说的,即一个类应该只负责一项职责。如类 A 负责两个不同职责:职责 1,职责 2。当职责 1 需求变更而改变 A 时,可能造成职责 2 执行错误,所以需要将类 A 的粒度分解为 A1,A2

1.2、代码分析

方案一

package com.tang.designpattern.principle.singleresponsibility;import com.tang.designpattern.domain.pojo.Vehicle;/*** @author Dream* @date 2022/4/12 14:26* 方案一分析:* 1、在方式 1 的 run 方法中,违反了单一职责原则* 2、解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可*/
public class SingleResponsibility1 {public static void main(String[] args) {Vehicle vehicle = new Vehicle();vehicle.run("摩托车");vehicle.run("汽车");vehicle.run("飞机");}
}
package com.tang.designpattern.domain.pojo;/*** @author Dream* @date 2022/4/12 14:27* 车辆类*/
public class Vehicle {public void run(String vehicle) {System.out.println(vehicle + " 在公路上运行....");}
}

方案一分析:
1、在方式 1 的 run 方法中,违反了单一职责原则
2、解决的方案非常的简单,根据交通工具运行方法不同,分解成不同类即可

方案二

package com.tang.designpattern.principle.singleresponsibility;import com.tang.designpattern.domain.pojo.AirVehicle;
import com.tang.designpattern.domain.pojo.RoadVehicle;/*** @author Dream* @date 2022/4/12 14:30* 方案二分析:* 1、遵守单一职责原则* 2、但是这样做的改动很大,即将类分解,同时修改客户端* 3、改进:直接修改 Vehicle 类,改动的代码会比较少=>方案 3*/
public class SingleResponsibility2 {public static void main(String[] args) {RoadVehicle roadVehicle = new RoadVehicle();roadVehicle.run("摩托车");roadVehicle.run("汽车");AirVehicle airVehicle = new AirVehicle();airVehicle.run("飞机");}
}
package com.tang.designpattern.domain.pojo;/*** @author Dream* @date 2022/4/12 14:31* 公路交通工具类*/
public class RoadVehicle {public void run(String vehicle) {System.out.println(vehicle + "公路运行");}
}
package com.tang.designpattern.domain.pojo;/*** @author Dream* @date 2022/4/12 14:31* 天空交通工具类*/
public class AirVehicle {public void run(String vehicle) {System.out.println(vehicle + "天空运行");}
}

方案二分析:
1、遵守单一职责原则
2、但是这样做的改动很大,即将类分解,同时修改客户端
3、改进:直接修改 Vehicle 类,改动的代码会比较少=>方案 3

方案三

package com.tang.designpattern.principle.singleresponsibility;import com.tang.designpattern.domain.pojo.Vehicle2;/*** @author Dream* @date 2022/4/12 14:37* 方案三分析:* 1、这种修改方法没有对原来的类做大的修改,只是增加方法* 2、这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责*/
public class SingleResponsibility3 {public static void main(String[] args) {// TODO Auto-generated method stubVehicle2 vehicle2 = new Vehicle2();vehicle2.run("汽车");vehicle2.runWater("轮船");vehicle2.runAir("飞机");}
}
package com.tang.designpattern.domain.pojo;/*** @author Dream* @date 2022/4/12 14:36*/
public class Vehicle2 {public void run(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、这种修改方法没有对原来的类做大的修改,只是增加方法
2、这里虽然没有在类这个级别上遵守单一职责原则,但是在方法级别上,仍然是遵守单一职责

1.3、单一职责原则注意事项和细节

1、降低类的复杂度,一个类只负责一项职责。
2、提高类的可读性,可维护性
3、降低变更引起的风险
4、通常情况下,我们应当遵守单一职责原则,只有逻辑足够简单,才可以在代码级违反单一职责原则;只有类中方法数量足够少,可以在方法级别保持单一职责原则

2、接口隔离原则

2.1、基本介绍

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

2、类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,如果接口 Interface1 对于类 A 和类 C来说不是最小接口,那么类 B 和类 D 必须去实现他们不需要的方法。
3、按隔离原则应当这样处理:
将接口 Interface1 拆分为独立的几个接口(这里我们拆分成 3 个接口),类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则

2.2、代码分析

改进前

package com.tang.designpattern.principle.interfacesegregation.before;/*** @author Dream* @date 2022/4/12 15:43*/
public class A {public void depend1(Interface1 i) {i.operation1();}public void depend2(Interface1 i) {i.operation2();}public void depend3(Interface1 i) {i.operation3();}
}
package com.tang.designpattern.principle.interfacesegregation.before;/*** @author Dream* @date 2022/4/12 15:41*/
public class B implements Interface1 {@Overridepublic void operation1() {System.out.println("B 实现了 operation1");}@Overridepublic void operation2() {System.out.println("B 实现了 operation2");}@Overridepublic void operation3() {System.out.println("B 实现了 operation3");}@Overridepublic void operation4() {System.out.println("B 实现了 operation4");}@Overridepublic void operation5() {System.out.println("B 实现了 operation5");}
}
package com.tang.designpattern.principle.interfacesegregation.before;/*** @author Dream* @date 2022/4/12 15:45*/
public class C {public void depend1(Interface1 i) {i.operation1();}public void depend4(Interface1 i) {i.operation4();}public void depend5(Interface1 i) {i.operation5();}}
package com.tang.designpattern.principle.interfacesegregation.before;/*** @author Dream* @date 2022/4/12 15:43*/
public class D implements Interface1 {@Overridepublic void operation1() {System.out.println("D 实现了 operation1");}@Overridepublic void operation2() {System.out.println("D 实现了 operation2");}@Overridepublic void operation3() {System.out.println("D 实现了 operation3");}@Overridepublic void operation4() {System.out.println("D 实现了 operation4");}@Overridepublic void operation5() {System.out.println("D 实现了 operation5");}
}
package com.tang.designpattern.principle.interfacesegregation.before;/*** @author Dream* @date 2022/4/12 15:40*/
public interface Interface1 {void operation1();void operation2();void operation3();void operation4();void operation5();
}
package com.tang.designpattern.principle.interfacesegregation.before;/*** @author Dream* @date 2022/4/12 15:45*/
public class Segregation1 {public static void main(String[] args) {A a = new A();B b = new B();a.depend1(b);}
}

方案分析

接口Interface1对于类A和类C不是最小的 因为对于A来说operation4,operation5方法是多余的,而对于C来说operation2,operation3是多余的。

改进后

package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 16:03*/
public class A {public void depend1(Interface1 i) {i.operation1();}public void depend2(Interface2 i) {i.operation2();}public void depend3(Interface2 i) {i.operation3();}}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 15:52*/
public class B implements Interface1, Interface2 {@Overridepublic void operation1() {System.out.println("B 实现了 operation1");}@Overridepublic void operation2() {System.out.println("B 实现了 operation2");}@Overridepublic void operation3() {System.out.println("B 实现了 operation3");}
}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 16:03*/
public class C {public void depend1(Interface1 i) {i.operation1();}public void depend4(Interface3 i) {i.operation4();}public void depend5(Interface3 i) {i.operation5();}
}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 16:02*/
public class D implements Interface1, Interface3{@Overridepublic void operation1() {System.out.println("D 实现了  operation1");}@Overridepublic void operation4() {System.out.println("D 实现了  operation4");}@Overridepublic void operation5() {System.out.println("D 实现了  operation5");}
}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 15:51*/
public interface Interface1 {void operation1();
}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 15:51*/
public interface Interface2 {void operation2();void operation3();}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 15:51*/
public interface Interface3 {void operation4();void operation5();}
package com.tang.designpattern.principle.interfacesegregation.after;/*** @author Dream* @date 2022/4/12 16:05*/
public class Segregation2 {public static void main(String[] args) {// 使用一把A a = new A();a.depend1(new B()); // A 类通过接口去依赖 B 类a.depend2(new B());a.depend3(new B());C c = new C();c.depend1(new D()); // C 类通过接口去依赖(使用)D 类c.depend4(new D());c.depend5(new D());}
}

方案分析

1、类 A 通过接口 Interface1 依赖类 B,类 C 通过接口 Interface1 依赖类 D,如果接口 Interface1 对于类 A 和类 C来说不是最小接口,那么类 B 和类 D 必须去实现他们不需要的方法
2、将接口 Interface1 拆分为独立的几个接口,类 A 和类 C 分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则
3、接口 Interface1 中出现的方法,根据实际情况拆分为三个接口

3、依赖倒转原则

3.1、基本介绍

依赖倒转原则(Dependence Inversion Principle)是指:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象
  2. 抽象不应该依赖细节,细节应该依赖抽象
  3. 依赖倒转(倒置)的中心思想是面向接口编程
  4. 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
  5. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成

3.2、代码分析

实现方案 1

package com.tang.designpattern.principle.dependenceinversion;/*** @author Dream* @date 2022/4/12 16:28*/
public class DependecyInversion {public static void main(String[] args) {Person person = new Person();person.receive(new Email());}
}
package com.tang.designpattern.principle.dependenceinversion;/*** @author Dream* @date 2022/4/12 16:28*/
public class Email {public String getInfo() {return "电子邮件信息: hello,world";}
}
package com.tang.designpattern.principle.dependenceinversion;/*** @author Dream* @date 2022/4/12 16:29*/
public class Person {public void receive(Email email) {System.out.println(email.getInfo());}
}

方案分析

分析

  1. 简单,比较容易想到
  2. 如果我们获取的对象是 微信,短信等等,则新增类,同时 Perons 也要增加相应的接收方法
  3. 解决思路:引入一个抽象的接口 IReceiver, 表示接收者, 这样 Person 类与接口 IReceiver 发生依赖
    因为 Email, WeiXin 等等属于接收的范围,他们各自实现 IReceiver 接口就 ok, 这样我们就符号依赖倒>转原则

方案 2

package com.tang.designpattern.principle.dependenceinversion.case2;/*** @author Dream* @date 2022/4/12 16:34*/
public class DependecyInversion {public static void main(String[] args) {//客户端无需改变Person person = new Person();person.receive(new Email());person.receive(new WeiXin());}
}
package com.tang.designpattern.principle.dependenceinversion.case2;/*** @author Dream* @date 2022/4/12 16:35*/
public class Email implements IReceiver{@Overridepublic String getInfo() {return "电子邮件信息: hello,world";}
}
package com.tang.designpattern.principle.dependenceinversion.case2;/*** @author Dream* @date 2022/4/12 16:34*/
public interface IReceiver {public String getInfo();
}
package com.tang.designpattern.principle.dependenceinversion.case2;/*** @author Dream* @date 2022/4/12 16:35*/
public class Person {//这里我们是对接口的依赖public void receive(IReceiver receiver) {System.out.println(receiver.getInfo());}}
package com.tang.designpattern.principle.dependenceinversion.case2;/*** @author Dream* @date 2022/4/12 16:35*/
public class WeiXin implements IReceiver{@Overridepublic String getInfo() {return "微信信息: hello,ok";}
}

方案二分析

引入一个抽象的接口 IReceiver表示发生信息的行为 接着Email, WeiXin 等等都可以发送信息,他们各自实现 IReceiver 接口就 ok, 这样我们就符号依赖倒转原则

3.3、依赖关系传递的三种方式和应用案例

  1. 接口传递
package com.tang.designpattern.principle.dependenceinversion.case3.byinterface;/*** @author Dream* @date 2022/4/12 16:47*/
public class ChangHong implements ITV{@Overridepublic void play() {System.out.println("长虹电视机,打开");}
}
package com.tang.designpattern.principle.dependenceinversion.case3.byinterface;/*** @author Dream* @date 2022/4/12 16:46*/
public class DependencyPass {public static void main(String[] args) {//通过接口进行依赖传递ChangHong changHong = new ChangHong();OpenAndClose openAndClose = new OpenAndClose();openAndClose.open(changHong);}
}
package com.tang.designpattern.principle.dependenceinversion.case3.byinterface;/*** @author Dream* @date 2022/4/12 16:46*/
public interface IOpenAndClose {public void open(ITV tv); //抽象方法,接收接口
}
package com.tang.designpattern.principle.dependenceinversion.case3.byinterface;/*** @author Dream* @date 2022/4/12 16:47*/
public interface ITV {public void play();
}
package com.tang.designpattern.principle.dependenceinversion.case3.byinterface;/*** @author Dream* @date 2022/4/12 16:47*/
public class OpenAndClose implements IOpenAndClose{@Overridepublic void open(ITV tv) {tv.play();}
}
  1. 构造方法传递
package com.tang.designpattern.principle.dependenceinversion.case3.constructionmethod;/*** @author Dream* @date 2022/4/12 16:47*/
public class ChangHong implements ITV {@Overridepublic void play() {System.out.println("长虹电视机,打开");}
}
package com.tang.designpattern.principle.dependenceinversion.case3.constructionmethod;
/*** @author Dream* @date 2022/4/12 16:46*/
public class DependencyPass {public static void main(String[] args) {//通过接口进行依赖传递ChangHong changHong = new ChangHong();//通过构造器进行依赖传递OpenAndClose openAndClose = new OpenAndClose(changHong);openAndClose.open();}
}
package com.tang.designpattern.principle.dependenceinversion.case3.constructionmethod;/*** @author Dream* @date 2022/4/12 16:52*/
public interface IOpenAndClose {public void open(); //抽象方法
}
package com.tang.designpattern.principle.dependenceinversion.case3.constructionmethod;/*** @author Dream* @date 2022/4/12 16:47*/
public interface ITV {public void play();
}
package com.tang.designpattern.principle.dependenceinversion.case3.constructionmethod;/*** @author Dream* @date 2022/4/12 16:53*/
public class OpenAndClose implements IOpenAndClose {//成员public ITV tv;//构造器public OpenAndClose(ITV tv) {this.tv = tv;}@Overridepublic void open() {this.tv.play();}
}
  1. setter 方式传递
package com.tang.designpattern.principle.dependenceinversion.case3.bysetter;/*** @author Dream* @date 2022/4/12 16:47*/
public class ChangHong implements ITV {@Overridepublic void play() {System.out.println("长虹电视机,打开");}
}
package com.tang.designpattern.principle.dependenceinversion.case3.bysetter;/*** @author Dream* @date 2022/4/12 16:46*/
public class DependencyPass {public static void main(String[] args) {ChangHong changHong = new ChangHong();//通过 setter 方法进行依赖传递OpenAndClose openAndClose = new OpenAndClose();openAndClose.setTv(changHong);openAndClose.open();}
}
package com.tang.designpattern.principle.dependenceinversion.case3.bysetter;/*** @author Dream* @date 2022/4/12 17:01*/
public interface IOpenAndClose {// 抽象方法public void open();public void setTv(ITV tv);
}
package com.tang.designpattern.principle.dependenceinversion.case3.bysetter;/*** @author Dream* @date 2022/4/12 17:01*/
public interface ITV {public void play();
}
package com.tang.designpattern.principle.dependenceinversion.case3.bysetter;/*** @author Dream* @date 2022/4/12 17:02*/
public class OpenAndClose implements IOpenAndClose{private ITV tv;@Overridepublic void open() {this.tv.play();}@Overridepublic void setTv(ITV tv) {this.tv = tv;}
}

3.4、依赖倒转原则的注意事项和细节

  1. 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好.
  2. 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
  3. 继承时遵循里氏替换原则

4、里氏替换原则

4.1、基本介绍

  1. 里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的。

  2. 如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。

  3. 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法

  4. 里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖 来解决问题。.

4.2、问题及思考

来看一段代码所带来的问题

package com.tang.designpattern.principle.liskovsubstitution;/*** @author Dream* @date 2022/4/18 10:10*/
public class A {// 返回两个数的差public int func1(int num1, int num2) {return num1 - num2;}}
package com.tang.designpattern.principle.liskovsubstitution;/*** @author Dream* @date 2022/4/18 10:11*/
public class B extends A {//这里,重写了 A 类的方法,  可能是无意识public int func1(int a, int b) {return a + b;}public int func2(int a, int b) {return func1(a, b) + 9;}}
package com.tang.designpattern.principle.liskovsubstitution;/*** @author Dream* @date 2022/4/18 10:10*/
public class Liskov {public static void main(String[] args) {// TODO Auto-generated method stubA a = new A();System.out.println("11-3=" + a.func1(11, 3));System.out.println("1-8=" + a.func1(1, 8));System.out.println("-----------------------");B b = new B();System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出 11-3System.out.println("1-8=" + b.func1(1, 8));// 1-8 System.out.println("11+3+9=" + b.func2(11, 3));}
}

4.3、解决方案

  1. 我们发现原来运行正常的相减功能发生了错误。原因就是类 B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
  2. 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替.
  3. 改进方案
  4. 代码实现
package com.tang.designpattern.principle.liskovsubstitution.solve;import com.tang.designpattern.principle.liskovsubstitution.solve.Base;/*** @author Dream* @date 2022/4/18 10:21*/
public class A extends Base {// 返回两个数的差public int func1(int num1, int num2) {return num1 - num2;}}
package com.tang.designpattern.principle.liskovsubstitution.solve;/*** @author Dream* @date 2022/4/18 10:21*/
public class B extends Base {//如果 B 需要使用 A 类的方法,使用组合关系private A a = new A();//这里,重写了 A 类的方法,  可能是无意识public int func1(int a, int b) {return a + b;}public int func2(int a, int b) {return func1(a, b) + 9;}//我们仍然想使用 A 的方法public int func3(int a, int b) {return this.a.func1(a, b);}}
package com.tang.designpattern.principle.liskovsubstitution.solve;/*** @author Dream* @date 2022/4/18 10:20*/
public class Base {//把更加基础的方法和成员写到 Base 类
}
package com.tang.designpattern.principle.liskovsubstitution.solve;/*** @author Dream* @date 2022/4/18 10:20*/
public class Liskov {public static void main(String[] args) {A a = new A();System.out.println("11-3=" + a.func1(11, 3));System.out.println("1-8=" + a.func1(1, 8));System.out.println("-----------------------");B b = new B();//因为 B 类不再继承 A 类,因此调用者,不会再 func1 是求减法//调用完成的功能就会很明确System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出 11+3System.out.println("1+8=" + b.func1(1, 8));// 1+8 System.out.println("11+3+9=" + b.func2(11, 3));//使用组合仍然可以使用到 A 类相关方法System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出 11-3}
}

5、开闭原则

5.1、基本介绍

  1. 开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则

  2. 一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。

  3. 当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

  4. 编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。

5.2、代码分析

  1. 类图
  2. 代码
package com.tang.designpattern.principle.openclosed;/*** @author Dream* @date 2022/4/18 10:31*/
public class Circle extends Shape {Circle() {super.m_type = 2;}}
package com.tang.designpattern.principle.openclosed;/*** @author Dream* @date 2022/4/18 10:29*/
public class GraphicEditor {//接收 Shape 对象,然后根据 type,来绘制不同的图形public void drawShape(Shape s) {if (s.m_type == 1)drawRectangle(s);else if (s.m_type == 2)drawCircle(s);else if (s.m_type == 3)drawTriangle(s);}//绘制矩形public void drawRectangle(Shape r) {System.out.println(" 绘制矩形 ");}//绘制圆形public void drawCircle(Shape r) {System.out.println(" 绘制圆形 ");}//绘制三角形public void drawTriangle(Shape r) {System.out.println(" 绘制三角形 ");}}
package com.tang.designpattern.principle.openclosed;/*** @author Dream* @date 2022/4/18 10:28*/
public class Ocp {public static void main(String[] args) {//使用看看存在的问题GraphicEditor graphicEditor = new GraphicEditor();graphicEditor.drawShape(new Rectangle());graphicEditor.drawShape(new Circle());graphicEditor.drawShape(new Triangle());}
}
package com.tang.designpattern.principle.openclosed;/*** @author Dream* @date 2022/4/18 10:30*/
public class Rectangle extends Shape {Rectangle() {super.m_type = 1;}}
package com.tang.designpattern.principle.openclosed;/*** @author Dream* @date 2022/4/18 10:30*/
public class Shape {int m_type;
}
package com.tang.designpattern.principle.openclosed;/*** @author Dream* @date 2022/4/18 10:31*/
public class Triangle extends Shape {Triangle() {super.m_type = 3;}}
  1. 优缺点
  • 优点是比较好理解,简单易操作。
  • 缺点是违反了设计模式的 ocp 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
  • 比如我们这时要新增加一个图形种类 三角形,我们需要做如下修改,修改的地方较多
  1. 改进的思路分析
  • 思路:把创建 Shape 类做成抽象类,并提供一个抽象的 draw 方法,让子类去实现即可,这样我们有新的图形种类时,只需要让新的图形类继承 Shape,并实现 draw 方法即可,使用方的代码就不需要修 -> 满足了开闭原则

  • 代码

package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:42*/
public class Circle extends Shape{Circle() {super.m_type = 2;}@Overridepublic void draw() {System.out.println(" 绘制圆形 ");}
}
package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:41*/
public class GraphicEditor {//接收 Shape 对象,调用 draw 方法public void drawShape(Shape s) {s.draw();}}
package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:41*/
public class Ocp {public static void main(String[] args) {//使用看看存在的问题GraphicEditor graphicEditor = new GraphicEditor();graphicEditor.drawShape(new Rectangle());graphicEditor.drawShape(new Circle());graphicEditor.drawShape(new Triangle());graphicEditor.drawShape(new OtherGraphic());}
}
package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:43*/
public class OtherGraphic extends Shape{OtherGraphic() {super.m_type = 4;}@Overridepublic void draw() {System.out.println(" 绘制其它图形 ");}
}
package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:42*/
public class Rectangle extends Shape{Rectangle() {super.m_type = 1;}@Overridepublic void draw() {// TODO Auto-generated method stubSystem.out.println(" 绘制矩形 ");}
}
package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:41*/
public abstract class Shape {int m_type;public abstract void draw();//抽象方法
}
package com.tang.designpattern.principle.openclosed.after;/*** @author Dream* @date 2022/4/18 10:43*/
public class Triangle extends Shape{Triangle() {super.m_type = 3;}@Overridepublic void draw() {System.out.println(" 绘制三角形  ");}
}

6、迪米特法则

6.1、基本介绍

  1. 一个对象应该对其他对象保持最少的了解

  2. 类与类关系越密切,耦合度越大

  3. 迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息

  4. 迪米特法则还有个更简单的定义:只与直接的朋友通信

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

6.2、应用实例

  1. 有一个学校,下属有各个学院和总部,现要求打印出学校总部员工 ID 和学院员工的 id
  2. 编程实现上面的功能, 看代码演示
  3. 代码演示
package com.tang.designpattern.principle.demeterprinciple.before;/*** @author Dream* @date 2022/4/22 13:35* 学院的员工类*/
public class CollegeEmployee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}}
package com.tang.designpattern.principle.demeterprinciple.before;import java.util.ArrayList;
import java.util.List;/*** @author Dream* @date 2022/4/22 13:36*/
public class CollegeManager {//返回学院的所有员工public List<CollegeEmployee> getAllEmployee() {List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();for (int i = 0; i < 10; i++) { //这里我们增加了 10 个员工到 listCollegeEmployee emp = new CollegeEmployee();emp.setId("学院员工 id= " + i);list.add(emp);}return list;}
}
package com.tang.designpattern.principle.demeterprinciple.before;/*** @author Dream* @date 2022/4/22 13:34*/
public class Demeter1 {public static void main(String[] args) {//创建了一个 SchoolManager 对象SchoolManager schoolManager = new SchoolManager();//输出学院的员工 id  和   学校总部的员工信息schoolManager.printAllEmployee(new CollegeManager());}
}
package com.tang.designpattern.principle.demeterprinciple.before;/*** @author Dream* @date 2022/4/22 13:35* 学校总部员工类*/
public class Employee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}
}
package com.tang.designpattern.principle.demeterprinciple.before;import java.util.ArrayList;
import java.util.List;/*** @author Dream* @date 2022/4/22 13:36*/
public class SchoolManager {//返回学校总部的员工public List<Employee> getAllEmployee() {List<Employee> list = new ArrayList<Employee>();for (int i = 0; i < 5; i++) { //这里我们增加了 5 个员工到 listEmployee emp = new Employee();emp.setId("学校总部员工 id= " + i);list.add(emp);}return list;}//该方法完成输出学校总部和学院员工信息(id)void printAllEmployee(CollegeManager sub) {//分析问题//1. 这 里 的  CollegeEmployee 不是  SchoolManager 的直接朋友//2. CollegeEmployee 是以局部变量方式出现在 SchoolManager//3. 违反了 迪米特法则//获取到学院员工List<CollegeEmployee> list1 = sub.getAllEmployee();System.out.println("------------学院员工------------");for (CollegeEmployee e : list1) {System.out.println(e.getId());}//获取到学校总部员工List<Employee> list2 = this.getAllEmployee();System.out.println("------------学校总部员工------------");for (Employee e : list2) {System.out.println(e.getId());}}}

6.3、应用实例改进

  1. 前面设计的问题在于 SchoolManager 中,CollegeEmployee 类并不是 SchoolManager 类的直接朋友 (分析)
  2. 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合
  3. 对代码按照迪米特法则 进行改进. (看老师演示)
  4. 代码演示
package com.tang.designpattern.principle.demeterprinciple.after;/*** @author Dream* @date 2022/4/22 13:35* 学院的员工类*/
public class CollegeEmployee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}}
package com.tang.designpattern.principle.demeterprinciple.after;import java.util.ArrayList;
import java.util.List;/*** @author Dream* @date 2022/4/22 13:36*/
public class CollegeManager {//返回学院的所有员工public List<CollegeEmployee> getAllEmployee() {List<CollegeEmployee> list = new ArrayList<CollegeEmployee>();for (int i = 0; i < 10; i++) { //这里我们增加了 10 个员工到 listCollegeEmployee emp = new CollegeEmployee();emp.setId("学院员工 id= " + i);list.add(emp);}return list;}// 输 出 学 院 员 工 的 信 息public void printEmployee() {//获取到学院员工List<CollegeEmployee> list1 = getAllEmployee();System.out.println("------------学院员工------------");for (CollegeEmployee e : list1) {System.out.println(e.getId());}}}
package com.tang.designpattern.principle.demeterprinciple.after;/*** @author Dream* @date 2022/4/22 13:34*/
public class Demeter1 {public static void main(String[] args) {//创建了一个 SchoolManager 对象SchoolManager schoolManager = new SchoolManager();//输出学院的员工 id  和   学校总部的员工信息schoolManager.printAllEmployee(new CollegeManager());}
}
package com.tang.designpattern.principle.demeterprinciple.after;/*** @author Dream* @date 2022/4/22 13:35* 学校总部员工类*/
public class Employee {private String id;public void setId(String id) {this.id = id;}public String getId() {return id;}
}
package com.tang.designpattern.principle.demeterprinciple.after;import java.util.ArrayList;
import java.util.List;/*** @author Dream* @date 2022/4/22 13:36*/
public class SchoolManager {//返回学校总部的员工public List<Employee> getAllEmployee() {List<Employee> list = new ArrayList<Employee>();for (int i = 0; i < 5; i++) { //这里我们增加了 5 个员工到 listEmployee emp = new Employee();emp.setId("学校总部员工 id= " + i);list.add(emp);}return list;}//该方法完成输出学校总部和学院员工信息(id)void printAllEmployee(CollegeManager sub) {//分析问题//1. 将输出学院的员工方法,封装到 CollegeManagersub.printEmployee();//获取到学校总部员工List<Employee> list2 = this.getAllEmployee();System.out.println("------------学校总部员工------------");for (Employee e : list2) {System.out.println(e.getId());}}}

6.4、迪米特法则注意事项和细节

  1. 迪米特法则的核心是降低类之间的耦合
  2. 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系

7、合成复用原则

7.1、基本介绍

原则是尽量使用合成/聚合的方式,而不是使用继承

7.2、代码分析

package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:17*/
public class Black implements Color{@Overridepublic String color() {return "黑色";}
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:18*/
public class Car {protected Color color;public Car(Color color) {this.color = color;}public void move() {System.out.println(color.color() + "汽车移动");}
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:16*/
public interface Color {String color();
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:22*/
public class CRPTest {public static void main(String[] args) {White white = new White();Black black = new Black();Red red = new Red();Car car1 = new GasolineCar(white);Car car2 = new GasolineCar(black);Car car3 = new GasolineCar(red);Car car4 = new ElectricCar(white);Car car5 = new ElectricCar(black);Car car6 = new ElectricCar(red);car1.move();car2.move();car3.move();car4.move();car5.move();car6.move();}
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:22*/
public class ElectricCar extends Car {public ElectricCar(Color color) {super(color);}@Overridepublic void move() {System.out.println(color.color() + "电动汽车移动");}
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:19*/
public class GasolineCar extends Car {public GasolineCar(Color color) {super(color);}@Overridepublic void move() {System.out.println(color.color() + "汽油汽车移动");}
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:18*/
public class Red implements Color{@Overridepublic String color() {return "红色";}
}
package com.tang.designpattern.principle.compositereuse;/*** @author Dream* @date 2022/4/22 14:16*/
public class White implements Color{@Overridepublic String color() {return "白色";}
}

7.3、合成复用原则的实现方法

合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。

以汽车分类管理程序为例:
分析:汽车按“动力源"划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这这两种分类,其组合就很多。

第二章、23种设计模式

设计模式类型:
设计模式分为三种类型,共 23 种

  1. 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
  3. 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter 模式)、状态模式、策略模式、职责链模式(责任链模式)。
    注意:不同的书籍上对分类和名称略有差别

1.设计模式——代理模式

1.1.代理模式的基本介绍

(1)代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。

(2)被代理的对象可以是远程对象,创建开销大的对象或需要安全控制的对象
(3)代理模式有不同的形式,主要有三种 静态代理,动态代理(JDK代理,接口代理)和Cglib代理

1.2.静态代理

基本介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者是继承相同父类

应用实例

具体要求
(1)定义一个接口:ITeacherDao
(2)目标对象TeacherDao实现ITeacherDao
(3)使用静态代理方式,就需要在代理对象TeacherDaoProxy中也实现ITeacherDao
(4)调用的时候通过调用代理对象的方法来调用目标对象
(5)特别提醒:代理独享与目标对象要实现相同的接口,然会通过调用相同的方法来调用目标对象的方法。

(6)类图如下

代码示例:

package com.tang.DesignPatterns.Proxy.StaticProxy;/**   @author Dram*   @create 2021-07-19 11:23*/public interface ITeacherDao {void teach();
}
package com.tang.DesignPatterns.Proxy.StaticProxy;/**   @author Dram*   @create 2021-07-19 11:24*/
/*** 被代理对象*/
public class TeacherDaoImpl implements ITeacherDao{@Overridepublic void teach() {System.out.println("开始授课中....");}
}
package com.tang.DesignPatterns.Proxy.StaticProxy;/**   @author Dram*   @create 2021-07-19 11:24*//*** 代理对象*/
public class TeacherDaoProxy implements ITeacherDao{private ITeacherDao teacherDao;public TeacherDaoProxy(ITeacherDao teacherDao) {this.teacherDao = teacherDao;}@Overridepublic void teach() {System.out.println("开始代理中");teacherDao.teach();System.out.println("执行其他增强方法");}
}
package com.tang.DesignPatterns.Proxy.StaticProxy;/**   @author Dram*   @create 2021-07-19 11:25*/public class Client {public static void main(String[] args) {TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(new TeacherDaoImpl());teacherDaoProxy.teach();}
}

静态代理优缺点

(1)优点:在不修改目标对象的功能前提下,能通过代理对象对目标功能扩展
(2)缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很对代理类
一旦接口增加方法,目标对象与代理都要维护

1.3.动态代理

基本介绍

(1)代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理
(2)代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象
(3)动态代理也叫做:JDK代理,接口代理

JDK生产代理对象的API

(1)代理类所在包:java.long,reflect.Proxy
(2)JDK实现代理只需要使用newProXyInstance方法,但是该方法需要接收三个参数,完整的写法是:
static Object new ProxyInstance(ClassLoader loader,Class<?>[] interface,InvocationHandler h)
(3)类图如下

示例代码:

package com.tang.DesignPatterns.Proxy.DynamicProxy;/**   @author Dram*   @create 2021-07-19 11:23*/public interface ITeacherDao {void teach();
}
package com.tang.DesignPatterns.Proxy.DynamicProxy;/**   @author Dram*   @create 2021-07-19 11:24*//*** 被代理对象*/
public class TeacherDaoImpl implements ITeacherDao {@Overridepublic void teach() {System.out.println("开始授课中....");}
}
package com.tang.DesignPatterns.Proxy.DynamicProxy;/**   @author Dram*   @create 2021-07-19 14:30*/import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxyInstance(){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("JDK动态代理开始...");Object returnVal = method.invoke(target, args);System.out.println("JDK动态代理结束...");return returnVal;}});}}
package com.tang.DesignPatterns.Proxy.DynamicProxy;/**   @author Dram*   @create 2021-07-19 11:25*/import com.tang.DesignPatterns.Proxy.StaticProxy.TeacherDaoProxy;public class Client {public static void main(String[] args) {TeacherDaoImpl teacherDao = new TeacherDaoImpl();ProxyFactory proxyFactory = new ProxyFactory(teacherDao);ITeacherDao proxyInstance = (ITeacherDao) proxyFactory.getProxyInstance();proxyInstance.teach();}
}

优缺点
优点:
(1)不需要代理对象实现接口,只需要被代理对象实现接口
(2)接口方法扩展,只需维护被代理对象。不关心代理对象。代理对象是在内存中生成的。
缺点:
(1)利用反射机制。原理复杂。

1.4.Cglib代理

基本介绍

(1)静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何接口,这个时候可使用目标对象子类来实现代理。这就是Cglib代理
(2)Cglib代理也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展,有些书籍也将Cglib代理归属动态代理。
(3)Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截
(4)在AOP编程中如何选择代理模式:
1.目标对象需要实现接口,用JDK代理
2.目标对象不需要实现接口,用Cglib代理
(5)Cglib包的底层时通过使用字节码处理框架ASM来转换字节码并生成新的类
(6)类图:

代码示例

package com.tang.DesignPatterns.Proxy.Cglib;/**   @author Dram*   @create 2021-07-19 14:30*/import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class ProxyFactory implements MethodInterceptor {private Object target;public ProxyFactory(Object target) {this.target = target;}public Object getProxyInstance(){//1.创建工具类Enhancer enhancer = new Enhancer();//2.设置父类enhancer.setSuperclass(target.getClass());//3.设置回调函数enhancer.setCallback(this);//4.创建子类对象,即代理对象return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("这里可以插入执行关键代码之前的逻辑");//执行目标对象方法Object returnVal = methodProxy.invokeSuper(o, objects);System.out.println("这里可以插入执行关键代码之后的逻辑");return returnVal;}
}
package com.tang.DesignPatterns.Proxy.Cglib;/**   @author Dram*   @create 2021-07-19 11:24*//*** 被代理对象*/
public class TeacherDao{public String teach() {System.out.println(" 老师授课中  , 我是 cglib 代理,不需要实现接口 ");return "hello";}
}
 public class Client {public static void main(String[] args) {//创建目标对象TeacherDao target = new TeacherDao();//获取到代理对象,并且将目标对象传递给代理对象TeacherDao proxyInstance = (TeacherDao)new ProxyFactory(target).getProxyInstance();//执行代理对象的方法,触发 intecept  方法,从而实现 对目标对象的调用String res = proxyInstance.teach(); System.out.println("res=" + res);}
}

优点

不需要被代理对象实现接口

2.命令模式

2.1.智能生活项目需求

  1. 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作。
  2. 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个 App,分别控制,我们希望只要一个 app
    就可以控制全部智能家电。
  3. 要实现一个 app 控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给 app 调用,这时 就可以考虑使用命令模式。
  4. 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
  5. 在我们的例子中,动作的请求者是手机 app,动作的执行者是每个厂商的一个家电产品

2.2.命令模式基本介绍

  1. 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,
    我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计
  2. 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
  3. 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
  4. 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
    Invoker 是调用者(将军),Receiver 是被调用者(士兵),MyCommand 是命令,实现了 Command 接口,持有接收对象

2.3.命令模式的原理类图

  • 对原理类图的说明-即(命名模式的角色及职责)
  1. Invoker 是调用者角色
  2. Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
  3. Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
  4. ConcreteCommand: 将一个接受者对象与一个动作绑定,调用接受者相应的操作,实现 execute

2.4.代码示例

Command

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:49*/
public interface Command {//执行动作(操作)public void execute();//撤销动作(操作)public void undo();
}

LightOffCommand

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:50*/
public class LightOffCommand implements Command {// 聚合LightReceiverLightReceiver light;// 构造器public LightOffCommand(LightReceiver light) {super();this.light = light;}@Overridepublic void execute() {// 调用接收者的方法light.off();}@Overridepublic void undo() {// 调用接收者的方法light.on();}
}

LightOnCommand

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:49*/
public class LightOnCommand implements Command {//聚合LightReceiverLightReceiver light;//构造器public LightOnCommand(LightReceiver light) {super();this.light = light;}@Overridepublic void execute() {//调用接收者的方法light.on();}@Overridepublic void undo() {//调用接收者的方法light.off();}
}

LightReceiver

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:51*/
public class LightReceiver {public void on() {System.out.println(" 电灯打开了.. ");}public void off() {System.out.println(" 电灯关闭了.. ");}}

NoCommand

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:51*/
public class NoCommand implements Command {@Overridepublic void execute() {}@Overridepublic void undo() {}}

RemoteController

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:51*/
public class RemoteController {// 开 按钮的命令数组Command[] onCommands;Command[] offCommands;// 执行撤销的命令Command undoCommand;// 构造器,完成对按钮初始化public RemoteController() {onCommands = new Command[5];offCommands = new Command[5];for (int i = 0; i < 5; i++) {onCommands[i] = new NoCommand();offCommands[i] = new NoCommand();}}// 给我们的按钮设置你需要的命令public void setCommand(int no, Command onCommand, Command offCommand) {onCommands[no] = onCommand;offCommands[no] = offCommand;}// 按下开按钮public void onButtonWasPushed(int no) { // no 0// 找到你按下的开的按钮, 并调用对应方法onCommands[no].execute();// 记录这次的操作,用于撤销undoCommand = onCommands[no];}// 按下开按钮public void offButtonWasPushed(int no) { // no 0// 找到你按下的关的按钮, 并调用对应方法offCommands[no].execute();// 记录这次的操作,用于撤销undoCommand = offCommands[no];}// 按下撤销按钮public void undoButtonWasPushed() {undoCommand.undo();}
}

TVOffCommand

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:50*/
public class TVOffCommand implements Command {// 聚合TVReceiverTVReceiver tv;// 构造器public TVOffCommand(TVReceiver tv) {super();this.tv = tv;}@Overridepublic void execute() {// 调用接收者的方法tv.off();}@Overridepublic void undo() {// 调用接收者的方法tv.on();}
}

TVOnCommand

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:50*/
public class TVOnCommand implements Command {// 聚合TVReceiverTVReceiver tv;// 构造器public TVOnCommand(TVReceiver tv) {super();this.tv = tv;}@Overridepublic void execute() {// 调用接收者的方法tv.on();}@Overridepublic void undo() {// 调用接收者的方法tv.off();}
}

TVReceiver

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:51*/
public class TVReceiver {public void on() {System.out.println(" 电视机打开了.. ");}public void off() {System.out.println(" 电视机关闭了.. ");}
}

Client

package com.tang.designpattern.pattern.command;/*** @author Dream* @date 2022/9/8 10:52*/
public class Client {public static void main(String[] args) {//使用命令设计模式,完成通过遥控器,对电灯的操作//创建电灯的对象(接受者)LightReceiver lightReceiver = new LightReceiver();//创建电灯相关的开关命令LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);//需要一个遥控器RemoteController remoteController = new RemoteController();//给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作remoteController.setCommand(0, lightOnCommand, lightOffCommand);System.out.println("--------按下灯的开按钮-----------");remoteController.onButtonWasPushed(0);System.out.println("--------按下灯的关按钮-----------");remoteController.offButtonWasPushed(0);System.out.println("--------按下撤销按钮-----------");remoteController.undoButtonWasPushed();System.out.println("=========使用遥控器操作电视机==========");TVReceiver tvReceiver = new TVReceiver();TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver);TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver);//给我们的遥控器设置命令, 比如 no = 1 是电视机的开和关的操作remoteController.setCommand(1, tvOnCommand, tvOffCommand);System.out.println("--------按下电视机的开按钮-----------");remoteController.onButtonWasPushed(1);System.out.println("--------按下电视机的关按钮-----------");remoteController.offButtonWasPushed(1);System.out.println("--------按下撤销按钮-----------");remoteController.undoButtonWasPushed();}
}

2.5.代码解释说明

Command 以及实现类是一个命令的具体实现。它与接收者绑定。最终都会调用接收者的方法
RemoteController 相当于是一个遥控器。它与命令绑定。通过调用该类方法,来调用命令的execute方法来让接收者执行命令。

2.6.命令模式的注意事项和细节

  1. 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
  2. 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
  3. 容易实现对请求的撤销和重做
  4. 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
  5. 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
  6. 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发- 反馈机制

3.章职责链模式

3.1.基本介绍

  1. 职责链模式(Chain of Responsibility Pattern), 又叫 责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
  2. 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
  3. 这种类型的设计模式属于行为型模式

3.2.职责链模式的原理类图

对原理类图的说明-即(职责链模式的角色及职责)

  1. Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含义另外 Handler
  2. ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个处理者), 如果可以处理当前请求,则处理,否则就将该请求交个 后继者去处理,从而形成一个职责链
  3. Request , 含义很多属性,表示一个请求

3.3.职责链模式解决 OA 系统采购审批

  1. 应用实例要求
    编写程序完成学校 OA 系统的采购审批项目:需求采购员采购教学器材
    如果金额 小于等于 5000, 由教学主任审批如果金额 小于等于 10000, 由院长审批
    如果金额 小于等于 30000, 由副校长审批如果金额 超过 30000 以上,有校长审批
  2. 思路分析和图解(类图)
  3. 代码实现
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 13:58*/
public abstract class Approver {/*** 下一个处理者*/Approver approver;/*** 名字*/String name;public Approver(String name) {this.name = name;}//下一个处理者public void setApprover(Approver approver) {this.approver = approver;}//处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象public abstract void processRequest(PurchaseRequest purchaseRequest);}
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 14:00*/
public class Client {public static void main(String[] args) {//创建一个请求PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);//创建相关的审批人DepartmentApprover departmentApprover = new DepartmentApprover("张主任");CollegeApprover collegeApprover = new CollegeApprover("李院长");ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");//需要将各个审批级别的下一个设置好 (处理人构成环形: )departmentApprover.setApprover(collegeApprover);collegeApprover.setApprover(viceSchoolMasterApprover);viceSchoolMasterApprover.setApprover(schoolMasterApprover);schoolMasterApprover.setApprover(departmentApprover);departmentApprover.processRequest(purchaseRequest);// viceSchoolMasterApprover.processRequest(purchaseRequest);}
}
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 14:01*/
public class CollegeApprover extends Approver {public CollegeApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest purchaseRequest) {if (purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) {System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(purchaseRequest);}}
}
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 14:02*/
public class DepartmentApprover extends Approver {public DepartmentApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest purchaseRequest) {if (purchaseRequest.getPrice() <= 5000) {System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(purchaseRequest);}}
}
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 13:59*/
public class PurchaseRequest {/*** 请求类型*/private int type = 0;/*** 请求金额*/private float price = 0.0f;private int id = 0;//构造器public PurchaseRequest(int type, float price, int id) {this.type = type;this.price = price;this.id = id;}public int getType() {return type;}public float getPrice() {return price;}public int getId() {return id;}}
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 14:03*/
public class SchoolMasterApprover extends Approver {public SchoolMasterApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest purchaseRequest) {if (purchaseRequest.getPrice() > 30000) {System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(purchaseRequest);}}
}
package com.tang.designpattern.pattern.chainOfResponsibility;/*** @author Dream* @date 2022/9/13 14:03*/
public class ViceSchoolMasterApprover extends Approver {public ViceSchoolMasterApprover(String name) {super(name);}@Overridepublic void processRequest(PurchaseRequest purchaseRequest) {if (purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");} else {approver.processRequest(purchaseRequest);}}
}

3.4.职责链模式的注意事项和细节

  1. 将请求和处理分开,实现解耦,提高系统的灵活性
  2. 简化了对象,使对象不需要知道链的结构
  3. 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
  4. 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
  5. 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中 Tomcat
    对 Encoding 的处理、拦截器

设计模式——从零到一,从入门到精通相关推荐

  1. 零基础python从入门到精通 pdf-跟老齐学Python:从入门到精通PDF电子版

    跟老齐学Python:从入门到精通是一本面向初学python的人提供的最好教程,在书中作者以非常欢快的描述方式为读者讲解Python语言,以各种案例帮助你能够更简单的深入学习! 跟老齐学Python介 ...

  2. 零基础python从入门到精通 pdf-跟老齐学Python从入门到精通

    跟老齐学Python从入门到精通是一款由老齐写作的Python电子书籍.书籍讲述了零基础读者的Python入门教程,内容涵盖了Python的基础知识和初步应用,需要的赶紧下载吧! 目录: 第1季 基础 ...

  3. 零基础python从入门到精通 pdf-跟老齐学Python从入门到精通.pdf

    跟老齐学Python从入门到精通是一款由老齐写作的Python电子书籍.书籍讲述了零基础读者的Python入门教程,内容涵盖了Python的基础知识和初步应用,需要的赶紧下载吧! 目录: 第1季 基础 ...

  4. 零基础python从入门到精通 pdf-跟老齐学Python从入门到精通 电子版(pdf格式)

    跟老齐学python从入门到精通是一款由老齐写作的Python电子书籍.书籍讲述了零基础读者的Python入门教程,内容涵盖了Python的基础知识和初步应用,需要的赶紧在巴士下载站下载吧! 目录: ...

  5. 零基础python从入门到精通 pdf-100G Python从入门到精通全套资料!

    Python 究竟有多火?目前在编程语言排行榜中,Python当之无愧的成为了第一!Python已经逐渐成为所有IT技术的首选语言.几乎所有IT领域都将Python作为首选编程语言. Python崇尚 ...

  6. 零基础python从入门到精通 pdf-PYTHON从入门到精通 PDF 下载

    相关截图: 资料简介: <Python从入门到精通>从初学者角度出发,通过通俗易懂的语言.丰富多彩的实例,详细介绍了使用Python进行程序开发应该掌握的各方面技术.全书共分22章,包括初 ...

  7. 网络安全工程师需要学什么?零基础怎么从入门到精通,看这一篇就够了

    网络安全工程师需要学服务漏洞扫描.程序漏洞分析检测.权限管理.入侵和攻击分析追踪.网站渗透.病毒木马防范.计算机语言 等内容,还需要懂得网络安全产品的配置和使用. 网络安全工程师的工作职责: 主持项目 ...

  8. 网络安全工程师需要学哪些内容?零基础如何从入门到精通,看完这一篇就够了

    网络安全工程师需要学习哪些内容? 作为一名网络安全工程师,需要学习以下内容: 计算机网络和操作系统:网络安全工程师需要了解计算机网络和操作系统的基础知识,以便理解网络安全的基础原理. 网络协议和技术: ...

  9. 【零基础C++从入门到精通】(一) 走进C++

    文章目录 参考书籍 安装Visual Studio 2019 参考书籍 计算机运行程序时使用的指令是编码过的抽象的二进制序列,而程序员在开发过程中需要一种方便人们理解的高级编程语言,而C++就是这样一 ...

  10. lightroom 闪退_Lightroom从入门到精通系统课程,提升您图片后期处理的速度

    Lightroom入门到精通提升图片处理的速度摄影师必备修图效率工具 金秋9-10月我们在旅途中拍了那么多的照片成千上万张的原图急需后期处理近期看到很多影友留言说,能不能开设Lightroom这款修图 ...

最新文章

  1. 八皇后的一个回溯递归解法
  2. 科技/IT:2019 年 Q3 表现最佳和最差的企业
  3. 青岛西海岸新区将建大数据交易中心
  4. 使用@Valid进行Spring验证bindingresult 用法
  5. Linux C: 信号及异常和捕捉函数原理
  6. 使用canvas绘制小程序码
  7. 线程故事:Web应用程序中的ThreadLocal
  8. LeetCode 88. 合并两个有序数组 golang
  9. apache ab 测试 apr_socket_connect(): 由于目标机器积极拒绝 无法连接
  10. oracle sga pga mysql_oracle实例内存(SGA和PGA)调整-xin
  11. python菜鸟教程网-Python JSON
  12. 如何用计算机制作海报,用电脑怎么做海报_电脑用什么软件做海报
  13. Typora 语法结构与功能
  14. 基于安卓平台的远程医疗APP设计
  15. 这些片子你猜到结局了吗?
  16. 【python+E-prime+fNIRS】探究认知资源在情绪与认知灵活性中是否存在中介效应课题
  17. python之excel处理画图
  18. supermap三维地下管线
  19. 6.windbg-windbg环境
  20. Maven的爱恨情仇

热门文章

  1. 计算机二级有纸质的证书吗
  2. 基于 Python 的 Stacking 集成机器学习实践
  3. 基恩士 用IO模块来驱动伺服和步进定位
  4. 郑宇:多源数据融合与时空数据挖掘(转载)
  5. Systemverilog的一个牛人总结
  6. 七年级英语tapescripts翻译,七年级英语tapescripts
  7. Xposed入门教程
  8. 无人自动叉车常见升降故障及排除方法
  9. 李开复——如何平衡你的工作和家庭
  10. MySQL无法启用/etc/my.cnf配置文件,重启报错Warning: World-writable config file ‘/etc/my.cnf’ is ignored的解决方法