设计模式详解

一、介绍

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式,就是为了一套对写代码的写法经验的总结,怎么写效率更高,可读性更强,上手更简单。简单的抽象形容,就是摩天大楼和简易茅草屋的区别,前者使用更大更强的地基,有更加强大的设计蓝图,所以才能高耸入云;而茅草屋,只是简单的材料拼接而成的房子,来一场风可能就塌了。所以程序也是一样,要想做到和摩天大楼一样的程序,设计模式是必不可少的。

image-20220108193404306

大部分人写代码应该都是茅草屋吧,我也是一样。哈哈哈,在写公司的代码时,只想着把功能做出来,常常就忽略的架构上的东西。

本文的绝大部分,都源自于哔哩哔哩尚硅谷学堂,谢谢他们的教程

1)GOF(Gang of Four)

GOF,我们通常叫四人帮。在 1994 年,由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 的书,该书首次提到了软件开发中设计模式的概念。

一想到如今的我还得学近30年前的知识,我就觉得我好嫩,哈哈哈。

2)七大原则

在四人帮的著作中,通过以下7种设计原则,为我们展现了23种设计模式。这7大原则分别是

  1. 单一职责原则

  2. 接口隔离原则

  3. 依赖倒转原则

  4. 里氏替换原则

  5. 开闭原则

  6. 迪米特原则

  7. 合成复用原则

设计模式到底有几大原则,有人说是五种,有人说6种,而本文列举了七种。对比了一下其他的说法,发现了可以进行组合。具体组合成什么,还请继续往下看

按五大原则划分:1、(2、6)、(3、4)、5、7

按六大原则划分:1、(2、6)、3、4、5、7

听不懂?没关系,一会通俗的讲解下

2.1)单一职责原则

简单的来说,就是一个类只负责一项职责。如果一个类A负责了两项不同的职责,则要将类的细粒度分解为A1,A2。

常见的场景不好说,比如说一个UserMapper,只进行处理用户相关的职责,而不能处理其他的功能。

别问为什么要这样,问就是强迫症。

但原则是死的,人是活的,自己怎么舒服怎么来,让别人恶心去吧。额,面试的时候可别这样说,还有不要被自己多年前的代码恶心到就行。

2.2)接口隔离原则

一个类它所实现的接口,这个接口细粒度一定是最低的。这句话很是难懂,使用一个图解来进行描述

image-20220108213934902

简单的图解,上述写法就违背了接口隔离原则,一个接口被类实现,却没有完全实现其接口内的方法。接口隔离原则,一个接口的细粒度需要拉到最低,仅看类需要完全实现的方法,不然不用添加到接口方法中去。

那么上述的代码要怎么改呢,既然类B只需要实现方法01和方法02,我们就创建一个接口给它,同样对应类C;

修改过后的关系

image-20220108214954153

2.3)依赖倒转原则

依赖倒转原则,有的翻译又叫依赖倒置原则。本质上他是对面向对象的使用,必须要使用上接口进行编程,将具体的对象抽象化

在Java中,将对象进行抽象成接口或者抽象类。接口只需要定义规范,具体的实现交给具体的类。我们在进行使用时,都通过对抽象接口或者抽象类进行使用。如此一来,也就实现了高内聚,低耦合的效果。

下面写一段测试代码,效果是一个人接受到邮件,展示一下没有用到依赖倒转原则的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Test {

public static void main(String[] args) {
Person banmoon = new Person("半月");
banmoon.receive(new Email());
}

}

class Email{
public String getInfo(){
return "邮件";
}
}

class Person{
private String name;

public Person(String name) {
this.name = name;
}

public void receive(Email email){
System.out.println(this.name + "收到了" + email.getInfo());
}
}

执行结果就不贴了,挺简单的代码。简单的基础上,进行扩展就有一定的问题了。倘若这个人接受到的不是邮件,而是其他的短信或者微信呢,我们不得再重载receive方法?确实是一条思路,但路子走窄了,这就违背了依赖倒转原则。

我们应该规定一个Email,以及其他消息的抽象接口,看下列优化结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Test {

public static void main(String[] args) {
Person banmoon = new Person("半月");
banmoon.receive(new Email());
banmoon.receive(new Wechat());
}

}

interface Something{
String getInfo();
}

class Email implements Something{
public String getInfo(){
return "邮件";
}
}

class Wechat implements Something{
public String getInfo(){
return "微信消息";
}
}

class Person{
private String name;

public Person(String name) {
this.name = name;
}

public void receive(Something sth){
System.out.println(this.name + "收到了" + sth.getInfo());
}
}

执行结果,在进行改进的过程中,原本的main方法都不用改动,效果是嘎嘎的好,后续扩展就很舒服了。

image-20220108231357159

2.4)里氏替换原则

里氏替换原则,简单的来说是对面向对象语言的继承特性做出的限制。它规定以下几点

  1. 一个类最好来自与继承抽象类或者实现接口,而不是继承一个具体类。具体的类已经是一个完整的个体,不应该进行继承。

  2. 如果非要继承,尽量不要重写父类的方法。否则在使用时,会造成方法调用不明确的问题

总的来说,就是不要用继承,尽量不要用。继承会使得类之间增加耦合性,当想修改父类的方法逻辑时,必须要考虑到它的所有子类

2.5)开闭原则

开闭原则,是良好程序架构中最基础最重要的设计原则。

简单的来说,对程序的扩展开启,对修改关闭。比如说一个类,当需要新增一个功能时,最好不要影响到原有的方法和属性。原有的功能,能跑就行,只做增强,不做修改,这就是开闭原则。我马上能想到的就是AOP技术,就是对开闭原则最好的诠释。

像其他的原则,本质上都是在遵循开闭原则。

2.6)迪米特原则

迪米特原则,又叫最少知道原则。

它指的是类之间的依赖关系,应当做到最小,细粒度要拉到最小,以减少类之间的耦合度 。

  1. 只和直接朋友类发生依赖

  2. 不要依赖其他陌生的类

直接朋友,当一个类中的使用到其他类,出现的位置在类成员变量,方法参数,方法返回值时。这就说明两个类是直接朋友。

相反的,只在代码块或者方法中出现的类,我们称为陌生的类

2.7)合成复用原则

合成复用原则,要尽量使用对象聚合和组合,而不是继承关系达到软件复用的目的。

举个例子,类A中的方法1需要使用到类B的方法1和方法2,不应该使用继承,使类A继承类B。这就违背了合成复用原则。

我们可以使用聚合或组合的方式,来进行使用

  • 聚合:将类B作为类A的成员属性,再创建一个set方法,在使用类A的方法1前,先使用set方法将类B进行设置

  • 组合:将类B作为类A的成员属性,通过类A的构造方法进行实例化

二、设计模式介绍

上面的章节介绍了设计模式的由来,和它的七大设计原则。那么在设计模式的书中,一共列举了23种设计模式,可以分为三大类

  1. 创建型模式:单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式

  2. 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

  3. 行为型模式:模板方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、责任链模式

由于设计模式是针对软件程序的代码做出了规范,所以它并不局限于一种语言,本文列举的是Java语言。

其次,设计模式有很多,上文中列举的23种,是前人总结经验得到的,一般都能覆盖程序出现的设计问题。如果您有什么新的设计模式,请让我学习学习。

三、创建型设计模式

1)单例模式

单例模式,相信大家这这个模式都不陌生,可以说经常听到了。

不过这里还是要讲解一下他的概念,它主要体现在一个类只能有一个实例。

如果其他类想要使用这个类,对不起,你不能直接构造出我的实例,而是只能获取到我有且唯一的一个实例进行使用。

单例模式也可以分为很多种,分别是

  1. 饿汉式(静态常量)

  2. 饿汉式(静态代码块)

  3. 懒汉式(线程不安全)

  4. 懒汉式(线程安全,同步方法)

  5. 懒汉式(线程安全,同步代码块)

  6. 双重检查

  7. 静态内部类

  8. 枚举

单例模式的使用场景

  • 需要频繁进行创建和销毁,且创建对象时耗时过多或耗费资源过多(重量级对象)
  • 经常用到的工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

1.1)饿汉式(静态常量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.banmoon.singleton;

/**
* 饿汉式(静态常量)
*/
public class Singleton01 {

private Singleton01() {
}

private final static Singleton01 instance = new Singleton01();

public static Singleton01 getInstance(){
return instance;
}

}

步骤

  1. 将构造器私有化,这样其他类就不能通过new来创建该类的实例了

  2. 写静态常量,并直接给予一个实例

  3. 在写一个静态方法,获取上面的静态常量实例

这种方法,是利用了类的加载机制,在Java启动,类装载的时候,进行的实例化,从而达到单例的效果。

在程序中,这个实例会一直伴随着我们,不会被回收,直到服务死亡。如果我们一直没有去使用它,这个实例也一直会存在,从而会造成一定资源的浪费。

1.2)饿汉式(静态代码块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.singleton;

/**
* 饿汉式(静态代码块)
*/
public class Singleton02 {

private Singleton02() {
}

private static Singleton02 instance;

static {
instance = new Singleton02();
}

public static Singleton02 getInstance(){
return instance;
}

}

对比第一种饿汉式,没有直接指定实例,而是在静态代码块中进行赋值。

1.3)懒汉式(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.singleton;

/**
* 懒汉式(线程不安全)
*/
public class Singleton03 {

private Singleton03() {
}

private static Singleton03 instance;

public static Singleton03 getInstance(){
if(instance==null)
instance = new Singleton03();
return instance;
}

}

对比前面的饿汉式,这种方法避免了资源的浪费。用的时候才会判断,没有则会创建。

如此一来,便达到了懒加载的效果。但同时也会带来新的问题,那就是线程不安全。试想一下,如果同时有很多线程获取实例,还没有来得及创建实例,别的线程就又通过了判断,需要创建实例了。

所以在使用这种方式前,只能提前确定当前使用的是否为多线程。

1.4)懒汉式(线程安全,同步方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.singleton;

/**
* 懒汉式(线程安全,同步方法)
*/
public class Singleton04 {

private Singleton04() {
}

private static Singleton04 instance;

public static synchronized Singleton04 getInstance(){
if(instance==null)
instance = new Singleton04();
return instance;
}

}

和上面的懒汉式的区别主要是添加了synchronized,以此来保证线程的安全。

1.5)懒汉式(线程安全,同步代码块)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.singleton;

/**
* 懒汉式(线程安全,同步代码块)
*/
public class Singleton05 {

private Singleton05() {
}

private static Singleton05 instance;

public static Singleton05 getInstance(){
synchronized(Singleton05.class){
if(instance==null)
instance = new Singleton05();
}
return instance;
}

}

和上面的懒汉式对比,仅仅只是将synchronized修饰位置变化一下,对性能的提升不大。

大家都知道,线程安全问题解决,就是需要建立在效率的牺牲上。但无论怎么说,都要比单线程的效率来得高。

1.6)双重检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.banmoon.singleton;

/**
* 双重检查
*/
public class Singleton06 {

private Singleton06() {
}

private static volatile Singleton06 instance;

public static Singleton06 getInstance(){
if(instance==null){
synchronized(Singleton06.class){
if(instance==null)
instance = new Singleton06();
}
}
return instance;
}

}

双重检查,就是对实例判断了两次,并有volatile关键字保证了实例的可见性。相当于对饿汉式(同步代码块)的再度升级版。

这种方式推荐在项目中使用,既有懒加载,线程安全,同时它的效率也相对较高。

1.7)静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.singleton;

/**
* 静态内部类
*/
public class Singleton07 {

private Singleton07() {
}

private static class SingletonInstance{
private static final Singleton07 instance = new Singleton07();
}

public static Singleton07 getInstance(){
return SingletonInstance.instance;
}

}

这种方式使用了静态内部类的方法,从而达到懒加载的效果。

怎么会懒加载呢,这里利用了静态内部类的加载特性,当JVM装载Singleton07类时,其内部类并不会进行装载,也就是静态内部类的静态变量还没有初始化。直到getInstance被调用去获取内部类的静态变量时,内部类才进行初始化。

同时,这个静态内部类的装载过程是线程安全的。

这种方式的单例一定得要记住,面试官问你的时候,就可以把面试官往JVM的知识上面引。

1.8)枚举

1
2
3
4
5
6
7
8
9
10
package com.banmoon.singleton;

/**
* 枚举
*/
public enum Singleton08 {

INSTANCE();

}

借助了jdk1.5添加的枚举来实现单例模式,这里只进行编写一个实例,在使用的时候直接取这个实例。这种方式,可以避免线程安全的问题,而且还能防止反序列化时重新创建新对象的问题。

2)工厂模式

当我们想创建一个对象的实例时,我们通常会使用new进行创建,这没什么问题,大多数情况下当然可以这样干。

但如果这个对象的创建比较复杂,需要进行一系列的判断后才知道需要创建哪个对象。一次两次还好,多次创建肯定很烦人,干脆我们把进行创建的那段逻辑代码进行封装,需要获取新的对象时,只需要调用那段封装的代码就可以获取到对象。

所以,工厂模式就来了。它里面封装了一些创建对象的具体细节,外部程序不需要知道,只要自己使用这个工厂类可以获取到自己需要的对象就可以了。

2.1)简单工厂模式

简单工厂模式,又称为静态工厂模式。其原因是内部的创建是固定的,外部需要什么对象,我生产什么对象。

现有一家面馆,里面售卖牛肉面,鸡蛋面,杂酱面等,那我们将如何使用简单工厂模式获取这些实例呢。

首先我们先写出这三个类,我们用一个抽象类来表示面食,让这三个类去继承它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.banmoon.factory.noodle;

/**
* 面食抽象类
*/
public abstract class Noodle {

private String name;

private String area;

public Noodle(String name) {
this.name = name;
this.area = "";
}

public void setArea(String area) {
this.area = area;
}

public String getName() {
return area+name;
}
}
1
2
3
4
5
6
7
8
9
10
package com.banmoon.factory.noodle;

/**
* 牛肉面
*/
public class BeefNoodle extends Noodle{
public BeefNoodle() {
super("牛肉面");
}
}
1
2
3
4
5
6
7
8
9
10
package com.banmoon.factory.noodle;

/**
* 鸡蛋面
*/
public class EggNoodle extends Noodle{
public EggNoodle() {
super("鸡蛋面");
}
}
1
2
3
4
5
6
7
8
9
10
11
package com.banmoon.factory.noodle;

/**
* 杂酱面
*/
public class MeatSauceNoodle extends Noodle{
public MeatSauceNoodle() {
super("杂酱面");
}

}

