前言
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 stub
Vehicle2 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 {
@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");
}
}
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 {
@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");
}
}
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 {
@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");
}
}
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{
@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");
}
}
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)是指:
- 高层模块不应该依赖低层模块,二者都应该依赖其抽象
- 抽象不应该依赖细节,细节应该依赖抽象
- 依赖倒转(倒置)的中心思想是面向接口编程
- 依赖倒转原则是基于这样的设计理念:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类
- 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成
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());
}
}
方案分析
分析
- 简单,比较容易想到
- 如果我们获取的对象是 微信,短信等等,则新增类,同时 Perons 也要增加相应的接收方法
- 解决思路:引入一个抽象的接口 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{
@Override
public 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{
@Override
public String getInfo() {
return "微信信息: hello,ok";
}
}
方案二分析
引入一个抽象的接口 IReceiver表示发生信息的行为 接着Email, WeiXin 等等都可以发送信息,他们各自实现 IReceiver 接口就 ok, 这样我们就符号依赖倒转原则
3.3、依赖关系传递的三种方式和应用案例
- 接口传递
package com.tang.designpattern.principle.dependenceinversion.case3.byinterface;
/**
* @author Dream
* @date 2022/4/12 16:47
*/
public class ChangHong implements ITV{
@Override
public 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{
@Override
public void open(ITV tv) {
tv.play();
}
}
- 构造方法传递
package com.tang.designpattern.principle.dependenceinversion.case3.constructionmethod;
/**
* @author Dream
* @date 2022/4/12 16:47
*/
public class ChangHong implements ITV {
@Override
public 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;
}
@Override
public void open() {
this.tv.play();
}
}
- setter 方式传递
package com.tang.designpattern.principle.dependenceinversion.case3.bysetter;
/**
* @author Dream
* @date 2022/4/12 16:47
*/
public class ChangHong implements ITV {
@Override
public 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;
@Override
public void open() {
this.tv.play();
}
@Override
public void setTv(ITV tv) {
this.tv = tv;
}
}
3.4、依赖倒转原则的注意事项和细节
- 低层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好.
- 变量的声明类型尽量是抽象类或接口, 这样我们的变量引用和实际对象间,就存在一个缓冲层,利于程序扩展和优化
- 继承时遵循里氏替换原则
4、里氏替换原则
4.1、基本介绍
-
里氏替换原则(Liskov Substitution Principle)在 1988 年,由麻省理工学院的以为姓里的女士提出的。
-
如果对每个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。换句话说,所有引用基类的地方必须能透明地使用其子类的对象。
-
在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
-
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖 来解决问题。.
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 stub
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();
System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出 11-3
System.out.println("1-8=" + b.func1(1, 8));// 1-8 System.out.println("11+3+9=" + b.func2(11, 3));
}
}
4.3、解决方案
- 我们发现原来运行正常的相减功能发生了错误。原因就是类 B 无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候
- 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替.
- 改进方案
- 代码实现
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+3
System.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、基本介绍
-
开闭原则(Open Closed Principle)是编程中最基础、最重要的设计原则
-
一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。
-
当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。
-
编程中遵循其它原则,以及使用设计模式的目的就是遵循开闭原则。
5.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;
}
}
- 优缺点
- 优点是比较好理解,简单易操作。
- 缺点是违反了设计模式的 ocp 原则,即对扩展开放(提供方),对修改关闭(使用方)。即当我们给类增加新功能的时候,尽量不修改代码,或者尽可能少修改代码.
- 比如我们这时要新增加一个图形种类 三角形,我们需要做如下修改,修改的地方较多
- 改进的思路分析
-
思路:把创建 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;
}
@Override
public 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;
}
@Override
public 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;
}
@Override
public void draw() {
// TODO Auto-generated method stub
System.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;
}
@Override
public void draw() {
System.out.println(" 绘制三角形 ");
}
}
6、迪米特法则
6.1、基本介绍
-
一个对象应该对其他对象保持最少的了解
-
类与类关系越密切,耦合度越大
-
迪米特法则(Demeter Principle)又叫最少知道原则,即一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类不管多么复杂,都尽量将逻辑封装在类的内部。对外除了提供的 public 方法,不对外泄露任何信息
-
迪米特法则还有个更简单的定义:只与直接的朋友通信
-
直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖,关联,组合,聚合等。其中,我们称出现成员变量,方法参数,方法返回值中的类为直接的朋友,而出现在局部变量中的类不是直接的朋友。也就是说,陌生的类最好不要以局部变量的形式出现在类的内部。
6.2、应用实例
- 有一个学校,下属有各个学院和总部,现要求打印出学校总部员工 ID 和学院员工的 id
- 编程实现上面的功能, 看代码演示
- 代码演示
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 个员工到 list
CollegeEmployee 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 个员工到 list
Employee 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、应用实例改进
- 前面设计的问题在于 SchoolManager 中,CollegeEmployee 类并不是 SchoolManager 类的直接朋友 (分析)
- 按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合
- 对代码按照迪米特法则 进行改进. (看老师演示)
- 代码演示
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 个员工到 list
CollegeEmployee 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 个员工到 list
Employee emp = new Employee();
emp.setId("学校总部员工 id= " + i);
list.add(emp);
}
return list;
}
//该方法完成输出学校总部和学院员工信息(id)
void printAllEmployee(CollegeManager sub) {
//分析问题
//1. 将输出学院的员工方法,封装到 CollegeManager
sub.printEmployee();
//获取到学校总部员工
List<Employee> list2 = this.getAllEmployee();
System.out.println("------------学校总部员工------------");
for (Employee e : list2) {
System.out.println(e.getId());
}
}
}
6.4、迪米特法则注意事项和细节
- 迪米特法则的核心是降低类之间的耦合
- 但是注意:由于每个类都减少了不必要的依赖,因此迪米特法则只是要求降低类间(对象间)耦合关系, 并不是要求完全没有依赖关系
7、合成复用原则
7.1、基本介绍
原则是尽量使用合成/聚合的方式,而不是使用继承
7.2、代码分析
package com.tang.designpattern.principle.compositereuse;
/**
* @author Dream
* @date 2022/4/22 14:17
*/
public class Black implements Color{
@Override
public 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);
}
@Override
public 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);
}
@Override
public 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{
@Override
public String color() {
return "红色";
}
}
package com.tang.designpattern.principle.compositereuse;
/**
* @author Dream
* @date 2022/4/22 14:16
*/
public class White implements Color{
@Override
public String color() {
return "白色";
}
}
7.3、合成复用原则的实现方法
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
以汽车分类管理程序为例:
分析:汽车按“动力源"划分可分为汽油汽车、电动汽车等;按“颜色”划分可分为白色汽车、黑色汽车和红色汽车等。如果同时考虑这这两种分类,其组合就很多。
第二章、23种设计模式
设计模式类型:
设计模式分为三种类型,共 23 种
- 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式。
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
- 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(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{
@Override
public 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;
}
@Override
public 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 {
@Override
public 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() {
@Override
public 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();
}
@Override
public 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.智能生活项目需求
- 我们买了一套智能家电,有照明灯、风扇、冰箱、洗衣机,我们只要在手机上安装 app 就可以控制对这些家电工作。
- 这些智能家电来自不同的厂家,我们不想针对每一种家电都安装一个 App,分别控制,我们希望只要一个 app
就可以控制全部智能家电。 - 要实现一个 app 控制所有智能家电的需要,则每个智能家电厂家都要提供一个统一的接口给 app 调用,这时 就可以考虑使用命令模式。
- 命令模式可将“动作的请求者”从“动作的执行者”对象中解耦出来.
- 在我们的例子中,动作的请求者是手机 app,动作的执行者是每个厂商的一个家电产品
2.2.命令模式基本介绍
- 命令模式(Command Pattern):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,
我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计 - 命名模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命名模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命名),同时命令模式也支持可撤销的操作。
- 通俗易懂的理解:将军发布命令,士兵去执行。其中有几个角色:将军(命令发布者)、士兵(命令的具体执行者)、命令(连接将军和士兵)。
Invoker 是调用者(将军),Receiver 是被调用者(士兵),MyCommand 是命令,实现了 Command 接口,持有接收对象
2.3.命令模式的原理类图
- 对原理类图的说明-即(命名模式的角色及职责)
- Invoker 是调用者角色
- Command: 是命令角色,需要执行的所有命令都在这里,可以是接口或抽象类
- Receiver: 接受者角色,知道如何实施和执行一个请求相关的操作
- 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 {
// 聚合LightReceiver
LightReceiver light;
// 构造器
public LightOffCommand(LightReceiver light) {
super();
this.light = light;
}
@Override
public void execute() {
// 调用接收者的方法
light.off();
}
@Override
public void undo() {
// 调用接收者的方法
light.on();
}
}
LightOnCommand
package com.tang.designpattern.pattern.command;
/**
* @author Dream
* @date 2022/9/8 10:49
*/
public class LightOnCommand implements Command {
//聚合LightReceiver
LightReceiver light;
//构造器
public LightOnCommand(LightReceiver light) {
super();
this.light = light;
}
@Override
public void execute() {
//调用接收者的方法
light.on();
}
@Override
public 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 {
@Override
public void execute() {
}
@Override
public 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 {
// 聚合TVReceiver
TVReceiver tv;
// 构造器
public TVOffCommand(TVReceiver tv) {
super();
this.tv = tv;
}
@Override
public void execute() {
// 调用接收者的方法
tv.off();
}
@Override
public void undo() {
// 调用接收者的方法
tv.on();
}
}
TVOnCommand
package com.tang.designpattern.pattern.command;
/**
* @author Dream
* @date 2022/9/8 10:50
*/
public class TVOnCommand implements Command {
// 聚合TVReceiver
TVReceiver tv;
// 构造器
public TVOnCommand(TVReceiver tv) {
super();
this.tv = tv;
}
@Override
public void execute() {
// 调用接收者的方法
tv.on();
}
@Override
public 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.命令模式的注意事项和细节
- 将发起请求的对象与执行请求的对象解耦。发起请求的对象是调用者,调用者只要调用命令对象的 execute()方法就可以让接收者工作,而不必知道具体的接收者对象是谁、是如何实现的,命令对象会负责让接收者执行请求的动作,也就是说:”请求发起者”和“请求执行者”之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用。
- 容易设计一个命令队列。只要把命令对象放到列队,就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式不足:可能导致某些系统有过多的具体命令类,增加了系统的复杂度,这点在在使用的时候要注意
- 空命令也是一种设计模式,它为我们省去了判空的操作。在上面的实例中,如果没有用空命令,我们每按下一个按键都要判空,这给我们编码带来一定的麻烦。
- 命令模式经典的应用场景:界面的一个按钮都是一条命令、模拟 CMD(DOS 命令)订单的撤销/恢复、触发- 反馈机制
3.章职责链模式
3.1.基本介绍
- 职责链模式(Chain of Responsibility Pattern), 又叫 责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
- 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
- 这种类型的设计模式属于行为型模式
3.2.职责链模式的原理类图
对原理类图的说明-即(职责链模式的角色及职责)
- Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含义另外 Handler
- ConcreteHandlerA , B 是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个处理者), 如果可以处理当前请求,则处理,否则就将该请求交个 后继者去处理,从而形成一个职责链
- Request , 含义很多属性,表示一个请求
3.3.职责链模式解决 OA 系统采购审批
- 应用实例要求
编写程序完成学校 OA 系统的采购审批项目:需求采购员采购教学器材
如果金额 小于等于 5000, 由教学主任审批如果金额 小于等于 10000, 由院长审批
如果金额 小于等于 30000, 由副校长审批如果金额 超过 30000 以上,有校长审批 - 思路分析和图解(类图)
- 代码实现
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);
}
@Override
public 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);
}
@Override
public 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);
}
@Override
public 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);
}
@Override
public 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.职责链模式的注意事项和细节
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中 Tomcat
对 Encoding 的处理、拦截器