面食的类已经写完,我们接下来编写工厂方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.factory.simple;

/**
* 面食工厂
*/
public class NoodleFactory {

public static Noodle getNoodle(int type){
if(type==1001)
return new BeefNoodle();
if(type==1002)
return new EggNoodle();
if(type==1003)
return new MeatSauceNoodle();
System.out.println("未知的面食");
return null;
}

}

就这就这?对的没错,这段代码就是我们封装进工厂中的繁琐的判断逻辑,通过判断传入的编号,获取面食。

简单写个main方法,进行使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.banmoon;

import com.banmoon.factory.noodle.Noodle;
import com.banmoon.factory.simple.NoodleFactory;

public class Main {

public static void main(String[] args) {
Noodle noodle1 = NoodleFactory.getNoodle(1001);
Noodle noodle2 = NoodleFactory.getNoodle(1002);
Noodle noodle3 = NoodleFactory.getNoodle(1003);
System.out.println(noodle1.getName());
System.out.println(noodle2.getName());
System.out.println(noodle3.getName());
}

}

查看执行结果,这里小偷一个懒,获取到的实例需要做判空处理。工厂不可能什么面食都有的,你输入的编号很可能会返回一个null

image-20220111235725912

为什么如此简单的判断逻辑,需要进行封装?

因为我这里仅仅只是为了方便举例,在实际的项目中,一个对象的创建远比这要来的复杂。当你只创建一次,那当然按照怎么舒服怎么来。但一旦同样的判断逻辑出现,我建议你封装成工厂进行创建对象。

2.2)工厂方法模式

上面介绍了简单工厂模式,这里介绍一下工厂方法模式。

面食的几个类就沿用之前的就好,这里只需要示范下工厂方法模式的写法就好,我们需要一个工厂接口或者工厂抽象类,让其它的子类工厂来实现继承它。

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.factory.method;

import com.banmoon.factory.noodle.Noodle;

/**
* 抽象工厂类
*/
public abstract class AbstractNoodleFactory {

abstract Noodle getNoodle();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.factory.method;

import com.banmoon.factory.noodle.BeefNoodle;
import com.banmoon.factory.noodle.Noodle;

/**
* 牛肉面工厂类
*/
public class BeefNoodleFactory extends AbstractNoodleFactory{
@Override
Noodle getNoodle() {
return new BeefNoodle();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.factory.method;

import com.banmoon.factory.noodle.EggNoodle;
import com.banmoon.factory.noodle.Noodle;

/**
* 鸡蛋面工厂类
*/
public class EggNoodleFactory extends AbstractNoodleFactory{
@Override
Noodle getNoodle() {
return new EggNoodle();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.factory.method;

import com.banmoon.factory.noodle.MeatSauceNoodle;
import com.banmoon.factory.noodle.Noodle;

/**
* 杂酱面工厂类
*/
public class MeatSauceNoodleFactory extends AbstractNoodleFactory{
@Override
Noodle getNoodle() {
return new MeatSauceNoodle();
}
}

可以看到一个子类工厂只对应生产一种面,在获取面食的时候得到对应的工厂能可以获取到对应的实例了。

好处在于,我们不用去修改原有的代码了。如果又有一种新的炒面,泡面等,只需要再进行添加类即可,对原本的程序代码侵入性几乎为零。

缺点也有,这种方法造成了类爆炸的问题,这也是Java语言被人诟病的原因之一。

3)抽象工厂模式

抽象工厂模式,是上面工厂模式的组合,就是将简单工厂和工厂方法组合弄出了一个更高级的工厂。

这个更高级的工厂就是抽象工厂,它下面有不同的实现工厂,每一个实现工厂都负责生产自己的实例对象。

在这里我们添加一种汤抽象类,并且有两个子类继承于它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.banmoon.factory.soup;

/**
* 汤抽象类
*/
public abstract class Soup {

private String name;

private String area;

public Soup(String name) {
this.name = name;
this.area = "";
}

public void setArea(String area) {
this.area = area;
}

public String getName() {
return area+name;
}
}
1
2
3
4
5
6
7
8
9
10
package com.banmoon.factory.soup;

/**
* 蛋花汤
*/
public class EggDropSoup extends Soup{
public EggDropSoup(String name) {
super("蛋花汤");
}
}
1
2
3
4
5
6
7
8
9
10
package com.banmoon.factory.soup;

/**
* 鲜鱼汤
*/
public class FishSoup extends Soup{
public FishSoup(String name) {
super("鲜鱼汤");
}
}

关于汤的类创建完毕,接下来还要创建抽象工厂,这次我们使用接口定义抽象工厂

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.factory.abs;

import com.banmoon.factory.noodle.Noodle;
import com.banmoon.factory.soup.Soup;

public interface AbstractFactory {

Noodle createNoodle(int type);

Soup createSoup(int type);

}

以及它的两个子类,具体的工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.banmoon.factory.abs;

import com.banmoon.factory.noodle.BeefNoodle;
import com.banmoon.factory.noodle.EggNoodle;
import com.banmoon.factory.noodle.MeatSauceNoodle;
import com.banmoon.factory.noodle.Noodle;
import com.banmoon.factory.soup.EggDropSoup;
import com.banmoon.factory.soup.FishSoup;
import com.banmoon.factory.soup.Soup;

public class BJShopFactory implements AbstractFactory{

@Override
public Noodle createNoodle(int type) {
Noodle noodle = null;
if(type==1001)
noodle = new BeefNoodle();
else if(type==1002)
noodle = new EggNoodle();
else if(type==1003)
noodle = new MeatSauceNoodle();

if(noodle==null)
System.out.println("未知的面食");
else
noodle.setArea("北京");
return noodle;

}

@Override
public Soup createSoup(int type) {
Soup soup = null;
if(type==2001)
soup = new EggDropSoup();
else if(type==2002)
soup = new FishSoup();

if(soup==null)
System.out.println("未知的汤");
else
soup.setArea("北京");
return soup;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.banmoon.factory.abs;

import com.banmoon.factory.noodle.BeefNoodle;
import com.banmoon.factory.noodle.EggNoodle;
import com.banmoon.factory.noodle.MeatSauceNoodle;
import com.banmoon.factory.noodle.Noodle;
import com.banmoon.factory.soup.EggDropSoup;
import com.banmoon.factory.soup.FishSoup;
import com.banmoon.factory.soup.Soup;

public class GZShopFactory implements AbstractFactory{

@Override
public Noodle createNoodle(int type) {
Noodle noodle = null;
if(type==1001)
noodle = new BeefNoodle();
else if(type==1002)
noodle = new EggNoodle();
else if(type==1003)
noodle = new MeatSauceNoodle();

if(noodle==null)
System.out.println("未知的面食");
else
noodle.setArea("广州");
return noodle;

}

@Override
public Soup createSoup(int type) {
Soup soup = null;
if(type==2001)
soup = new EggDropSoup();
else if(type==2002)
soup = new FishSoup();

if(soup==null)
System.out.println("未知的汤");
else
soup.setArea("广州");
return soup;
}
}

简单获取一种工厂,并得到它生产的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon;

import com.banmoon.factory.abs.AbstractFactory;
import com.banmoon.factory.abs.GZShopFactory;
import com.banmoon.factory.noodle.Noodle;
import com.banmoon.factory.soup.Soup;

public class Main {

public static void main(String[] args) {
AbstractFactory factory = new GZShopFactory();
Noodle noodle = factory.createNoodle(1001);
Soup soup = factory.createSoup(2001);
System.out.println(noodle.getName());
System.out.println(soup.getName());
}

}

image-20220113223051902

工厂模式并不算复杂,难的是如何结合业务,造出一个高度复用的轮子。

大家都可以去看看优秀的工厂案例,分析一下他们使用的是哪种工厂模式。

LoggerFactoryBeanFactoryjava.util.Calendar#createCalendar

4)原型模式

知道克隆羊多莉吗,这是从两只羊的细胞克隆出来的羊,通过现代工程创造出来的绵羊,也是世界之初第一个成功克隆的哺乳动物。

那么在程序的世界里,对象的克隆就显得十分重要了。简单看下面这个类,目前我有一个实例,如何快速的克隆这个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.banmoon.prototype;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Sheep {

private String name;

private int age;

private String color;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.banmoon;

import com.banmoon.prototype.Sheep;

public class Main {

public static void main(String[] args) {
Sheep sheep = new Sheep("多莉", 6, "白色");
System.out.println(sheep);
System.out.println("=========== 分割线 ===========");
// 克隆
Sheep sheep1 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep2 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep3 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
Sheep sheep4 = new Sheep(sheep.getName(), sheep.getAge(), sheep.getColor());
System.out.println(sheep1);
System.out.println(sheep2);
System.out.println(sheep3);
System.out.println(sheep4);
}

}

查看结果,属性完全一致,克隆成功
image-20220114213840052

但这样是有问题的,万一这羊突然增加了一个属性,我们每一个克隆的代码就都要进行修改,这明显违背的开闭原则。

4.1)浅拷贝

我们可以这样改进,修改羊实现Cloneable,调用clone方法即可,这是java为我们准备好的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.prototype;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Sheep implements Cloneable{

private String name;

private int age;

private String color;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.prototype;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Sheep implements Cloneable{

private String name;

private int age;

private String color;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

执行结果,这样就可以达到克隆的效果了

image-20220114215100480

那么,我们在添加一个新的类,对这个农场主进行克隆会怎么样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.prototype;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Estanciero implements Cloneable{

private String name;

private List<Sheep> sheepList;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.prototype;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;

public class PrototypeMain {

public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("多莉", 6, "白色");
Estanciero estanciero = new Estanciero("农场主A", CollUtil.newArrayList(sheep));
System.out.println(estanciero);
System.out.println("=========== 分割线 ===========");

Estanciero estanciero1 = (Estanciero) estanciero.clone();
System.out.println(estanciero1);
System.out.println(StrUtil.format("农场主是否一致:{},羊是否一致:{}",
estanciero==estanciero1, estanciero.getSheepList()==estanciero1.getSheepList()));
}

}

执行结果,发现了克隆出来的农场主是新的实例,但它的引用对象还是指向了原来的空间,这就是浅拷贝。

image-20220114223159562

4.2)套娃型深拷贝

套娃型深拷贝?为什么这么叫呢,这种方法就是将重写clone方法,自己加上引用对象的克隆。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.banmoon.prototype;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Estanciero implements Cloneable{

private String name;

private List<Sheep> sheepList;

@Override
protected Object clone() throws CloneNotSupportedException {
Estanciero estanciero = (Estanciero) super.clone();
if(estanciero.getSheepList()!=null){
ArrayList<Sheep> list = new ArrayList<>();
estanciero.getSheepList().forEach(sheep -> list.add(sheep));
estanciero.setSheepList(list);
}
return estanciero;
}
}

重新执行测试结果,你也可以将list里面的羊拿出来做对比,克隆出来的引用都是新的对象。

image-20220115181356315

4.3)序列化深拷贝

上面套娃型深拷贝有个弊端,即类中所有引用的对象都要像浅拷贝那样,实现Cloneable接口。如果引用的对象还有引用,那么恭喜你,你要套娃去写Cloneable 了。

这上面的就是它的弊端,所以序列化深拷贝来了。

我们将EstancieroSheep类实现Serializable序列化接口,再写一个序列化反序列化克隆的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.banmoon.prototype;

import cn.hutool.core.io.IoUtil;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Estanciero implements Cloneable, Serializable {

private String name;

private List<Sheep> sheepList;

@Override
protected Object clone() throws CloneNotSupportedException {
Estanciero estanciero = (Estanciero) super.clone();
if(estanciero.getSheepList()!=null){
ArrayList<Sheep> list = new ArrayList<>();
estanciero.getSheepList().forEach(sheep -> list.add(sheep));
estanciero.setSheepList(list);
}
return estanciero;
}

public Estanciero cloneBySerializable(){
// 创建输出流
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
// 创建输入流
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;

try {
// 序列化当前对象
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);

// 反序列化
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
Estanciero copyEstanciero = (Estanciero) ois.readObject();

return copyEstanciero;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 关闭流
IoUtil.close(bos);
IoUtil.close(oos);
IoUtil.close(bis);
IoUtil.close(ois);
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.prototype;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;

public class PrototypeMain {

public static void main(String[] args) throws CloneNotSupportedException {
Sheep sheep = new Sheep("多莉", 6, "白色");
Estanciero estanciero = new Estanciero("农场主A", CollUtil.newArrayList(sheep));
System.out.println(estanciero);
System.out.println("=========== 分割线 ===========");

// Estanciero estanciero1 = (Estanciero) estanciero.clone();
Estanciero estanciero1 = estanciero.cloneBySerializable();
System.out.println(estanciero1);
System.out.println(StrUtil.format("农场主是否一致:{},羊是否一致:{}",
estanciero==estanciero1, estanciero.getSheepList()==estanciero1.getSheepList()));
}

}

执行结果,这种方法也可以实现深拷贝,也是比较推荐的方法

image-20220115191954678

4.4)使用第三方工具包

上面的原型只做了解,讲真我不喜欢用,不是因为有工具类,单纯觉得上面的方法难用(不难用的话,对应的工具包都出不来)。

使用apache的BeanUtils,实现克隆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.banmoon.prototype;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import org.apache.commons.beanutils.BeanUtils;

import java.lang.reflect.InvocationTargetException;

public class CopyUtilMain {

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Sheep sheep = new Sheep("多莉", 6, "白色");
Estanciero estanciero = new Estanciero("农场主A", CollUtil.newArrayList(sheep));
System.out.println(estanciero);

System.out.println(System.lineSeparator() + "=========== 分割线 Apache ===========");
Estanciero estanciero1 = (Estanciero) BeanUtils.cloneBean(estanciero);
System.out.println(estanciero1);
System.out.println(StrUtil.format("农场主是否一致:{},羊是否一致:{}",
estanciero==estanciero1, estanciero.getSheepList()==estanciero1.getSheepList()));

System.out.println(System.lineSeparator() + "=========== 分割线 Spring ===========");
Estanciero estanciero2 = new Estanciero();
org.springframework.beans.BeanUtils.copyProperties(estanciero, estanciero2);
System.out.println(estanciero2);
System.out.println(StrUtil.format("农场主是否一致:{},羊是否一致:{}",
estanciero==estanciero2, estanciero.getSheepList()==estanciero2.getSheepList()));

System.out.println(System.lineSeparator() + "=========== 分割线 fastjson序列化 ===========");
Estanciero estanciero3 = JSON.parseObject(JSON.toJSONString(estanciero), Estanciero.class);
System.out.println(estanciero3);
System.out.println(StrUtil.format("农场主是否一致:{},羊是否一致:{}",
estanciero==estanciero3, estanciero.getSheepList()==estanciero3.getSheepList()));
}

}

执行结果,注意看它们的克隆是浅拷贝还是深拷贝

image-20220115201510378

5)建造者模式

建造者模式,就是将一个对象组装出来,通过这个对象内的成员属性,一步一步的将这个类实例化。还有不一样的构建顺序,可能导致最终构造出来的实例也是不同的

5.1)示例

比如说,房子的创建步骤可以简单分为打地基、砌墙、封顶。但不同的房子,他们每一个步骤都不是一样的。想想看,普通房子和高楼大厦使用的是一样吗。

所以我们就先创建一个抽象类,将打地基、砌墙、封顶作为抽象方法,让具体的类去实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.banmoon.builder.house;

import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public abstract class House {

private String subgrade;

private String wall;

private String roof;

@Override
public String toString() {
return StrUtil.format("地基:{}\n" +
"砌墙:{}\n" +
"封顶:{}", subgrade, wall, roof);
}
}

两个具体的实现类,高楼房类和公共平房类

1
2
3
4
5
package com.banmoon.builder.house;

public class HighHouse extends House{

}
1
2
3
4
5
package com.banmoon.builder.house;

public class CommonHouse extends House{

}

两个房子弄好了后,我们需要建造者。同样对应上面的房子,一个抽象,两个实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.banmoon.builder;

import com.banmoon.builder.house.House;

public abstract class HouseBuilder {

protected House house;

public HouseBuilder(House house) {
this.house = house;
}

public abstract HouseBuilder buildSubgrade();

public abstract HouseBuilder buildWall();

public abstract HouseBuilder buildRoof();

public House build(){
return house;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.banmoon.builder;

import com.banmoon.builder.house.HighHouse;
import com.banmoon.builder.house.House;

public class HighHouseBuilder extends HouseBuilder {

public HighHouseBuilder(House house) {
super(new HighHouse());
}

@Override
public HouseBuilder buildSubgrade() {
super.house.setSubgrade("打10米深的地基");
return this;
}

@Override
public HouseBuilder buildWall() {
super.house.setWall("打20厘米宽的墙");
return this;
}

@Override
public HouseBuilder buildRoof() {
super.house.setRoof("盖钢化玻璃封顶");
return this;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.banmoon.builder;

import com.banmoon.builder.house.CommonHouse;

public class CommonHouseBuilder extends HouseBuilder {

public CommonHouseBuilder() {
super(new CommonHouse());
}

@Override
public HouseBuilder buildSubgrade() {
super.house.setSubgrade("打2米深的地基");
return this;
}

@Override
public HouseBuilder buildWall() {
super.house.setWall("打10厘米宽的墙");
return this;
}

@Override
public HouseBuilder buildRoof() {
super.house.setRoof("盖瓦片封顶");
return this;
}
}

此时,还需要一个指挥者,与建造者进行组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.builder;

import com.banmoon.builder.house.House;

public class Director {

private HouseBuilder builder;

public Director(HouseBuilder builder) {
this.builder = builder;
}

public House construct() {
return builder.buildSubgrade()
.buildWall()
.buildRoof()
.build();
}

}

这样,就准备完了,如何使用呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon;

import com.banmoon.builder.CommonHouseBuilder;
import com.banmoon.builder.Director;
import com.banmoon.builder.house.House;

public class Main {

public static void main(String[] args) {
House house = new Director(new CommonHouseBuilder()).construct();
System.out.println(house.toString());

System.out.println("=========== 分割线 ===========");

House house1 = new Director(new CommonHouseBuilder()).construct();
System.out.println(house1.toString());
}

}

查看结果

image-20220117223903059

由于它们之间的关系比较复杂,所以画了一个UML类图加深理解

image-20220117231031962

5.2)简化与问题

在上述的示例中,我们能感觉到十分的繁琐。在实际开发中,我们可以进行一些简化

  • 如果只有一个具体对象,那么我们也就不需要抽象建造者了,只需要一个具体建造者

  • 如果创建的对象对建造顺序不重要,那么我们可以取消掉指挥者

由于建造者的书写十分繁琐,所以Lombok推出了简化,使用注解@Builder,示例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.builder.demo;

import lombok.Builder;
import lombok.ToString;

@Builder
@ToString
public class UserInfo {

private Integer id;

private String username;

private String nickname;

private String password;

private String address;

}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.banmoon;

import com.banmoon.builder.demo.UserInfo;

public class Main {

public static void main(String[] args) {
UserInfo userInfo = UserInfo.builder()
.username("banmoon")
.password("2022")
.nickname("半月无霜")
.address("广州")
.build();
System.out.println(userInfo);
}

}

建造模式与工厂模式的区别

我相信这是很多人比较困扰的,实际上这很好理解。

  • 工厂模式不关心内部的构造,只需要准备好参数,让工厂直接返回实例即可。

  • 造者不同,它的每一步外部都是知道的,更关心创建内部的细节。

  • 建造者模式更加注重方法的调用顺序,可以灵活调用,而工厂模式不关心内部参数的调用顺序

在什么场景下使用建造模式

  • 相同的方法,不同的执行顺序,产生不同的结果。
  • 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
  • 产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
  • 初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。

四、结构型设计模式

6)适配器模式

在生活中,我们常常会遇到充电口不匹配的情况,出去旅游的小伙伴感触会深点,国外的插座和我们的不是一个型号,这样的话,我们只需要一个中间层充当适配器的角色,所以适配器模式它来了。

image-20220119203226491

image-20220119203343191

简单了解以前这三点命名概念

  • source:源,被适配的对象,对应的就是上图描述的插座

  • adapter:适配器,对应的就是充电转接器

  • target:目标,对应的就是充电器

适配器模式可以分为类适配器模式对象适配器模式接口适配器模式

6.1)类适配器模式

我们来对电压进行讲解,平常的家庭用电都是220V,经过充电头的转换,将220V的电压转为5V,这样的低电压手机才能使用。

先写一个输出220V电压的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.adapter.classmode;

import cn.hutool.core.util.StrUtil;

public class Electricity220V {

private int voltage = 220;

public int output(){
System.out.println(StrUtil.format("输出电压:{}V", voltage));
return voltage;
}

}

这时我们需要一个充电器,这个充电器就是一个适配器

1
2
3
4
5
6
7
package com.banmoon.adapter.classmode;

public interface ElectricityAdapter {

int transform();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.banmoon.adapter.classmode;

import cn.hutool.core.util.StrUtil;

public class Electricity220VTo5VAdapter extends Electricity220V implements ElectricityAdapter{

@Override
public int transform() {
int voltage = super.output();
int newVoltage = voltage - 215;
System.out.println(StrUtil.format("成功:已将{}V转化为{}V", voltage, newVoltage));
System.out.println(StrUtil.format("输出电压:{}V", newVoltage));
return newVoltage;
}

}

再来一个手机,里面给一个充电的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.adapter;

public class Phone {

public void recharge(int voltage){
if(voltage==5){
System.out.println("手机正在充电中");
}else{
System.out.println("电压不匹配,手机要爆了");
}
}

}

我们进行测试,在使用适配器后,电压会降低为5V给手机供电

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.adapter;

import com.banmoon.adapter.classmode.Electricity220VTo5VAdapter;

public class Main {

public static void main(String[] args) {
Phone phone = new Phone();
phone.recharge(new Electricity220VTo5VAdapter().transform());
}

}

执行结果

image-20220119211141703

类适配器的特点,由于是继承源对象,所以造成了一定的耦合。

而且,如果适配器没有重写源对象的方法,那就意味着适配器上就可以获得源对象的方法和属性。

所以这点原因,此适配器不推荐使用

6.2)对象适配器模式

在里氏替换原则中,能不用继承就不用继承,改用组合,聚合的方法来代替继承。

这点很好理解,在外部将返回的电压转换就可以了。

220V电压,没什么问题,也不做改动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.adapter.objmode;

import cn.hutool.core.util.StrUtil;

public class Electricity220V {

private int voltage = 220;

public int output(){
System.out.println(StrUtil.format("输出电压:{}V", voltage));
return voltage;
}

}

适配器接口,不做改变

1
2
3
4
5
6
7
package com.banmoon.adapter.objmode;

public interface ElectricityAdapter {

int transform();

}

适配器类将不再继承220V电压,改用组合的形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.adapter.objmode;

import cn.hutool.core.util.StrUtil;

public class Electricity220VTo5VAdapter implements ElectricityAdapter{

private Electricity220V electricity220V;

public Electricity220VTo5VAdapter(Electricity220V electricity220V) {
this.electricity220V = electricity220V;
}

@Override
public int transform() {
int voltage = electricity220V.output();
int newVoltage = voltage - 215;
System.out.println(StrUtil.format("成功:已将{}V转化为{}V", voltage, newVoltage));
System.out.println(StrUtil.format("输出电压:{}V", newVoltage));
return newVoltage;
}
}

我们再进行一个测试使用,在使用适配器时,需要传入一个220V的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.adapter;

import com.banmoon.adapter.objmode.Electricity220V;
import com.banmoon.adapter.objmode.Electricity220VTo5VAdapter;

public class Main {

public static void main(String[] args) {
Phone phone = new Phone();
Electricity220VTo5VAdapter adapter = new Electricity220VTo5VAdapter(new Electricity220V());
phone.recharge(adapter.transform());
}

}

image-20220119213218720

6.3)接口适配器模式

当一个接口有四个方法,而这四个方法都需要实现,这是正常的实现类,没什么问题。

但问题是,如果一个类就不想实现其中的两个方法,只需要实现两个就够了,那该怎么办。

这时,我想到了偶像练习生。一般来说,他们都会唱、跳、Rap、篮球。但总有几个练习生拉跨怎么办。接口适配器可以解决这个问题。

接口适配器,又叫默认(缺省)适配器。先将使用一个抽象类来实现这个接口,里面写上默认的方法。由于是抽象类,它又不能直接实例化,抽象类也需要被继承才能实例化。而此时,就可以想实现哪个方法,就实现哪个方法了。

我们就举偶像练习生这个例子,我们先写一个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.adapter.interfacemode;

public interface ITrainee {

String getName();

String[] chang();

String[] tiao();

String[] rap();

String[] lanqiu();

}

接着我们需要一个中间层的适配器,里面默认他什么都不会,但这只是一个抽象的练习生

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.banmoon.adapter.interfacemode;

public abstract class AbsTrainee implements ITrainee{

private String name;

public AbsTrainee(String name) {
this.name = name;
}

@Override
public String getName() {
return name;
}

@Override
public String[] chang() {
return new String[0];
}

@Override
public String[] tiao() {
return new String[0];
}

@Override
public String[] rap() {
return new String[0];
}

@Override
public String[] lanqiu() {
return new String[0];
}
}

我们来一个真正的练习生类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.adapter.interfacemode;

public class CXK extends AbsTrainee{

public CXK(String name) {
super(name);
}

@Override
public String[] chang() {
String[] chang = {"鸡你太美", "鸡你太美鬼畜版"};
return chang;
}

@Override
public String[] tiao() {
String[] tiao = {"与篮球的独舞"};
return tiao;
}

@Override
public String[] lanqiu() {
String[] lanqiu = {"美国校队担当"};
return lanqiu;
}
}

再来一个舞台吧,用来展示练习生的才艺的舞台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.banmoon.adapter.interfacemode;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;

public class Stage {

public void show(ITrainee iTrainee){
String name = iTrainee.getName();
String[] chang = iTrainee.chang();
System.out.println(StrUtil.format("{}开始唱歌:{}", name,
ArrayUtil.isEmpty(chang)? "他怎么什么都不会": chang));
String[] tiao = iTrainee.tiao();
System.out.println(StrUtil.format("{}开始跳舞:{}", name,
ArrayUtil.isEmpty(tiao)? "他怎么什么都不会": tiao));
String[] rap = iTrainee.rap();
System.out.println(StrUtil.format("{}开始rap:{}", name,
ArrayUtil.isEmpty(rap)? "他怎么什么都不会": rap));
String[] lanqiu = iTrainee.lanqiu();
System.out.println(StrUtil.format("{}开始打篮球:{}", name,
ArrayUtil.isEmpty(lanqiu)? "他怎么什么都不会": lanqiu));
}

}

准备就绪,我们来进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.banmoon.adapter;

import com.banmoon.adapter.interfacemode.AbsTrainee;
import com.banmoon.adapter.interfacemode.CXK;
import com.banmoon.adapter.interfacemode.Stage;

public class Main {

public static void main(String[] args) {
Stage stage = new Stage();
stage.show(new CXK("KUN"));

System.out.println("================== 分隔符 ==================");

stage.show(new AbsTrainee("初音") {
@Override
public String[] chang() {
return new String[]{"甩葱歌"};
}
});
}

}

执行结果,除了CXK这个类外,还有另外一个匿名内部类,她的甩葱歌是真的洗脑

image-20220119221753343

7)桥接模式

在现实生活中,手机有着不同的品牌,型号。如果用Java来将他们作为类,你该怎么搞。

可以使用继承,顶上一个父类Phone,它的子类就会出现小米牌手机MiPhone、华为手机HuaweiPhone等等。乍一看没什么问题,也可以使,但是要是加上型号呢?那就会出现下图的结果

image-20220123115442713

这种问题被称为类爆炸问题,当然你也可以将品牌和型号作为手机的一个属性,这样来区分他们的不同品牌型号的手机。然而桥接模式就是这样思考的。

桥接模式,将上述类型拆成两部分。比如说上面的手机,一部分为抽象类手机,另一部分则拆为品牌接口。抽象类手机则有这样的一个品牌属性,这样抽象类手机的子类就能明确知道是什么品牌型号的手机了。

这一层被称为实现层。先写出一个品牌接口,以及它的两个实现类。

1
2
3
4
5
6
7
8
9
10
package com.banmoon.birdge.brand;

/**
* 品牌接口
*/
public interface Brand {

String getBrandName();

}
1
2
3
4
5
6
7
8
package com.banmoon.birdge.brand;

public class Huawei implements Brand{
@Override
public String getBrandName() {
return "华为";
}
}
1
2
3
4
5
6
7
8
package com.banmoon.birdge.brand;

public class Mi implements Brand{
@Override
public String getBrandName() {
return "小米";
}
}

这一层叫做抽象层,会将上面实现层的类作为属性使用。写一个抽象类,再写它的两个子类,分别为折叠屏手机全面屏手机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.birdge.phone;

import com.banmoon.birdge.brand.Brand;

public abstract class Phone {

public Brand brand;

public Phone(Brand brand) {
this.brand = brand;
}

public abstract String getPhoneName();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.birdge.phone;

import com.banmoon.birdge.brand.Brand;

public class FullScreenPhone extends Phone{

public FullScreenPhone(Brand brand) {
super(brand);
}

@Override
public String getPhoneName() {
return "全面屏" + brand.getBrandName() + "手机";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.birdge.phone;

import com.banmoon.birdge.brand.Brand;

public class FoldablePhone extends Phone{

public FoldablePhone(Brand brand) {
super(brand);
}

@Override
public String getPhoneName() {
return "折叠屏" + brand.getBrandName() + "手机";
}
}

上面两层都已经写完,我们再来进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.birdge;

import com.banmoon.birdge.brand.Huawei;
import com.banmoon.birdge.brand.Mi;
import com.banmoon.birdge.phone.FoldablePhone;
import com.banmoon.birdge.phone.FullScreenPhone;

public class BirdgeMain {

public static void main(String[] args) {
FullScreenPhone phone = new FullScreenPhone(new Mi());
String phoneName = phone.getPhoneName();
System.out.println(phoneName);

System.out.println("============= 分割线 =============");
FoldablePhone phone1 = new FoldablePhone(new Huawei());
String phoneName1 = phone1.getPhoneName();
System.out.println(phoneName1);
}

}

执行结果

image-20220123145011769

以上面手机举例,它有两层维度。一个是品牌,一个是型号。

桥接模式,只是将一个维度作为了抽象层,另一个维度作为实现层,通过组合的方式进行关联。上面举例将品牌作为了抽象层,将型号作为了实现层,这个我觉得反过来定义也没有什么问题。不过具体设计时,还是要根据实际业务出发,达到一个容易理解的设计。

若以后还有什么新的品牌,只需要再进行实现品牌接口即可,对原本的代码没有侵害。

如果需要新增维度,那么只需要将其再作为实现层,通过组合的方式进行关联即可。

8)装饰模式

我们来简单实现下这个功能,点杯咖啡,咖啡种类挺多,有美式咖啡,有浓缩咖啡等。这还不够,额外还可以添加方糖,牛奶或者巧克力。

上述添加选择完后,就是我们想要的,问如何设计上述场景的结构。

8.1)我的设计

额,在我没有学习装饰模式前,我会这样设计。

既然方糖,牛奶,巧克力都是添加至到咖啡内的,我就将咖啡弄成一个抽象类,抽象类中有三个属性,分别是方糖,牛奶和巧克力。不同的咖啡种类继承这个咖啡抽象类。在使用时,根据自己的情况先点咖啡,再选择自己的方糖等调料。

我用代码简单来演示一下,先写出方糖、牛奶、巧克力三个类

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.decorator.myimpl.dosing;

import lombok.Getter;

@Getter
public class Sugar {

private Float price = 2F;

private String name = "方糖";

}
1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.decorator.myimpl.dosing;

import lombok.Getter;

@Getter
public class Milk {

private Float price = 3F;

private String name = "牛奶";

}
1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.decorator.myimpl.dosing;

import lombok.Getter;

@Getter
public class Chocolate {

private Float price = 4F;

private String name = "巧克力";

}

再来个咖啡的抽象类,里面定义上述几个类的属性,自身的价格,两个抽象的方法交由子类去重写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.banmoon.decorator.myimpl.coffee;

import com.banmoon.decorator.myimpl.dosing.Chocolate;
import com.banmoon.decorator.myimpl.dosing.Milk;
import com.banmoon.decorator.myimpl.dosing.Sugar;
import lombok.Data;

@Data
public abstract class Coffee {

private Float price;

private String name;

private Sugar sugar;

private Milk milk;

private Chocolate chocolate;

public abstract Float cost();

public abstract String getDesc();

}

再来两个,美式咖啡和浓缩咖啡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.decorator.myimpl.coffee;

public class LongBlack extends Coffee{

public LongBlack() {
setPrice(10F);
setName("美式咖啡");
}

@Override
public Float cost() {
Float price = getPrice();
price += getSugar()==null? 0F: getSugar().getPrice();
price += getMilk()==null? 0F: getMilk().getPrice();
price += getChocolate()==null? 0F: getChocolate().getPrice();
return price;
}

@Override
public String getDesc() {
String desc = getName();
if(getSugar()!=null)
desc += " && " + getSugar().getName() + " " + getSugar().getPrice();
if(getMilk()!=null)
desc += " && " + getMilk().getName() + " " + getMilk().getPrice();
if(getChocolate()!=null)
desc += " && " + getChocolate().getName() + " " + getChocolate().getPrice();
return desc;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.decorator.myimpl.coffee;

public class Espresso extends Coffee{

public Espresso() {
setPrice(8F);
setName("浓缩咖啡");
}

@Override
public Float cost() {
Float price = getPrice();
price += getSugar()==null? 0F: getSugar().getPrice();
price += getMilk()==null? 0F: getMilk().getPrice();
price += getChocolate()==null? 0F: getChocolate().getPrice();
return price;
}

@Override
public String getDesc() {
String desc = getName();
if(getSugar()!=null)
desc += " && " + getSugar().getName() + " " + getSugar().getPrice();
if(getMilk()!=null)
desc += " && " + getMilk().getName() + " " + getMilk().getPrice();
if(getChocolate()!=null)
desc += " && " + getChocolate().getName() + " " + getChocolate().getPrice();
return desc;
}
}

基本已经完成,我们来进行测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.banmoon.decorator.myimpl;

import com.banmoon.decorator.myimpl.coffee.Coffee;
import com.banmoon.decorator.myimpl.coffee.Espresso;
import com.banmoon.decorator.myimpl.coffee.LongBlack;
import com.banmoon.decorator.myimpl.dosing.Chocolate;
import com.banmoon.decorator.myimpl.dosing.Milk;
import com.banmoon.decorator.myimpl.dosing.Sugar;

public class CoffeeShopMain {

public static void main(String[] args) {
LongBlack longBlack = new LongBlack();
longBlack.setMilk(new Milk());
longBlack.setSugar(new Sugar());
System.out.println(longBlack.getDesc());
System.out.println("金额是:" + longBlack.cost());

System.out.println("==================== 分割线 ====================");
Coffee espresso = new Espresso();
espresso.setMilk(new Milk());
espresso.setSugar(new Sugar());
espresso.setChocolate(new Chocolate());
System.out.println(espresso.getDesc());
System.out.println("金额是:" + espresso.cost());
}
}

image-20220128204948630

测试发现,咖啡选择,配料的添加,金额的计算没有出现问题。但如果从设计层面上讲,我这样写代码,绝对会被领导批,为什么?

试想一下,如果需求改变,添加了个豆浆的配料呢。好说,再加一个属性豆浆嘛,那这样就违反了开闭原则了,尽量不要修改到以前的代码。

关于有人说可以将配料抽出一个接口或者抽象类,让咖啡持有一个List就好。其实我也有考虑过这样设计,简单方便。

这样的话,添加一个配料倒是没什么问题,如果添加的是包装费呢。如果把包装费也算作是配料的话,感觉不太对啊。

关键时刻还是得,装饰模式出场。

8.2)装饰模式实现

装饰模式,其实就是封装主体,在外围包上一层,也可以包上多层。这每一层的包装,就是装饰。

如果是上面咖啡的例子的话,美式咖啡、浓缩咖啡就是被装饰的主体,而其他的配料就是装饰,需要什么配料包上去就行。

如何包装,这个是一个问题,我们先来看代码,需要一个抽象类,这和上面没有什么区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.banmoon.decorator.optimize;

import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public abstract class Drink {

private Float price;

private String name;

public String getDesc(){
return StrUtil.format("{} {}元", name, price);
};

public abstract Float cost();

}

再来个咖啡的类,以及美式咖啡和浓缩咖啡

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.decorator.optimize.coffee;

import com.banmoon.decorator.optimize.Drink;

public class Coffee extends Drink {

@Override
public Float cost(){
return getPrice();
}

}
1
2
3
4
5
6
7
8
9
10
package com.banmoon.decorator.optimize.coffee;

public class LongBlack extends Coffee {

public LongBlack() {
setPrice(10F);
setName("美式咖啡");
}

}
1
2
3
4
5
6
7
8
9
10
package com.banmoon.decorator.optimize.coffee;

public class Espresso extends Coffee {

public Espresso() {
setPrice(8F);
setName("浓缩咖啡");
}

}

上面是咖啡,是被装饰的主体,接下来要编写装饰者

同样装饰者也需要继承Drink类,同时它还组合一个Drink的属性。这也就是说装饰者,必须有一个被装饰的主体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.banmoon.decorator.optimize;

import cn.hutool.core.util.StrUtil;
import lombok.Getter;

@Getter
public abstract class Decorator extends Drink {

private Drink drink;

public Decorator(Drink drink) {
this.drink = drink;
}

@Override
public String getDesc(){
return StrUtil.format("{} | {}", super.getDesc(), drink.getDesc());
}

@Override
public Float cost(){
return super.getPrice() + drink.cost();
}
}

现在编写装饰者的几个子类,在此例中,他们都是具体的调料。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.decorator.optimize.dosing;

import com.banmoon.decorator.optimize.Decorator;
import com.banmoon.decorator.optimize.Drink;
import lombok.Getter;

@Getter
public class Chocolate extends Decorator {

public Chocolate(Drink drink) {
super(drink);
setPrice(4F);
setName("巧克力");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.decorator.optimize.dosing;

import com.banmoon.decorator.optimize.Decorator;
import com.banmoon.decorator.optimize.Drink;
import lombok.Getter;

@Getter
public class Milk extends Decorator {

public Milk(Drink drink) {
super(drink);
setPrice(3F);
setName("牛奶");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.decorator.optimize.dosing;

import com.banmoon.decorator.optimize.Decorator;
import com.banmoon.decorator.optimize.Drink;
import lombok.Getter;

@Getter
public class Sugar extends Decorator {

public Sugar(Drink drink) {
super(drink);
setPrice(2F);
setName("方糖");
}
}

如此一来,被装饰的主体和装饰者都已经写好了,马上来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.banmoon.decorator.optimize;


import com.banmoon.decorator.optimize.coffee.Espresso;
import com.banmoon.decorator.optimize.coffee.LongBlack;
import com.banmoon.decorator.optimize.dosing.Chocolate;
import com.banmoon.decorator.optimize.dosing.Milk;
import com.banmoon.decorator.optimize.dosing.Sugar;

public class CoffeeShopMain {

public static void main(String[] args) {
Drink longBlack = new LongBlack();
longBlack = new Milk(longBlack);
longBlack = new Sugar(longBlack);
System.out.println(longBlack.getDesc());
System.out.println("金额是:" + longBlack.cost());

System.out.println("==================== 分割线 ====================");
Drink espresso = new Espresso();
espresso = new Milk(espresso);
espresso = new Sugar(espresso);
espresso = new Chocolate(espresso);
espresso = new Chocolate(espresso);// 点两份巧克力
System.out.println(espresso.getDesc());
System.out.println("金额是:" + espresso.cost());
}
}

image-20220129175945539

简单画一个类图,他们的关系会是这样

image-20220129181823631

按照上面的设计结构,如果是添加一个包装费,也很容易实现扩展。

只要将包装费弄成一个装饰者,马上实现产品的需求,而且对之前的结构没有产生任何影响。

在jkd中,它的IO流就是使用的装饰模式,可以翻翻看。

9)组合模式

组合模式,又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

简单而言,就是多个结构,你中有我,我中有很多个他,他的心中又有许多人。每一个人都是部分,组合起来就是整体。这就是部分-整体的结构概念。

现在简单来看看这三个部分,学校、学院和专业。

  • 这三个从学校开始,学校包含学院,学院包含专业

  • 支持学校增加删除学院,学院增加删除专业

首先定义一个抽象类用来管理学校,学院和专业,让他们继承这个抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.composite;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public abstract class OrganizationComponent {

private String name;// 名称
private String desc;// 简介

public void add(OrganizationComponent component){
// 默认实现,抛出不支持操作异常
throw new UnsupportedOperationException();
}

public void remove(OrganizationComponent component){
throw new UnsupportedOperationException();
}

public abstract void show();

}

学校、学院、专业

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.banmoon.composite;

import java.util.ArrayList;
import java.util.List;

/**
* 学校
*/
public class University extends OrganizationComponent{

private List<OrganizationComponent> orgList = new ArrayList<>();

public University(String name, String desc) {
super(name, desc);
}

@Override
public void add(OrganizationComponent component) {
orgList.add(component);
}

@Override
public void remove(OrganizationComponent component) {
orgList.remove(component);
}

@Override
public void show() {
System.out.println("===========" + getName() + "===========");
orgList.forEach(OrganizationComponent::show);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.banmoon.composite;

import cn.hutool.core.util.StrUtil;

import java.util.ArrayList;
import java.util.List;

/**
* 学院
*/
public class College extends OrganizationComponent{

private List<OrganizationComponent> orgList = new ArrayList<>();

public College(String name, String desc) {
super(name, desc);
}

@Override
public void add(OrganizationComponent component) {
System.out.println(StrUtil.format("{}添加了《{}》专业", getName(), component.getName()));
orgList.add(component);
}

@Override
public void remove(OrganizationComponent component) {
System.out.println(StrUtil.format("{}移除了《{}》专业", getName(), component.getName()));
boolean b = orgList.remove(component);
if(!b)
System.out.println(StrUtil.format("笑死,根本就没有《{}》这个专业", component.getName()));
}

@Override
public void show() {
System.out.println("===========" + getName() + "===========");
orgList.forEach(OrganizationComponent::show);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.composite;

import cn.hutool.core.util.StrUtil;

/**
* 专业
*/
public class Specialty extends OrganizationComponent{

public Specialty(String name, String desc) {
super(name, desc);
}

@Override
public void show() {
System.out.println(StrUtil.format("{} => {}", getName(), getDesc()));
}
}

上述就已经完成组合的结构了,写段代码来测试一下效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.banmoon.composite;

public class CompositeMain {

public static void main(String[] args) {
University university = new University("清华大学", "清华大学");
College collegeA = new College("计算机学院", "计算机学院");
College collegeB = new College("信息工程学院", "信息工程学院");
university.add(collegeA);
university.add(collegeB);
collegeA.add(new Specialty("软件工程", "软件工程掉头发啊"));
collegeA.add(new Specialty("网络工程", "网络工程掉头发啊"));
collegeA.add(new Specialty("计算机科学技术", "计算机科学技术掉头发啊"));
collegeB.add(new Specialty("通信工程", "通信工程掉头发啊"));
collegeB.add(new Specialty("信息工程", "信息工程掉头发啊"));

// 查看学校的信息
university.show();
// 只查看其他的院系
// collegeA.show();
}

}

image-20220208222806004

同样,我也可以从学院进行展示

image-20220208222936458

10)外观模式

在现实生活中,常常存在办事较复杂的例子。如办理什么证件,需要这的那的一大堆材料,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。

软件设计也是这样,一个大的功能下,包含了许多子功能。尽量不要让客户端知道里面的细节,也能可以将事情办成。在一系列复杂的操作上再加上一层,这就是外观模式,也叫过程模式

简单看下这个场景,电脑的开关机。我们知道电脑中有cpu,硬盘,内存,显卡等,但我们不关心内部具体的启动,只需要在机箱上点击启动键。

1
2
3
4
5
6
7
8
9
package com.banmoon.facade;

public interface ComputerComponent {

void open();

void close();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.facade;

public class Cpu implements ComputerComponent{
@Override
public void open() {
System.out.println("cpu启动");
}

@Override
public void close() {
System.out.println("cpu关闭");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.facade;

public class Ddr implements ComputerComponent{
@Override
public void open() {
System.out.println("内存启动");
}

@Override
public void close() {
System.out.println("内存关闭");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.facade;

public class PowerSupply implements ComputerComponent{
@Override
public void open() {
System.out.println("接通电源");
}

@Override
public void close() {
System.out.println("关闭电源");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.facade;

public class Ssd implements ComputerComponent{
@Override
public void open() {
System.out.println("硬盘启动");
}

@Override
public void close() {
System.out.println("硬盘关闭");
}
}

再写一个外观类,通过组合的方式将内部组件配在一起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.banmoon.facade;

public class ComputerFacade implements ComputerComponent{

private Cpu cpu;

private Ddr ddr;

private PowerSupply ps;

private Ssd ssd;

public ComputerFacade() {
cpu = new Cpu();
ddr = new Ddr();
ps = new PowerSupply();
ssd = new Ssd();
}


@Override
public void open() {
ps.open();
cpu.open();
ddr.open();
ssd.open();
System.out.println("==== 电脑启动成功 ====");
}

@Override
public void close() {
ddr.close();
cpu.close();
ssd.close();
ps.close();
System.out.println("==== 电脑关闭成功 ====");
}
}

来个客户端进行开关机吧

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.facade;

public class FacadeMain {

public static void main(String[] args) {
ComputerFacade computerFacade = new ComputerFacade();
// 开启
computerFacade.open();
// 关闭
computerFacade.close();
}
}

image-20220212123701372

外观模式和工厂模式的区别。有些人不理解,但这其实很好理解。

外观模式是过程,大动作里面有一系列的小动作,将这些小动作进行封装,形成的就是外观模式。

工厂模式是实例结果,为了得到这个对象,里面封装的许多对象的细节,这就是工厂模式。

虽然都在外部包上了一层,但结构型和创建型的区别就在这里。

11)享元模式

享元模式,主要用于减少创建对象的数量,以减少内存占用和提高性能。这种模式在我们的代码中经常用到,比如说连接池,或者线程池,这池子就是一个享元模式。

比如说,我们可以看下面这个五子棋例子,棋盘和下棋的一段示例代码。

棋子类,有个颜色的属性

1
2
3
4
5
6
7
8
9
10
11
package com.banmoon.flyweight;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
class ChessPieces{

private String color;
}

再来一个工厂类,让工厂类去控制棋子类的生产

与前面讲到的工厂类有点区别

享元模式的这个会将生产出来的棋子实例放入容器中,后续有需要将从这个容器中去取,从而控制棋子的实例数量。

上面工厂模式是只管创建,外部需要,工厂就创建一个新的实例进行返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.banmoon.flyweight;

import java.util.HashMap;
import java.util.Map;

public class ChessPiecesFactory {

private Map<String, ChessPieces> chessPiecesContainer = new HashMap<>();

public ChessPieces getChessPieces(String type){
ChessPieces chessPieces = chessPiecesContainer.get(type);
if(!chessPiecesContainer.containsKey(type)){
if("white".equals(type)){
chessPieces = new ChessPieces("白");
}else if("black".equals(type)){
chessPieces = new ChessPieces("黑");
}
chessPiecesContainer.put(type, chessPieces);
}
return chessPieces;
}

}

再来一个棋盘类,里面有落子和展示的方法,暂时不要管重复落子的BUG

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.banmoon.flyweight;

import java.rmi.activation.UnknownObjectException;

public class Chessboard {

public ChessPieces[][] chessboard = new ChessPieces[19][19];

private ChessPiecesFactory chessPiecesFactory = new ChessPiecesFactory();

public void put(int x, int y, String type) throws UnknownObjectException {
if(x<0 || x>18 || y<0 || y>18)
throw new UnsupportedOperationException("不能在此落子");
if(!"white".equals(type) && !"black".equals(type))
throw new UnknownObjectException("未知的棋子");
chessboard[x][y] = chessPiecesFactory.getChessPieces(type);
}

public void showChessboard(){
for (int i = 0; i < chessboard.length; i++) {
for (ChessPieces chessPieces : chessboard[i]) {
if(chessPieces!=null)
System.out.print(chessPieces.getColor());
else
System.out.print("空");
}
System.out.println();
}
}

}

写段代码测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.flyweight;

import java.rmi.activation.UnknownObjectException;

public class FlyweightMain {

public static void main(String[] args) throws UnknownObjectException {
Chessboard chessboard = new Chessboard();
chessboard.put(0, 0, "black");
chessboard.put(0, 1, "black");
chessboard.put(1, 0, "white");
chessboard.put(1, 1, "white");
System.out.println(chessboard.chessboard[0][0] == chessboard.chessboard[0][1]);
System.out.println(chessboard.chessboard[1][0] == chessboard.chessboard[1][1]);
// chessboard.showChessboard();
}

}

查看结果,大家也可以将showChessboard()展示出来看看效果

image-20220214195011770

在此处,需要区分对象实例的内部属性和外部属性。在此处,内部属性就是颜色,外部属性就是坐标的x和y,将两者进行区分,否则会造成系统的混乱。

在jdk中,我们常用的Integer就使用到了享元模式。对的,就是那个缓存,具体可以看看源码,和本文的示例不同,但也是享元模式的思想。

12)代理模式

代理模式,大家肯定很熟悉了,它也是属于结构型设计模式的一种,对原本的功能做出的一种增强,而不用影响到原来的代码。

简单的来说,是为目标对象提供一个代替的对象,称之为替身。我们通过使用替身来进行访问目标对象的一些方法。如此一来,我们在替身上就可以做出一些额外的操作,也就是功能增强。

代理模式的分类可以分为两种

  • 静态代理:是我们编写出的结构型代理,编译完成后,就存在有替身的class文件

  • 动态代理:在内存中动态为对象创建替身

    • JDK代理:又叫接口代理
    • Cglib代理

12.1)静态代理

静态代理,目标类和替身类实现同一个接口,替身类再重写的方法中去调用目标类的方法。

先简单来个接口

1
2
3
4
5
package com.banmoon.proxy.staticproxy;

public interface ITeacher {
void teach();
}

再有一个它的实现类

1
2
3
4
5
6
7
8
package com.banmoon.proxy.staticproxy;

public class Teacher implements ITeacher {
@Override
public void teach() {
System.out.println("老师授课中");
}
}

现在代理对象出场,它聚合了目标对象。这里可以选择组合也没有问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.banmoon.proxy.staticproxy;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class TeacherProxy implements ITeacher {

private Teacher teacherDao;

@Override
public void teach() {
System.out.println("上课铃声响了");
teacherDao.teach();
System.out.println("下课铃声响了");
System.out.println("老师开始布置作业");
}
}

在简单使用一下

1
2
3
4
5
6
7
8
9
package com.banmoon.proxy.staticproxy;

public class StaticProxyMain {

public static void main(String[] args) {
TeacherProxy proxy = new TeacherProxy(new Teacher());
proxy.teach();
}
}

image-20220214222818655

静态代理平常不怎么使用,因为实在过于繁杂。这就有了动态代理

12.2)动态代理之jdk代理

jdk动态代理,是使用了jdk中的一个api,代理需要java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类,以及它生产代理类的核心方法

1
2
3
4
5
6
7
8
9
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h);
/**
* 简单说明一下几个参数
* ClassLoader loader:指定目标对象的类加载器,获取加载器的方式是固定的
* Class<?>[] interfaces:目标对象实现的接口类型数组
* InvocationHandler h:事件处理,执行目标对象方法时,会触发此执行器的方法。函数式接口,该接口只有一个方法
*/

照样我们将上面静态代理改成jdk代理的方式

1
2
3
4
5
6
package com.banmoon.proxy.jdkproxy;

public interface ITeacher {

void teach();
}
1
2
3
4
5
6
7
8
package com.banmoon.proxy.jdkproxy;

public class Teacher implements ITeacher {
@Override
public void teach() {
System.out.println("老师授课中");
}
}

编写一个工厂类,将需要代理的对象传入,工厂会生产出这个对象的代理对象的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.proxy.jdkproxy;

import java.lang.reflect.Proxy;

public class JdkProxyFactory {

private Object target;

public JdkProxyFactory(Object target) {
this.target = target;
}

public Object getProxyInstance(){
// 简化使用了lambda表达式,需要大家能看懂
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
(p, m, args) -> {
System.out.println("JDK代理开始");
Object result = m.invoke(target, args);
System.out.println("后置作业通知");
return result;
}
);
return proxyInstance;
}
}

写完了,我们来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.proxy.jdkproxy;

public class JdkProxyMain {

public static void main(String[] args) {
// 创建目标对象
ITeacher teacher = new Teacher();
// 将目标对象传入,让其工厂生产代理对象
ITeacher teacherProxy = (ITeacher) new JdkProxyFactory(teacher).getProxyInstance();
// 查看代理对象Class
System.out.println("teacherProxy:" + teacherProxy.getClass());

System.out.println("============ 分割线 ============");
// 调用代理对象teach方法
teacherProxy.teach();
}

}

image-20220215214732944

可以看到结果,目标对象成功被代理,而teacherProxy对象就是其代理对象,大家可以看看它的包路径,是jdk动态生成的。

12.3)动态代理之Cglib代理

前面的两种代理方式都需要目标对象是有实现一个接口的,但有时候目标对象仅仅只是一个对象,没有实现任何接口。这时候就需要Cglib代理了。

如何选择代理模式

  • 目标对象实现了接口,可以使用jdk代理

  • 目标对象没有实现接口,用Cglib代理,更推荐

Cglib时一个强大的高性能的代码生成包,它的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。所以,就保证了Java在运行时,可以扩展Java的类。它被广泛的运用到其他AOP框架中,如Spring AOP。

Cglib代理又称为子类代理,所以对目标对象有了一定的限制

  • 目标对象的class不能是final类

  • 目标对象中finalstatic方法,不能被拦截代理

引入包依赖

1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

继续演示教师的例子,这次没有教师接口

1
2
3
4
5
6
7
package com.banmoon.proxy.cglibproxy;

public class Teacher {
public void teach() {
System.out.println("老师授课中");
}
}

实现MethodInterceptor接口,并重写,这个相当于jdk代理的InvocationHandler接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.banmoon.proxy.cglibproxy;

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 CglibProxyFactory implements MethodInterceptor {

private Object target;

public CglibProxyFactory(Object target) {
this.target = target;
}

public Object getProxyInstance(){
// 1、创建工具类
Enhancer enhancer = new Enhancer();
// 2、设置父类
enhancer.setSuperclass(target.getClass());
// 3、设置回调函数,实现了MethodInterceptor的类对象实例,这里传入this
enhancer.setCallback(this);
// 4、创建子类对象,即代理对象
return enhancer.create();
}

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib代理 开始");
Object result = method.invoke(target, args);
System.out.println("Cglib代理 后置通知");
return result;
}
}

简单测试一下

1
2
3
4
5
6
7
8
9
10
11
package com.banmoon.proxy.cglibproxy;

public class CglibProxyMain {

public static void main(String[] args) {
Teacher teacher = new Teacher();
Teacher teacherProxy = (Teacher) new CglibProxyFactory(teacher).getProxyInstance();
System.out.println("teacherProxy:" + teacherProxy.getClass());
teacherProxy.teach();
}
}

image-20220215231902753

在日常的项目中,我们常常使用Spring AOP比较多,这个功能可以单独出一篇文章来进行讲解使用。

五、行为型设计模式

13)模板模式

模板模式,又称为模板方法模式。它定义了一套流程,有一定的实现步骤,就和模板一样。只是其中有些方法在模板中不清楚,所以将这些方法让子类去进行实现,子类继承模板类并重写关键的几个方法。这就是模板模式,是行为型设计模式的一种。

基本上,模板类是一个抽象类,负责封装了一个模板方法和其他多个基本方法组成。

  • 模板方法:在此方法中按照一定的顺序调用了其他基本方法

  • 基本方法:除模板方法外的其他方法

    • 抽象方法:模板定义的抽象方法,需要由子类继续实现的方法
    • 具体方法:模板中已经定义实现,必要时在子类中可以重写它
    • 钩子方法:模板中已经定义实现,此类方法用于判断,必要时在子类中可以重写它

在日常生活中,我们常常去医院挂号看病,大致的流程都是一样的吧,挂号,就诊,获取诊单,签字付钱,手术,康复,出院……根据这样一个流程,我们可以把这些步骤弄成一个模板类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.banmoon.template;

public abstract class HospitalTemplate {

/**
* 模板方法
*/
public final void goHospital(){
registration();
seeADoctor();
if(getOrder()>5){
payMoney();
operation();
recovery();
}
leaveHospital();
}

public void registration(){
System.out.println("挂号成功,等待就诊中~");
}

public void seeADoctor(){
System.out.println("正在就诊中~");
}

public abstract int getOrder();

public void payMoney(){
}

public void operation(){
}

public void recovery(){
System.out.println("康复了,好开心~");
}

public void leaveHospital(){
System.out.println("终于离开这该死的医院了~");
}

}

来一个黑心医院

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.template;

public class DirtyHospital extends HospitalTemplate{

@Override
public int getOrder() {
System.out.println("医生心中暗喜,冤大头来了");
return 8;
}

@Override
public void payMoney() {
System.out.println("支付医疗费用 $2333333");
}

@Override
public void operation() {
System.out.println("麻醉,简单输了点葡萄糖~");
}
}

再来一个公共医院

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.banmoon.template;

public class CommonHospital extends HospitalTemplate{

@Override
public void registration() {
System.out.println("挂号,收取费用¥5");
}

@Override
public void seeADoctor() {
System.out.println("医生认真检查中~");
System.out.println("医生并未发现疾病隐患");
}

@Override
public int getOrder() {
return 0;
}

@Override
public void leaveHospital() {
System.out.println("走喽~");
}
}

来写段代码测试一下上面两个医院

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.template;

public class TemplateMain {

public static void main(String[] args) {
DirtyHospital dirtyHospital = new DirtyHospital();
dirtyHospital.goHospital();
System.out.println("=========== 分割线 ===========");
CommonHospital commonHospital = new CommonHospital();
commonHospital.goHospital();
}
}

image-20220216233904361

在学习的过程中,我发现许多弹幕说模板方法模式不就是建造者模式吗,也有的弹幕说是外观模式。这不是一样的吗,这样也能挣到钱,我真的是。他们肯定没有多想,为什么设计模式会分类型,来看看它们的区别吧。

模板方法模式、建造者和外观模式的区别

  • 建造者模式:这是一个构造型设计模式,重点在于提供细节构建出一个对象实例

  • 外观模式:这是一个结构型设计模式,将许多的类封装出一个外观类,通过这个外观类从而省略掉客户端一系列麻烦的操作。上面我举的例子是电脑主机,有CPU、显卡、内存和电源这些类,而外观类相当于机箱,客户端只对机箱进行使用。

  • 模板方法模式:这是一个行为型设计模式,主要在于行为,模板类规定了模板方法,其他方法有的自己默认实现,有的则交给具体的子类去实现,这就是行为不同。由此看来,子类是模板类行为的扩展,是这个模板的具体例子。

14)命令模式

在日常的生活中,我们总扮演者两种角色,官府和小吏,领导和员工,分为了指挥的人和做事的人,而在他们之间是一道命令。在程序中,也是有发起者和执行者,所以命令模式,就是将发起者和执行者进行解耦。

如何解耦,我们可以看看这个例子。将军调兵的场景,按理说将军可以直接命令军队,但现在不同,需要一道有虎符的军令。军队不听将军,只听这道有虎符的军令。所以解耦,这中间层就是命令。

上面例子对应的几个角色:

  • 请求者(Invoker):命令的发起人,在上面的例子就是将军

  • 接收者(Receiver):具体的执行者,执行的方法叫做行动方法,如上例子便是军队

  • 命令(Command):命令的抽象类或者接口,军令

  • 具体命令(ConcreteCommand):具体的命令,负责调用接收者的行动方法。在上面没有特别举例,这是一个具体的军令,如列阵、行军等具体的军令

根据上面的例子,我们来写写看具体的代码。先简单写个命令抽象类当做军令

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.command;

/**
* 军令
*/
public interface Command {

/**
* 执行方法
*/
void execute();

}

等等再写具体的军令,先写出接受者,也就是军队。里面有两个行动方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.banmoon.command;

/**
* 军队
*/
public class Army {

private String name;

public Army(String name) {
this.name = name;
}

public void advance(){
System.out.println(this.name + "正在向前方冲锋");
}

public void retreat(){
System.out.println(this.name + "正在有序撤退");
}

}

现在可以写出具体的军令了,命令要聚合一个军队进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.command;

/**
* 冲锋命令
*/
public class AdvanceCommand implements Command{

private Army army;

public AdvanceCommand(Army army) {
this.army = army;
}

@Override
public void execute() {
army.advance();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.command;

/**
* 撤退命令
*/
public class RetreatCommand implements Command{

private Army army;

public RetreatCommand(Army army) {
this.army = army;
}

@Override
public void execute() {
army.retreat();
}
}

这样一来,就只差个请求者了。嗯对,缺一个将军

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.banmoon.command;

import java.util.ArrayList;
import java.util.List;

/**
* 将军
*/
public class General {

private List<Army> armyList;

public General() {
armyList = new ArrayList<Army>(){{
add(new Army("调查兵团"));
add(new Army("宪兵团"));
add(new Army("驻扎兵团"));
}};
}

public void advance(int i){
AdvanceCommand advanceCommand = new AdvanceCommand(armyList.get(i));
advanceCommand.execute();
}

public void retreat(int i){
RetreatCommand retreatCommand = new RetreatCommand(armyList.get(i));
retreatCommand.execute();
}

}

写段代码测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.command;

public class CommandMain {

public static void main(String[] args) {
General general = new General();
general.advance(0);
general.advance(1);
general.advance(2);
System.out.println("======== 分割线 ========");
general.retreat(0);
}
}

image-20220219133430683

代码写完了,但有一段时间我还是迷茫的。我没有立马想到这个模式的使用场景,考虑不到具体的编码业务场景

后来看了网上许多示例写的都是什么遥控器,比如说我有一个万能遥控器,可以控制顶灯、空调、窗帘等许多物品。我乍一听这不该是外观模式吗?

上面举例的几个物品支持开关操作对吧,如果是外观模式,遥控器这个外观类上就会有m*n个方法,去控制物品的开和关。

但只要使用了命令模式,因为物品只有开和关两个操作,所以我们只需要在遥控器上写两个方法即可。

至此,我才能理解命令模式的思想和它带来的便捷。image-20220219134605904

15)访问者模式

首先,我们先来看一个场景。

比方说在一个歌手选秀现场,评委和现场观众都要对歌手进行打分。打分是一种操作,其他的操作还有直接晋级,直接淘汰。

在开始,选秀节目不够有互动性,仅有三个操作还不太够。结果后来再多出了一个操作,复活评分。

经过这次甜头,导演难免又要瞎整什么骚操作了。你是架构师,该如何设计程序架构对应对付以上导演的刁难。

别慌,访问者可以解决这个问题,这个设计模式针对的是对象不变,但操作频繁扩展的情况。我们先来看看对应的几个角色定义

  • 访问者(Visitor)

    • 访问者定义为接口或者抽象类,它的几个实现类对应具体的操作。
    • 按理来说,它的参数是一个具体的元素,每一个具体的元素都应该有一个自己的方法。
    • 对应上边例子的打分、直接复活等操作
  • 元素(Element)

    • 元素,本来是操作具体的实施角色,但由于操作被抽离出去变成了访问者,那么元素就仅仅只是个操作的发起者而已了。
    • 它定义了一个接受访问者的一个accept方法,指的是每个元素都可以被访问者访问。
    • 对应上边例子就是评委和现场观众
  • ObjectStructure:用来管理元素的一个类,可以有也可以无

我们先来定义一下元素这个角色的抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.banmoon.visitor.element;

import com.banmoon.visitor.visitor.Operation;
import lombok.Data;

/**
* 元素(Element)
*/
@Data
public abstract class Person {

private String name;

public Person(String name) {
this.name = name;
}

public abstract void accept(Operation operation);
}

再定义一下访问者的抽象类吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.visitor.visitor;

import com.banmoon.visitor.element.Judge;
import com.banmoon.visitor.element.Spectator;

/**
* 访问者
*/
public abstract class Operation {

public abstract void operation(Judge judge);

public abstract void operation(Spectator spectator);

}

这样,访问者和元素就聚合在一起了,我们再来写出它们的子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.visitor.element;

import com.banmoon.visitor.visitor.Operation;

/**
* 评委
*/
public class Judge extends Person{

public Judge(String name) {
super(name);
}

@Override
public void accept(Operation operation) {
operation.operation(this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.visitor.element;

import com.banmoon.visitor.visitor.Operation;

/**
* 现场观众
*/
public class Spectator extends Person{

public Spectator(String name) {
super(name);
}

@Override
public void accept(Operation operation) {
operation.operation(this);
}
}

访问者的几个子类操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.visitor.visitor;

import com.banmoon.visitor.element.Judge;
import com.banmoon.visitor.element.Spectator;

/**
* 打分
*/
public class GradeOperation extends Operation {

@Override
public void operation(Judge judge) {
System.out.println(judge.getName() + "正在打分");
}

@Override
public void operation(Spectator spectator) {
System.out.println(spectator.getName() + "正在打分");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.visitor.visitor;

import com.banmoon.visitor.element.Judge;
import com.banmoon.visitor.element.Spectator;

/**
* 直接晋级
*/
public class RiseOperation extends Operation {

@Override
public void operation(Judge judge) {
System.out.println(judge.getName() + "推荐歌手直接晋级");
}

@Override
public void operation(Spectator spectator) {
System.out.println(spectator.getName() + "推荐歌手直接晋级");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.visitor.visitor;

import com.banmoon.visitor.element.Judge;
import com.banmoon.visitor.element.Spectator;

/**
* 淘汰
*/
public class WeedOutOperation extends Operation {

@Override
public void operation(Judge judge) {
System.out.println(judge.getName() + "建议歌手直接淘汰");
}

@Override
public void operation(Spectator spectator) {
System.out.println(spectator.getName() + "建议歌手直接淘汰");
}
}

再来一个ObjectStructure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.banmoon.visitor;

import com.banmoon.visitor.element.Judge;
import com.banmoon.visitor.element.Person;
import com.banmoon.visitor.element.Spectator;
import com.banmoon.visitor.visitor.Operation;

import java.util.ArrayList;
import java.util.List;

public class ObjectStructure {

public List<Person> personElementList;

public ObjectStructure() {
this.personElementList = new ArrayList<>();
this.personElementList.add(new Judge("评委A"));
this.personElementList.add(new Judge("评委B"));
this.personElementList.add(new Judge("评委C"));
this.personElementList.add(new Spectator("观众甲"));
this.personElementList.add(new Spectator("观众乙"));
}

public void add(Person person){
this.personElementList.add(person);
}

public Person remove(Person person){
return this.remove(person);
}

public void accept(Operation operation, int index){
Person person = this.personElementList.get(index);
if(person==null)
throw new UnsupportedOperationException();
person.accept(operation);
}
}

这边你可以继续继承操作来试试,我们先来测试下当前的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.banmoon.visitor;

import com.banmoon.visitor.visitor.GradeOperation;
import com.banmoon.visitor.visitor.RiseOperation;
import com.banmoon.visitor.visitor.WeedOutOperation;

public class VisitorMain {

public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
GradeOperation gradeOperation = new GradeOperation();
WeedOutOperation weedOutOperation = new WeedOutOperation();
RiseOperation riseOperation = new RiseOperation();

objectStructure.accept(gradeOperation, 0);
objectStructure.accept(gradeOperation, 1);
objectStructure.accept(gradeOperation, 2);

objectStructure.accept(weedOutOperation, 3);
objectStructure.accept(riseOperation, 4);
}
}

image-20220220205425208

对于扩展:

新的操作:如果由新的操作的话,只需要继承访问者就可以完成快速扩展了

新的元素:对于新的元素,每个访问者都要进行修改。如果要频繁的改动元素,那么说明访问者模式不适用这种情况

16)迭代器模式

迭代器模式,在集合中常用它进行遍历。它提供了一种方法给你用顺序去访问里面的元素,而不暴露里面的具体存数逻辑。里面可能是数组、链表或者二叉树,这些在外部看来不重要的细节,将被迭代器隐藏。

那么它是怎么被使用的呢,我们先来看一个Iterator接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package java.util;

import java.util.function.Consumer;

public interface Iterator<E> {

boolean hasNext();

E next();

default void remove() {
throw new UnsupportedOperationException("remove");
}

default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}

接口定义的几个方法都很简单,我们主要实现这个接口,为一段元素创造一个迭代器,从而可以实现自己的迭代逻辑。

这边我举例书架上的书,我们将对书架上的书进行遍历。

先来看看迭代器模式中的一个成员角色吧

  • 元素(Element):迭代器中的元素,对应上面例子就是书

  • 迭代器接口(Iterator):迭代器接口,我们使用jdk中的这个就好

    • 迭代器实现类(IteratorImpl):对应上面元素,每一个元素都应该有自己的迭代器接口实现类
  • 总计接口(Aggregate):一个总计的接口,里面将要有一系列元素的集合,不管这个集合你是怎么实现的。并且这个接口定义了createIterator方法,通过这个方法我们可以去创建对应的迭代器

    • 总计实现:实现类,对应上面例子就应该是书架,管理着书本。

我们先来个书本类,以及它具体的书本迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.banmoon.iterator;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Book {

private String name;

@Override
public String toString() {
return "《" + name + "》";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.iterator;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

import java.util.Iterator;
import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
public class BookIterator implements Iterator {

private List<Book> bookList;

private int index;

public BookIterator(List<Book> bookList) {
this(bookList, -1);
}

@Override
public boolean hasNext() {
return bookList!=null && bookList.size()>index+1;
}

@Override
public Object next() {
return bookList.get(++index);
}
}

定义一下Aggregate接口,它将是书架类的一个接口

1
2
3
4
5
6
7
8
package com.banmoon.iterator;

import java.util.Iterator;

public interface Aggregate {

Iterator createIterator();
}

再来一个书架类,这里我们简单点,直接用List集合,真实的情况可能是自己写的节点内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.banmoon.iterator;

import lombok.Data;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

@Data
public class Bookshelf implements Aggregate{

private List<Book> bookList;

public Bookshelf() {
this.bookList = new ArrayList<>();
}

@Override
public Iterator createIterator() {
return new BookIterator(bookList);
}
}

简单的迭代器就完成了,我们简单来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.iterator;

import java.util.Iterator;

public class IteratorMain {

public static void main(String[] args) {
Bookshelf bookshelf = new Bookshelf();
bookshelf.add(new Book("Java从入门到入土"));
bookshelf.add(new Book("Java虚拟机"));
bookshelf.add(new Book("Java内存模型"));

Iterator iterator = bookshelf.createIterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

image-20220221223239369

17)观察者模式

在现实生活中,我们常常会遇到一个对象改变从而影响其他对象的变化。这种类似于发布订阅的业务情况,我们可以使用观察者模式来进行解决。

简单举两个例子,

  • Excel表格中的数据和它对应的图表

  • 天气气象站和对应的天气网站

观察者模式的特点是,一个对象与多个对象之间的联动,存在一对多的依赖关系,当这个对象有所改动,我们就该通知其他对象进行处理。所以,对应的几个角色关系是下面这样的

  • 抽象主题(Subject):指的是那一个对象的抽象接口,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。

    • 具体主题(Concrete Subject):它将实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。对应上面就是Excel中的数据
  • 抽象观察者(Observer):它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。

    • 具体观察者(Concrete Observer):它将实现一个更新自己的一个操作方法,将在更改通知时被调用。对应上面例子就是Excel中的图表

用气象站和天气网站来进行举例,我们先写出两个接口

1
2
3
4
5
6
7
8
9
10
package com.banmoon.observer;

public interface Subject {

void addObserver(Observer observer);

void removeObserver(Observer observer);

void notifyAllObserver();
}
1
2
3
4
5
6
7
8
package com.banmoon.observer;

public interface Observer {

void update(int temperature, int pressure, int humidity);

void showData();
}

提供一个具体的气象站

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.banmoon.observer;

import lombok.Getter;

import java.util.ArrayList;
import java.util.List;

@Getter
public class WeatherBureau implements Subject{

private int temperature;

private int pressure;

private int humidity;

private List<Observer> observerList;

public WeatherBureau() {
this.observerList = new ArrayList<>();
}

public void setData(int temperature, int pressure, int humidity){
System.out.println("气象局天气数据更新");
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 通知
notifyAllObserver();
}

@Override
public void addObserver(Observer observer) {
this.observerList.add(observer);
}

@Override
public void removeObserver(Observer observer) {
this.observerList.remove(observer);
}

@Override
public void notifyAllObserver() {
System.out.println("正在通知所有观察者");
this.observerList.forEach(item -> item.update(this.temperature, this.pressure, this.humidity));
}
}

再来两个具体的观察者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.banmoon.observer;

import cn.hutool.core.util.StrUtil;

public class BaiduWebsite implements Observer{

private int temperature;

private int pressure;

private int humidity;

@Override
public void update(int temperature, int pressure, int humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
showData();
}

@Override
public void showData() {
System.out.println("********* 百度天气 *********");
System.out.println(StrUtil.format("=== 温度:{} ===", this.temperature));
System.out.println(StrUtil.format("=== 气压:{} ===", this.pressure));
System.out.println(StrUtil.format("=== 湿度:{} ===", this.humidity));
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.banmoon.observer;

import cn.hutool.core.util.StrUtil;

public class SinaWebsite implements Observer{

private int temperature;

private int pressure;

private int humidity;

@Override
public void update(int temperature, int pressure, int humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
showData();
}

@Override
public void showData() {
System.out.println("========== 新浪天气 ==========");
System.out.println(StrUtil.format("~~~ 温度:{} ~~~", this.temperature));
System.out.println(StrUtil.format("~~~ 气压:{} ~~~", this.pressure));
System.out.println(StrUtil.format("~~~ 湿度:{} ~~~", this.humidity));
}
}

写段代码来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.observer;

public class ObserverMain {

public static void main(String[] args) {
WeatherBureau weatherBureau = new WeatherBureau();
weatherBureau.addObserver(new BaiduWebsite());
weatherBureau.addObserver(new SinaWebsite());

weatherBureau.setData(15, 3, 6);
}
}

image-20220222214857116

观察者模式的缺点,一定得注意

  • 主题和观察者是通过抽象接口进行解耦,但没有完全解耦,所以可能会出现循环调用的情况,得注意。
  • 具体主题实现会有观察者的集合,当观察者过多时,通知会花费过大的时间,影响程序的效率

18)中介模式

在生活中,我们常常会有需要中介的场景。如买房租房需要中介,国家间的贸易有了WTO组织进行贸易等等。中介模式就是这样,它将由一个中介类来管理一组对象,不让这些对象之间产生联系。

我们简单看看租房的这样一个例子吧,我想要找一个房子,距离地铁500米内,三楼,有阳台,且租金要少于2000元的租房。我只需要找中介就好,中介手里有好多这样的房子,如果需要一个一个的找房东,那不得麻烦死。

所以简单看看它们的角色

  • 抽象中介(Mediator):定义了同事类的注册以及转发同事对象信息的抽象方法。

  • 具体中介(Concrete Mediator):实现中介者接口,定义一个集合来管理同事对象,用于协调调用具体同事对象。

  • 抽象同事(Colleague):同事类抽象出来的方法,将自己注册到中介中管理,实现所有相互影响的同事类的公共功能。

  • 具体同事(Concrete Colleague):具体的同事,里面有具体的属性及实现方法

整活开始,先写一个房子类吧,里面有一些基本的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.mediator;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class House {

private int floor;

private int distance;

private boolean balcony;

private int rent;

public void showInfo(){
System.out.println(StrUtil.format("租房信息【{}楼,距离地铁{}米,{}阳台,租金仅要{}元】",
floor, distance, balcony? "有": "没有", rent));
}

}

再写出抽象层,抽象中介和抽象同事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.mediator;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public abstract class AbstractLandlord {

private String name;

private Mediator mediator;

private List<House> houseList;

public AbstractLandlord(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
this.houseList = new ArrayList<>();
this.mediator.register(this);
}

abstract void sendMessage();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.banmoon.mediator;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public abstract class Mediator {

List<AbstractLandlord> landlordList;

public Mediator() {
landlordList = new ArrayList<>();
}

public void register(AbstractLandlord landlord){
this.landlordList.add(landlord);
}

abstract void getMessage(AbstractLandlord landlord, int type);

abstract House findHouse(int floor, int distance, boolean balcony, int rent);
}

写出房东的两个具体同事类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.banmoon.mediator;

import java.util.Arrays;

public class GoodLandlord extends AbstractLandlord{

public GoodLandlord(String name, Mediator mediator, House... houses) {
super(name, mediator);
super.setHouseList(Arrays.asList(houses));
}

@Override
void sendMessage() {
getMediator().getMessage(this, 1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.mediator;

import java.util.Arrays;

public class BadLandlord extends AbstractLandlord{

public BadLandlord(String name, Mediator mediator, House... houses) {
super(name, mediator);
super.setHouseList(Arrays.asList(houses));
}

@Override
void sendMessage() {
Mediator mediator = getMediator();
mediator.getMessage(this, 1);
mediator.getMessage(this, 2);
mediator.getMessage(this, 3);
mediator.getMessage(this, 4);
}
}

中介要有一个具体的中介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.banmoon.mediator;

import java.util.List;
import java.util.Optional;

public class ConcreteMediator extends Mediator{

@Override
void getMessage(AbstractLandlord landlord, int type) {
if(type==1)
showHouseInfo(landlord);
else
System.out.println(landlord.getName() + ",你TM哪来的这么多要求");
}

@Override
public House findHouse(int floor, int distance, boolean balcony, int rent) {
for (AbstractLandlord landlord : getLandlordList()) {
List<House> houseList = landlord.getHouseList();
Optional<House> first = houseList.stream().filter(house -> house.getFloor() == floor
&& house.getDistance() <= distance
&& house.isBalcony() == balcony
&& house.getRent() <= rent).findFirst();
if(first.isPresent())
return first.get();
}
return null;
}

private void showHouseInfo(AbstractLandlord landlord) {
System.out.println("========== "+ landlord.getName() +"的房子 ==========");
List<House> houses = landlord.getHouseList();
houses.forEach(House::showInfo);
}

}

万事俱备,只差调用,写段代码来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.mediator;

public class MediatorMain {

public static void main(String[] args) {
ConcreteMediator mediator = new ConcreteMediator();
GoodLandlord goodLandlord = new GoodLandlord("李四", mediator,
new House(3, 500, true, 1800),
new House(6, 500, true, 2000));
BadLandlord badLandlord = new BadLandlord("李四", mediator,
new House(3, 800, false, 2000),
new House(6, 800, true, 2200));

goodLandlord.sendMessage();
badLandlord.sendMessage();

System.out.println("============== 分割线 ==============");
// 找一个距离地铁500米内,三楼,有阳台,且租金要少于2000元的租房
House house = mediator.findHouse(3, 500, true, 2000);
if(house!=null){
System.out.println("找到了");
house.showInfo();
}else
System.out.println("可惜没有找到");
}
}

image-20220223220651200

中介模式和外观模式的区别,它们两个真的好像

  • 中介模式:这几个同事类互相纠缠,互相调用,中介模式强调对它们进行解耦,都来找我吧。
    • 上面例子不是特别好,用户和房东应该是同事类,用户去找中介应该是找房子,房东去找中介应该是放出房子信息
    • 如果电脑cpu,内存,主板,电源等要改成中介模式,那就是cpu,内存启动需要电源提供的电量,通过主板中介进行中转电量,嗯这样才对。
  • 外观模式:外观模式强调的是对外提供统一的接口

19)备忘录模式

在现实中,我们做错了事,那这件事就会一直存在,伴随着我们的一生。但在程序的世界中,我们并不是这样。

  • ctrl+c撤销上一步操作

  • 单机游戏的存档功能点

那么如此一个,可以支持撤销,回退到以前的状态的程序该如何进行设计呢。前人为我们总结出了备忘录模式。

备忘录模式:在不破坏对象的封装性的前提下,捕获一个对象的内部状态,并在对象之外保存这个状态。这样一来随时可以将对象恢复到原先保存的状态。备忘录模式常常与原型模式一同使用。

我们简单看看这个例子,即将挑战Boss的主角有三个属性,生命、攻击和防御。在进行挑战时,主角随时会有阵亡,而且还有其他的减攻击力和防御力的debuff效果。在挑战Boss前,将备份存档,如果主角出现意外,我们将恢复他的存档。

根据上面例子,看看备忘录模式需要哪些类进行完成

  • 发起人(Originator):记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。此角色将会有生命、攻击、防御三个属性

  • 备忘录(Memento):负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。此角色会保存住发起人需要保存的属性

  • 管理者(Caretaker):对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。里面保存着每一个存档位,也就是备忘录

根据上面角色,我们先写出有三个属性的发起人,它有生成备忘录和读取备忘录的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.memento;

import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Originator {

private int hp;

private int atk;

private int def;

public Memento createMemento() {
return new Memento(hp, atk, def);
}

public void showCurrentStatus(){
System.out.println(StrUtil.format("血量:{},攻击力:{},防御力:{}", hp, atk, def));
}

public void restoreMemento(Memento m) {
this.setHp(m.getHp());
this.setAtk(m.getAtk());
this.setDef(m.getDef());
}
}

备忘录,里面保存着发起人需要保存的属性信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.memento;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Memento {

private int hp;

private int atk;

private int def;
}

管理者,当然是备忘录的管理者啦,里面用数组管理者备忘录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.banmoon.memento;

public class Caretaker {

private static Memento[] mementoArr = new Memento[3];

public static void setMemento(int i, Memento memento){
mementoArr[i] = memento;
}

public static Memento getMemento(int i){
return mementoArr[i];
}
}

好了,来测试一下吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.memento;

public class MementoMain {

public static void main(String[] args) {
Originator originator = new Originator(5000, 1226, 888);
System.out.println("即将挑战Boss,先进行存档");
Caretaker.setMemento(0, originator.createMemento());

System.out.println("======= 挑战boss中 =======");
originator.setHp(originator.getHp()/2);
originator.setAtk((int)(originator.getAtk()*1.5));
originator.showCurrentStatus();

System.out.println("======= 挑战boss中,我还能坚持 =======");
originator.setHp(originator.getHp()/2);
originator.setAtk(originator.getAtk()/5);
originator.showCurrentStatus();

System.out.println("======= 挑战boss中,有点难度,再存个档 =======");
Caretaker.setMemento(1, originator.createMemento());
originator.setHp(originator.getHp()/2);
originator.setDef(originator.getDef()/5);
originator.showCurrentStatus();

System.out.println("======= 挑战boss中,不行啦,抓紧读档 =======");
originator.restoreMemento(Caretaker.getMemento(0));
originator.showCurrentStatus();
}
}

image-20220224220114715

20)解释器模式

解释器模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎、正则表达等。在平常项目中的使用不是特别大。

给定一个表达式,定义它的各种解释器。一般来说都有一个抽象的解释器,而它的子类会有许多实现,对每一个符号都有一个对应的解释器子类。

例如输入:a+b-c

  • AbstractExpression:抽象的解释器,声明了一个解释方法

  • TerminalExpression:终结符解释器,会继承抽象解释器,实现表达式中最后终止的一个解释操作

  • NoTerminalExpression:非终结符解释器,同样会继承抽象解释器,实现表示式中的一个解释操作

  • Context:上下文环境,在这个环境中,我们将整合上面的表达式和它对应的每个解释器,以及一个解释的入口

首先,一个顶级的抽象解释器,它定义了一个解释方法。这个解释方法的入参是具体的值map,假如表达式是a+b,那么这里将传入{a: 10, b: 20}这样的一个map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.banmoon.interpreter;

import java.util.Map;

/**
* 抽象解释器
*/
public abstract class Expression {

/**
* 解释方法
* @param map
* @return
*/
public abstract int interpreter(Map<String, Integer> map);
}

再来一个值解释器,这是一个具体的解释器,用来解释表达式中的数字占位如表达式为a+b,它将解释ab,将从map中将取出对应的数字值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.interpreter;

import java.util.Map;

/**
* 值解释器
*/
public class ValueExpression extends Expression{

private String key;

public ValueExpression(String key) {
this.key = key;
}

/**
* 值解释器,将对应的值返回
* @param map
* @return
*/
@Override
public int interpreter(Map<String, Integer> map) {
return map.get(key);
}
}

有了值解释器还不够,我们还要定义符号的解释器,准确来说是左右形式的符号解释器。

这个符号解释器是个抽象类,定义了左边的解释器和右边的解释器,在使用时将会传入值解释器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.banmoon.interpreter;

import lombok.Getter;

import java.util.Map;

/**
* 符号解释器
*/
@Getter
public abstract class SymbolExpression extends Expression{

private Expression left;

private Expression right;

public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}

@Override
public abstract int interpreter(Map<String, Integer> map);
}

加法解释器,继承于符号解释器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.interpreter;

import java.util.Map;

public class AddExpression extends SymbolExpression{

public AddExpression(Expression left, Expression right) {
super(left, right);
}

@Override
public int interpreter(Map<String, Integer> map) {
return super.getLeft().interpreter(map) + super.getRight().interpreter(map);
}
}

减法解释器,同样也是继承于符号解释器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.banmoon.interpreter;

import java.util.Map;

public class SubtractExpression extends SymbolExpression{

public SubtractExpression(Expression left, Expression right) {
super(left, right);
}

@Override
public int interpreter(Map<String, Integer> map) {
return super.getLeft().interpreter(map) - super.getRight().interpreter(map);
}
}

上面的几个解释器定义好了,如何使用?这时,我们将创建一个上下文环境的类来使用这些解释器。

上下文环境有点长,请耐心观看,必要时拉下来跑一跑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.banmoon.interpreter;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class Context {

private String expStr;

private Expression expression;

/**
* 构造函数,输入表达式,对表达式进行分析,得出对应的解释器
* @param expStr
*/
public Context(String expStr) {
// 创建栈,一会分析表达式
Stack<Expression> stack = new Stack<>();
// 分析表达式
char[] charArr = expStr.toCharArray();

Expression left = null;
Expression right = null;
for (int i = 0; i < charArr.length; i++) {
switch (charArr[i]){
case '+':
left = stack.pop();
right = new ValueExpression(StrUtil.toString(charArr[++i]));
stack.push(new AddExpression(left, right));
break;
case '-':
left = stack.pop();
right = new ValueExpression(StrUtil.toString(charArr[++i]));
stack.push(new SubtractExpression(left, right));
break;
default:
stack.push(new ValueExpression(StrUtil.toString(charArr[i])));
break;
}
}
this.expression = stack.pop();
this.expStr = expStr;
}

/**
* 进行计算
* @param map
* @return
*/
public int count(Map<String, Integer> map){
int result = this.expression.interpreter(map);
System.out.println(expStr + "=" + result);
return result;
}

/**
* 快速创建表达式上下文
* @return
* @throws IOException
*/
public static Context createContext() throws IOException {
System.out.print("请输入表达式:");
String expStr = new BufferedReader(new InputStreamReader(System.in)).readLine();
return new Context(expStr);
}

/**
* 根据当前的表单事,提供输入具体值
* @return
* @throws IOException
*/
public Map<String, Integer> setValMap() throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if((ch>='a' && ch<='z') || (ch>='A' && ch<='Z')){
System.out.print(StrUtil.format("请输入{}的值:", ch));
String number = new BufferedReader(new InputStreamReader(System.in)).readLine();
map.put(StrUtil.toString(ch), Convert.toInt(number, 0));
}
}
return map;
}

}

事不宜迟,写段代码来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.interpreter;

import java.io.IOException;
import java.util.Map;

public class ExpressionMain {

public static void main(String[] args) throws IOException {
Context context = Context.createContext();
Map<String, Integer> valMap = context.setValMap();
context.count(valMap);
}
}

通过控制台写入表达式a+b,输入值{a: 10, b: 30},查看结果

image-20220303212025761

再来测试下减法,输入表达式a+b-c,输入值{a: 10, b: 10, c: 5}

image-20220303212152634

好的完成,个人认为这个一个最难的设计模式。它和表达式的解析概念高度融合,好在我们平常没有什么解析的需求存在。

21)状态模式

在软件开发中,常常有对象有着不同的状态,而这些不同的状态往往会让对象执行不同的方法,如果使用if来判断的话。喂,这是谁写的代码,查看历史提交,好的吧,半年前的我。现在我可以使用状态模式来对这对象作出优化。

在现实生活中,人们常常会有各种状态,心情好心情差,兴奋度高或者兴奋度低之类的,有些事情只有你在特性的心情状态下才会去进行的。这种情况,我们就可以使用状态模式。

再比如游戏中,吃药,复活,探险是三个操作,而血量的状态却有满血、半血、空血等。

  • 只有在半血的时候才能吃药

  • 只有不是空血状态的时候,才能进行探险

  • 只有在空血的时候,才能进行复活

好的,上面的关系已经罗列出来了,来看看状态模式对这种情况会如何划分角色

  • 状态抽象类(State):状态的抽象层,这里定义了一些操作,将由具体的实现类去实现

    • 具体状态类(ConcreteState):具体的状态,它实现了上层抽象状态的一些操作方法
  • 环境(Context):上下文环境,这个对象将持有状态,对状态进行更变。

首先,状态抽象类先出,没什么问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.banmoon.state;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public abstract class AbstractState {

private int hp;

public AbstractState(int hp) {
this.hp = hp;
}

public abstract void takeMedicine();

public abstract void resurrect();

public abstract void adventure();

}

写出它的三个状态子类,注意这里关联了一个上下文环境类,也就是Person

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.state;

/**
* 空血状态
*/
public class NoHpState extends AbstractState{

private Person person;

public NoHpState(Person person) {
super(0);
this.person = person;
}

@Override
public void takeMedicine() {
System.out.println("阵亡啦,当前状态不能吃药,快去复活");
}

@Override
public void resurrect() {
System.out.println("正在返回出生点,请稍后");
person.setState(new FullHpState(person));
}

@Override
public void adventure() {
System.out.println("阵亡啦,当前状态不能探险,快去复活");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.banmoon.state;

/**
* 满血状态
*/
public class FullHpState extends AbstractState{

private Person person;

public FullHpState(Person person) {
super(100);
this.person = person;
}

@Override
public void takeMedicine() {
System.out.println("满血不能吃药");
}

@Override
public void resurrect() {
System.out.println("还活着,你复活啥?");
}

@Override
public void adventure() {
Person.adventure(person);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.banmoon.state;

/**
* 半血状态
*/
public class HalfHpState extends AbstractState{

private Person person;

public HalfHpState(Person person, int hp) {
super(hp);
this.person = person;
}

@Override
public void takeMedicine() {
System.out.println("快吃药~");
int i = super.getHp() + 30;
if(i>=100){
person.setState(new FullHpState(person));
}
}

@Override
public void resurrect() {
System.out.println("还活着,你复活啥?");
}

@Override
public void adventure() {
Person.adventure(person);
}

public void updateHp(int hp) {
super.setHp(hp);
}
}

上面有关联的Person类再补上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.banmoon.state;

import lombok.Getter;
import lombok.Setter;

import java.util.Random;

@Getter
@Setter
public class Person {

private AbstractState state;

public Person() {
state = new FullHpState(this);
}

public void takeMedicine(){
state.takeMedicine();
}

public void resurrect(){
state.resurrect();
}

public void adventure(){
state.adventure();
}

public static void adventure(Person person){
Random random = new Random();
for (int i = 0; i < 3; i++) {
System.out.println("正在探险中~~~");
int hp = person.getState().getHp() - random.nextInt(40);
if(hp<=0){
System.out.println("已阵亡,停止探险");
person.setState(new NoHpState(person));
break;
}
if(hp<100){
AbstractState state = person.getState();
if(state instanceof FullHpState){
person.setState(new HalfHpState(person, hp));
}else if(state instanceof HalfHpState){
((HalfHpState) state).updateHp(hp);
}
}
}
}

}

这样基本的就写完了,简单来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.banmoon.state;

public class StateMain {

public static void main(String[] args) {
Person person = new Person();
// 试试吃药
person.takeMedicine();
// 试试复活
person.resurrect();
// 试试探险
person.adventure();

System.out.println("========= 分割线 =========");
// 试试吃药
person.takeMedicine();
// 试试复活
person.resurrect();
// 试试探险
person.adventure();

System.out.println("========= 分割线 =========");
// 试试吃药
person.takeMedicine();
// 试试复活
person.resurrect();
// 试试探险
person.adventure();
}

}

image-20220303230301162

额,好像没有把hp打印出来,不能很直观的反应出状态。没事,效果也差不多。

22)策略模式

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

乍一听,什么都听不懂。诶,实际上它和我们的生活息息相关。

比如说,旅游的出行方式,我们可以选择坐高铁,可以选择坐飞机,这些不同的出行方式都是一种策略。

就根据上面的例子,查看下我们需要哪些角色

  • 策略接口(Strategy):策略接口,定义了一个该策略的执行类

    • 具体策略(ConcreteStrategy):具体策略,每一个具体的出行方式
  • 上下文环境(Context):此上下文环境将会聚合策略

话不多说,直接先来一个策略接口

1
2
3
4
5
6
7
package com.banmoon.strategy;

public interface Strategy {

void trip();

}

高铁策略

1
2
3
4
5
6
7
8
9
package com.banmoon.strategy;

public class GTrainStrategy implements Strategy{

@Override
public void trip() {
System.out.println("高铁出行,就是快");
}
}

飞机策略

1
2
3
4
5
6
7
8
package com.banmoon.strategy;

public class AirplaneStrategy implements Strategy{
@Override
public void trip() {
System.out.println("飞机出行,我看行");
}
}

上下文环境,本次为Person,聚合了一个策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.banmoon.strategy;

public class Person {

private Strategy strategy;

public Person(Strategy strategy) {
this.strategy = strategy;
}

public void travel(){
System.out.println("去旅行啦");
packing();
strategy.trip();
play();
}

public void packing(){
System.out.println("正在收拾行李");
}

public void play(){
System.out.println("玩得好开心,旅游真好");
}

}

写个客户端来测试一下

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.strategy;

public class StrategyMain {

public static void main(String[] args) {
Person person = new Person(new AirplaneStrategy());
person.travel();
System.out.println("========== 分割线 ==========");
Person person1 = new Person(new GTrainStrategy());
person1.travel();
}
}

image-20220305005752380

策略模式在开发中特别常见,以前工作的时候做的一个小程序聊天,分别可以发送文字、图片、语音,这就是可以使用策略模式来进行优化。

再比如我这个博客后台系统,支持本地文件上传和OSS文件上传,这也是使用了策略模式。

23)责任链模式

在生活中,我们常常会遇到踢皮球的现象,到一个部门处理说这不是我负责了,踢到了另一个部门,其他部门也是如此。有些部门确实是没有处理事件的能力,但有些部门就是怕承担责任,秉着多一事不如少一事的原则,把我们当做皮球来回踢。搞得我们身心俱疲,想骂而又没有力气。

在程序中,我们为了避免这种事情的发生,客户端不需要清楚的知道该找谁,只需要将问题请求出去,处理类会进行处理,如果自己真的处理不了,那将会将请求丢给下一个处理类处理。而这是处理类内部责任划分,客户端是无感知的。

那么来举个例子,就比如说学生请假,x为请假天数

  • 1<=x<=2:班主任有权限,可以批准

  • 2<x<=7:系主任有权限,可以批准

  • 7<x<=14:副院长有权限,可以批准

  • 14<x:只能由院长进行批准

要在以前,我绝对写一大堆的if...else...,但现在有了责任链模式,可以很方便的进行处理。

先来过一遍,责任链中的角色有哪些

  • 抽象处理者(Handler):抽象类,定义了一个handler,表示责任链上下一个处理者,同时定义了一个处理的方法,将由子类去实现。

  • 具体处理者(ConcreteHandler):抽象处理者的子类,实现了处理请求的方法。同时此处,责任链上下一个具体处理者也已经完成。

  • 请求(Request):包含了请求的一些具体信息

好的,我们先写出请求,包含了名字和请假天数两个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.banmoon.responsible;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class LeaveRequest {

private String name;

private Integer number;
}

再写出一个抽象处理者,它主要持有一个责任链上下个处理者的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.banmoon.responsible;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public abstract class Handler {

private String name;

private Handler nextHandler;

public abstract void executeRequest(LeaveRequest request);

}

这一块我们倒着写,先写出院长,它不再有下一位处理者,所以为null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.responsible;

import cn.hutool.core.util.StrUtil;

public class Dean extends Handler{

public Dean() {
super("院长", null);
}

@Override
public void executeRequest(LeaveRequest request) {
if(request.getNumber()>14)
System.out.println(StrUtil.format("{}批准了{}同学的{}天请假条", super.getName(), request.getName(), request.getNumber()));
else
System.out.println(StrUtil.format("{}拒绝了{}同学的{}天请假", super.getName(), request.getName(), request.getNumber()));
}
}

副院长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.responsible;

import cn.hutool.core.util.StrUtil;

public class VicePresident extends Handler{

public VicePresident() {
super("副院长", new Dean());
}

@Override
public void executeRequest(LeaveRequest request) {
if(request.getNumber()<=14)
System.out.println(StrUtil.format("{}批准了{}同学的{}天请假条", super.getName(), request.getName(), request.getNumber()));
else
super.getNextHandler().executeRequest(request);
}
}

系主任

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.banmoon.responsible;

import cn.hutool.core.util.StrUtil;

public class DepartmentHead extends Handler{

public DepartmentHead() {
super("系主任", new VicePresident());
}

@Override
public void executeRequest(LeaveRequest request) {
if(request.getNumber()<=7)
System.out.println(StrUtil.format("{}批准了{}同学的{}天请假条", super.getName(), request.getName(), request.getNumber()));
else
super.getNextHandler().executeRequest(request);
}
}

班主任

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.banmoon.responsible;

import cn.hutool.core.util.StrUtil;

public class ClassAdviser extends Handler{

public ClassAdviser() {
super("班主任", new DepartmentHead());
}

@Override
public void executeRequest(LeaveRequest request) {
if(request.getNumber()<=0)
throw new UnsupportedOperationException();
if(request.getNumber()<=2)
System.out.println(StrUtil.format("{}批准了{}同学的{}天请假条", super.getName(), request.getName(), request.getNumber()));
else
super.getNextHandler().executeRequest(request);
}
}

万事俱备,只差调用

1
2
3
4
5
6
7
8
9
10
11
12
package com.banmoon.responsible;

public class ResponsibleMain {

public static void main(String[] args) {
LeaveRequest request = new LeaveRequest("半月", 15);
LeaveRequest request1 = new LeaveRequest("九月", 5);
ClassAdviser handler = new ClassAdviser();
handler.executeRequest(request);
handler.executeRequest(request1);
}
}

image-20220305105917841

结果已经看到了,挺方便的一个模式。责任链模式被springMVC使用,就是拦截器。和本文示例有些差别,链路的顺序是由外部控制的,拦截器中优先级的设置。不像本章示例这样,在内部定死。

六、最后想说的话

这些设计模式,以前只是零零散散的整理,但像这样一次性整理还是挺少的。也断断续续的从去年末整理到现在,不为别人而做的努力,只是让自己有个回顾的文章。

在程序方面,上面将的设计模式示例并不能生搬硬套解决问题,而是需要一定的融会贯通,将设计模式变种,融合来进行解决实际程序中的问题。

对比与框架中的源码,如果懂了设计模式,对于今后debug源码也会有很大的帮助。

本文参考:bilibili尚硅谷C语言编程网菜鸟教程

想说的话就这些,与其共勉。

我是半月,祝你幸福!!!