设计模式分类
创建型模式
用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF(四人组)书中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
结构型模式
用于描述如何将类或对象按某种布局组成更大的结构,GoF(四人组)书中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。GoF(四人组)书中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
创建型-单例模式(Singleton pattern)
单例模式(Singleton pattern)确保一个类只有一个实例,并提供该实例的全局访问点
通过单例的创建时机分类,可分为懒汉式和饿汉式
| 实现类型 |
单例创建时机 |
应用场景 |
| 饿汉式 |
- 不可控,类加载时自动创建单例 |
初始化时就需要创建单例,希望单例对象初始化速度快,适合占用内存小的单例 |
| 懒汉式 |
可控,有需要时才手动创建单例 |
按需、延迟创建单例,单例初始化时间长,希望后续再加载此单例以提高服务启动速度,单例占用内存高 |
饿汉式最佳实践-枚举类
枚举类的特点是:
通过枚举类型实现优点是:
唯一缺点就是饿汉式的缺点:创建时机不可控
示例
1 2 3
| public enum Singleton { uniqueInstance; }
|
懒汉式最佳实现-静态内部类
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance() 方法从而触发 SingletonHolder.INSTANCE 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
优点:
线程安全
节省资源(不需要过多同步开销)
实现简单
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Singleton {
private Singleton() {}
private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); }
public static Singleton getInstance() { return SingletonHolder.INSTANCE; } }
|
创建型-简单工厂(Simple Factory)
工厂模式是在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程
把实例化的操作单独放到一个类中,这个类就成为简单工厂类
让简单工厂类来决定应该用哪个具体子类来实例化,这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类
简单工厂模式适用于以下场景:
- 当需要创建的对象只有一个具体产品类时;
- 创建对象的逻辑较为简单,不需要涉及复杂的业务逻辑;
- 不需要频繁地添加新的产品种类。
示例:啥咖啡都有的咖啡店
简单工厂模式适用于只需要创建单一种类的产品的情况。如果你只需要某一种产品,而且不涉及到多个产品族的情况,可以选择简单工厂模式。简单工厂模式通过一个工厂类,根据传入的参数或条件来创建不同的产品。
根据咖啡店需求的咖啡类型,咖啡工厂提供对应的咖啡
咖啡类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface Coffee { String serve(); }
class AmericanCoffee implements Coffee { @Override public String serve() { return "美式咖啡"; } }
class Espresso implements Coffee { @Override public String serve() { return "意式浓缩咖啡"; } }
|
咖啡工厂类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class SimpleCoffeeFactory { public static Coffee createCoffee(String type) { switch (type) { case "American": return new AmericanCoffee(); case "Espresso": return new Espresso(); default: throw new IllegalArgumentException("不支持的咖啡类型:" + type); } } }
|
客户端:
1 2 3 4 5 6 7 8 9 10
| public class Main { public static void main(String[] args) { Coffee coffee1 = SimpleCoffeeFactory.createCoffee("American"); System.out.println(coffee1.serve());
Coffee coffee2 = SimpleCoffeeFactory.createCoffee("Espresso"); System.out.println(coffee2.serve()); } }
|
创建型-工厂方法(Factory Method)
工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态,工厂方法模式保持了简单工厂模式的优点,而且遵守了开闭原则
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则
我们可以定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类
工厂方法模式的主要角色:
- 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
- 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
- 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
- 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
示例:美式咖啡店
如果需要某一类产品下的几个产品分支,可以选择工厂方法模式。每个具体工厂类负责创建自己特定的产品,从而实现了产品的扩展和变化
现在,美式咖啡厂专供美式咖啡店,意式咖啡厂专供意式咖啡店
咖啡类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface Coffee { String serve(); }
class AmericanCoffee implements Coffee { @Override public String serve() { return "美式咖啡"; } }
class Espresso implements Coffee { @Override public String serve() { return "意式浓缩咖啡"; } }
|
工厂类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface CoffeeFactory { Coffee createCoffee(); }
class AmericanCoffeeFactory implements CoffeeFactory { @Override public Coffee createCoffee() { return new AmericanCoffee(); } }
class ItalianCoffeeFactory implements CoffeeFactory { @Override public Coffee createCoffee() { return new Espresso(); } }
|
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Main { public static void main(String[] args) { CoffeeFactory coffeeFactory = new AmericanCoffeeFactory();
Coffee coffee = coffeeFactory.createCoffee(); System.out.println(coffee.serve());
coffeeFactory = new ItalianCoffeeFactory(); coffee = coffeeFactory.createCoffee(); System.out.println(coffee.serve()); } }
|
创建型-抽象工厂(Abstract Factory)
抽象工厂模式适用于需要创建多个相关产品族的情况。如果每个产品分支都需要对应的配套产品,可以选择抽象工厂模式。抽象工厂模式通过定义抽象工厂接口和多个工厂方法来创建不同产品族的产品,确保这些产品相互配合使用
当多个工厂同时生产不同种类的东西,而不是专职生产同种类产品时,那抽象工厂比工厂方法更加适用。换句话说,当工厂要生产一个带有配套产品的产品而不是单品时,抽象工厂更为合适。
示例:有甜点的美式咖啡店
和上个示例相比,现在我们的咖啡店不只是卖咖啡,还卖甜点。只不过美式咖啡厅卖美式甜点,意式咖啡店卖意式甜点。与此同时,我们的工厂现在是意式工厂和美式工厂。
咖啡类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface Coffee { String serve(); }
class AmericanCoffee implements Coffee { @Override public String serve() { return "美式咖啡"; } }
class Espresso implements Coffee { @Override public String serve() { return "意式浓缩咖啡"; } }
|
甜品类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface Dessert { String serve(); }
class AmericanDessert implements Dessert { @Override public String serve() { return "美式甜点"; } }
class ItalianDessert implements Dessert { @Override public String serve() { return "意式甜点"; } }
|
工厂类
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
| interface CoffeeFactory { Coffee createCoffee(); Dessert createDessert(); }
class AmericanCoffeeFactory implements CoffeeFactory { @Override public Coffee createCoffee() { return new AmericanCoffee(); }
@Override public Dessert createDessert() { return new AmericanDessert(); } }
class ItalianCoffeeFactory implements CoffeeFactory { @Override public Coffee createCoffee() { return new Espresso(); }
@Override public Dessert createDessert() { return new ItalianDessert(); } }
|
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Main { public static void main(String[] args) { CoffeeFactory italianFactory = new ItalianCoffeeFactory(); Coffee espresso = italianFactory.createCoffee(); Dessert italianDessert = italianFactory.createDessert(); System.out.println(espresso.serve()); System.out.println(italianDessert.serve());
CoffeeFactory americanFactory = new AmericanCoffeeFactory(); Coffee americano = americanFactory.createCoffee(); Dessert americanDessert = americanFactory.createDessert(); System.out.println(americano.serve()); System.out.println(americanDessert.serve()); } }
|
创建型-建造者模式(Builder)
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。换言之,当某个对象的属性构建过程复杂,但属性注入的程序固定
以一台计算机为例,使用建造者模式建造可以:
- 分离计算机部件的构造(由Builder来负责)和装配(由Director负责)。 计算机各个零件生产复杂,但装配各个部件的程序是一样的。
- 由于实现了构建和装配的解耦。不同的计算机硬件构建器,相同的装配,也可以做出不同的计算机对象;相同的计算机硬件构建器,不同的装配顺序也可以做出不同的计算机对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
- 建造者模式可以将计算机部件和其组装过程分开,一步一步创建一个复杂的计算机对象。用户只需要指定想要哪个装机套餐就可以得到该计算机对象,而无须知道其各零件的具体构造细节。
含有指挥者的示例:
生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类
自行车及其生成器:
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
| @Getter @Setter @ToString class Bicycle { private String frame; private String seat; }
interface BicycleBuilder { void buildFrame(); void buildSeat(); Bicycle getBicycle(); }
@Getter @Setter class CarbonFiberBicycleBuilder implements BicycleBuilder { private Bicycle bicycle;
public CarbonFiberBicycleBuilder() { this.bicycle = new Bicycle(); }
@Override public void buildFrame() { bicycle.setFrame("Carbon Fiber Frame"); }
@Override public void buildSeat() { bicycle.setSeat("Rubber Seat"); }
@Override public Bicycle getBicycle() { return bicycle; } }
@Getter @Setter class AluminumBicycleBuilder implements BicycleBuilder { private Bicycle bicycle;
public AluminumBicycleBuilder() { this.bicycle = new Bicycle(); }
@Override public void buildFrame() { bicycle.setFrame("Aluminum Frame"); }
@Override public void buildSeat() { bicycle.setSeat("Leather Seat"); }
@Override public Bicycle getBicycle() { return bicycle; } }
|
指挥者:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class BicycleDirector { private BicycleBuilder bicycleBuilder;
public BicycleDirector(BicycleBuilder bicycleBuilder) { this.bicycleBuilder = bicycleBuilder; }
public Bicycle construct() { bicycleBuilder.buildFrame(); bicycleBuilder.buildSeat(); return bicycleBuilder.getBicycle(); } }
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Client { public static void main(String[] args) { BicycleDirector bicycleDirector = new BicycleDirector(new CarbonFiberBicycleBuilder()); Bicycle carbonFiberBicycle = bicycleDirector.construct(); System.out.println("Carbon Fiber Bicycle:"); System.out.println(carbonFiberBicycle);
System.out.println();
bicycleDirector = new BicycleDirector(new AluminumBicycleBuilder()); Bicycle aluminumBicycle = bicycleDirector.construct(); System.out.println("Aluminum Bicycle:"); System.out.println(aluminumBicycle); } }
|
不含指挥者的示例:
当系统结构相对简单且复杂对象的构建过程比较固定时,比方说我们造的自行车都是碳纤维的,那么可以将指挥者类与抽象生成器进行结合,从而简化系统结构。
自行车及其生成器:
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
| @Getter @Setter @ToString class Bicycle { private String frame; private String seat; }
interface BicycleBuilder { void buildFrame(); void buildSeat(); Bicycle getBicycle(); }
@Getter @Setter class CarbonFiberBicycleBuilder implements BicycleBuilder { private Bicycle bicycle;
public CarbonFiberBicycleBuilder() { this.bicycle = new Bicycle(); }
@Override public void buildFrame() { bicycle.setFrame("Carbon Fiber Frame"); }
@Override public void buildSeat() { bicycle.setSeat("Rubber Seat"); }
@Override public Bicycle getBicycle() { return bicycle; } }
|
客户端:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Client { public static void main(String[] args) { BicycleBuilder bicycleBuilder = new CarbonFiberBicycleBuilder();
bicycleBuilder.buildFrame(); bicycleBuilder.buildSeat(); Bicycle bicycle = bicycleBuilder.getBicycle();
System.out.println("Carbon Fiber Bicycle:"); System.out.println(bicycle); } }
|
创建型-原型模式(Prototype)
原型模式适用于在需要大量创建相似对象时使用。它通过复制现有对象的方式来创建新对象,而无需重复进行初始化和构建过程
如果我们拥有一批构建好的不同种类的模板,且需要大量输出相同或相似的产品,那么可以使用原型模式把对应模板进行批量复制输出
这种方式特别适合于创建成本较高、创建过程复杂或者需要频繁创建的对象。通过复制现有的对象,我们可以节省创建对象的开销,并且可以方便地对克隆出的对象进行个性化定制。
示例:复制成品
假设我们有一个简单的图形类 Shape,它有一个 draw() 方法用于绘制图形。我们希望能够复制已有的图形对象,而无需知道具体的图形类型。
首先,我们需要创建一个抽象的原型接口 CloneableShape,它包含一个 clone() 方法用于复制图形对象。
1 2 3 4
| public interface CloneableShape extends Cloneable { @Override public CloneableShape clone(); }
|
然后,我们实现具体的图形类,这里以矩形和圆形为例。它们都需要实现 CloneableShape 接口,并且在 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 29 30 31 32 33 34 35
| public class Rectangle implements CloneableShape { private int width; private int height;
public Rectangle(int width, int height) { this.width = width; this.height = height; }
public void draw() { System.out.println("绘制矩形"); }
@Override public CloneableShape clone() { return new Rectangle(this.width, this.height); } }
public class Circle implements CloneableShape { private int radius;
public Circle(int radius) { this.radius = radius; }
public void draw() { System.out.println("绘制圆形"); }
@Override public CloneableShape clone() { return new Circle(this.radius); } }
|
接下来,我们可以创建一个图形缓存类 ShapeCache,用于存储已有的图形对象。在该类中,我们使用一个 HashMap 来存储图形对象,其中键为图形类型,值为对应的原型对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import java.util.HashMap;
public class ShapeCache { private static HashMap<String, CloneableShape> shapeMap = new HashMap<>();
public static CloneableShape getShape(String shapeId) { CloneableShape cachedShape = shapeMap.get(shapeId); return (CloneableShape) cachedShape.clone(); }
public static void loadCache() { shapeMap.put("rectangle", new Rectangle(10, 20)); shapeMap.put("circle", new Circle(30)); } }
|
最后,我们可以使用原型模式来复制图形对象,而无需知道具体的图形类型。
1 2 3 4 5 6 7 8 9 10 11
| public class Main { public static void main(String[] args) { ShapeCache.loadCache();
CloneableShape clonedShape1 = ShapeCache.getShape("rectangle"); clonedShape1.draw();
CloneableShape clonedShape2 = ShapeCache.getShape("circle"); clonedShape2.draw(); } }
|
结构型-外观(Facade)
提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用
在外观模式中,子系统负责各自的功能实现,并且将其封装在具体的类中。外观类则作为一个集合器或者接口提供者,集中收集了子系统的操作,并且将其封装成简单易用的方法。
用户使用外观类时,只需要调用外观类提供的方法,而不需要关心底层子系统的复杂性和细节。外观类内部会根据用户的操作调用子系统的相应方法,完成一系列的操作。这样,外观类提供了一个简化的接口给用户,让用户可以更加方便地使用整个系统。
缺点:
示例:一键看电视
在下面代码示例中,可能会有人觉得外观模式和责任链模式目的是相似的,但是它们的目的和应用场景却是不同的。
外观模式更倾向于分类接口来方便调用,而责任链模式更倾向于让一串任务开始执行的开关
通过语音直接控制智能家电的开启和关闭:
打开灯、打开电视、打开空调
关闭灯、关闭电视、关闭空调
子系统:
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
| public class Light { public void turnOn() { System.out.println("灯已打开"); }
public void turnOff() { System.out.println("灯已关闭"); } }
public class TV { public void turnOn() { System.out.println("电视已打开"); }
public void turnOff() { System.out.println("电视已关闭"); } }
public class AirConditioner { public void turnOn() { System.out.println("空调已打开"); }
public void turnOff() { 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
| public class SmartHomeFacade { private Light light; private TV tv; private AirConditioner airConditioner;
public SmartHomeFacade() { light = new Light(); tv = new TV(); airConditioner = new AirConditioner(); }
public void turnOnAllDevices() { light.turnOn(); tv.turnOn(); airConditioner.turnOn(); }
public void turnOffAllDevices() { light.turnOff(); tv.turnOff(); airConditioner.turnOff(); } }
|
客户端:
1 2 3 4 5 6 7 8 9 10 11
| public class VoiceControlDemo { public static void main(String[] args) { SmartHomeFacade smartHome = new SmartHomeFacade();
smartHome.turnOnAllDevices();
smartHome.turnOffAllDevices(); } }
|
结构型-适配器(Adapter)
适配器模式(Adapter pattern)可以将一个类的接口, 转换成客户期望的另一个接口。 适配器让原本接口不兼容的类可以合作无间。 对象适配器使用组合, 类适配器使用多重继承。我们一般用对象适配器
示例:TF卡转SD卡
现有一台电脑只能读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来
SD卡接口和实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public interface SDCard { String readSD(); void writeSD(String msg); }
public class SDCardImpl implements SDCard { public String readSD() { return "sd card read a msg :hello word SD"; }
public void writeSD(String msg) { System.out.println("sd card write msg : " + msg); } }
|
电脑类
1 2 3 4 5 6 7 8 9
| public class Computer {
public String readSD(SDCard sdCard) { if(sdCard == null) { throw new NullPointerException("sd card null"); } return sdCard.readSD(); } }
|
TF卡接口和实现类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public interface TFCard { String readTF(); void writeTF(String msg); }
public class TFCardImpl implements TFCard {
public String readTF() { return "tf card read msg : hello word tf card"; }
public void writeTF(String msg) { System.out.println("tf card write a msg : " + msg); } }
|
适配器类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class SDAdapterTF implements SDCard {
private TFCard tfCard;
public SDAdapterTF(TFCard tfCard) { this.tfCard = tfCard; }
public String readSD() { System.out.println("adapter read tf card "); return tfCard.readTF(); }
public void writeSD(String msg) { System.out.println("adapter write tf card"); tfCard.writeTF(msg); } }
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class Client { public static void main(String[] args) { Computer computer = new Computer(); SDCard sdCard = new SDCardImpl(); System.out.println(computer.readSD(sdCard));
System.out.println("------------");
TFCard tfCard = new TFCardImpl(); SDAdapterTF adapter = new SDAdapterTF(tfCard); System.out.println(computer.readSD(adapter)); } }
|
结构型-桥接(Bridge)
桥接模式主要是为了解决两个维度的东西使用继承方式配对时造成的继承爆炸
现在有一个需求,需要创建不同的图形,并且每个图形都有可能会有不同的颜色。我们可以利用继承的方式来设计类的关系。但使用继承的话,后续不断的添加子类就会造成继承爆炸(子类过多)
颜色和形状是两个维度的东西。试想,在一个有多种可能会变化的维度的系统中,用继承方式会造成类爆炸,扩展起来不灵活。每次在一个维度上新增一个具体实现都要增加多个子类。为了更加灵活的设计系统,我们此时可以考虑使用桥接模式。
现在你大概理解了我们为什么要使用桥接,用正规的术语来描述使用场景,就是:
- 当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
- 当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
- 当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
示例:不同系统播不同格式视频
需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows、Mac、Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB、AVI、WMV等。该播放器包含了两个维度,适合使用桥接模式
在操作系统抽象类中不提供默认实现的原因是,父类无法确定子类应该如何具体实现播放操作。不同的操作系统版本可能会有特定的实现细节和环境差异,因此需要交给子类去实现
视频接口:
1 2 3
| public interface VideoFile { void decode(String fileName); }
|
不同格式的视频实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class AVIFile implements VideoFile { @Override public void decode(String fileName) { System.out.println("avi视频文件:" + fileName); } }
public class REVBBFile implements VideoFile { @Override public void decode(String fileName) { System.out.println("rmvb文件:" + fileName); } }
|
抽象的操作系统:
1 2 3 4 5 6 7 8 9 10
| public abstract class OperatingSystemVersion {
protected VideoFile videoFile;
public OperatingSystemVersion(VideoFile videoFile) { this.videoFile = videoFile; }
public abstract void play(String fileName); }
|
不同的操作系统子类:
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
| public class Windows extends OperatingSystemVersion {
public Windows(VideoFile videoFile) { super(videoFile); }
@Override public void play(String fileName) { videoFile.decode(fileName); } }
public class Mac extends OperatingSystemVersion {
public Mac(VideoFile videoFile) { super(videoFile); }
@Override public void play(String fileName) { videoFile.decode(fileName); } }
|
测试类:
1 2 3 4 5 6
| public class Client { public static void main(String[] args) { OperatingSystem os = new Windows(new AVIFile()); os.play("三体"); } }
|
为什么是抽象类对接口?
我们来重温一下接口和抽象类的区别:
抽象类和接口在设计上有不同的用途和特点,选择使用抽象类或接口取决于你的设计需求。
抽象类:
- 抽象类可以包含成员变量、非抽象方法和抽象方法。
- 抽象类可以提供默认实现,并且子类可以通过继承来复用这些实现。
- 抽象类适合用于拥有共享代码和行为的相关类之间的继承关系。
接口:
- 接口只能包含常量和抽象方法。接口中的方法没有默认实现,需要在实现时进行具体实现。
- 类可以实现多个接口,从而具备多个接口定义的行为。这种多继承的特性使得接口在实现类的灵活性上更加强大。
- 接口适合用于描述一组相似的行为,并且这些行为可以被不同的类实现。
因此,当你需要定义一组行为,并希望多个类实现这些行为时,使用接口是一个不错的选择。接口更加灵活并且支持类的多继承。
在代码片段中,VideoFile是一个接口,描述了视频文件的解码行为。OperatingSystemVersion是一个抽象类,提供了操作系统版本的通用行为,并将具体的视频文件解码行为委托给了实现了VideoFile接口的具体类。这样设计的好处是,可以通过定义不同的视频文件实现类来支持不同格式的视频文件解码,而不需要修改操作系统版本类。
总结来说,抽象类适用于共享代码和行为的继承关系,而接口适用于描述一组相似行为并让多个类实现。在特定的设计场景中,你可以选择使用抽象类、接口或二者的结合,以满足你的需求。
结构型 - 组合(Composite)
组合模式主要用来解决树形结构和嵌套结构的问题。它通过将对象组织成树状结构,使得我们可以统一处理单个对象和对象组合,从而简化对树形结构的操作和管理
组合模式主要包含三种角色:
- 抽象根节点(Component):定义系统各层次对象的共有方法和属性,可以预先定义一些默认行为和属性。
- 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
- 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
示例:软件菜单
如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称
1 2 3 4 5 6 7 8 9
| - 主菜单 - 子菜单 1 - 菜单项 1.1 - 菜单项 1.2 - 子菜单 2 - 菜单项 2.1 - 菜单项 2.2 - 菜单项 2.3 - 子菜单 3
|
不管是菜单还是菜单项,都应该继承自统一的接口,这里姑且将这个统一的接口称为菜单组件。
这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现。Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法。
举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,也可以根据自己的需要改写默认实现。
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
| public abstract class MenuComponent {
protected String name; protected int level;
public void add(MenuComponent menuComponent){ throw new UnsupportedOperationException(); }
public void remove(MenuComponent menuComponent){ throw new UnsupportedOperationException(); }
public MenuComponent getChild(int i){ throw new UnsupportedOperationException(); }
public String getName(){ return name; }
public void print(){ throw new UnsupportedOperationException(); } }
|
Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。
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 Menu extends MenuComponent {
private List<MenuComponent> menuComponentList;
public Menu(String name,int level){ this.level = level; this.name = name; menuComponentList = new ArrayList<MenuComponent>(); }
@Override public void add(MenuComponent menuComponent) { menuComponentList.add(menuComponent); }
@Override public void remove(MenuComponent menuComponent) { menuComponentList.remove(menuComponent); }
@Override public MenuComponent getChild(int i) { return menuComponentList.get(i); }
@Override public void print() {
for (int i = 1; i < level; i++) { System.out.print("--"); } System.out.println(name); for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } }
|
MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MenuItem extends MenuComponent {
public MenuItem(String name,int level) { this.name = name; this.level = level; }
@Override public void print() { for (int i = 1; i < level; i++) { System.out.print("--"); } System.out.println(name); } }
|
结构型 - 装饰(Decorator)
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。简单来说,装饰者模式是为插件设计而生
示例:添加配菜
快餐店有炒面、炒饭这些快餐,可以额外附加鸡蛋、火腿、培根这些配菜,当然加配菜需要额外加钱,每个配菜的价钱通常不太一样,那么计算总价就会显得比较麻烦。使用继承方式实现的话扩展性差,且会造成继承爆炸。现在我们用装饰者模式实现
快餐接口:
1 2 3 4 5 6 7 8 9 10 11
| @Getter @Setter @NoArgsConstructor @AllArgsConstructor public abstract class FastFood { private float price; private String desc;
public abstract float cost(); }
|
快餐实现类:
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
| public class FriedRice extends FastFood {
public FriedRice() { super(10, "炒饭"); }
@Override public float cost() { return getPrice(); } }
public class FriedNoodles extends FastFood {
public FriedNoodles() { super(12, "炒面"); }
@Override public float cost() { return getPrice(); } }
|
配菜抽象类
1 2 3 4 5 6 7 8 9 10 11
| @Getter @Setter public abstract class Garnish extends FastFood {
private FastFood fastFood;
public Garnish(FastFood fastFood, float price, String desc) { super(price, desc); this.fastFood = fastFood; } }
|
配菜实现类:
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
| public class Egg extends Garnish {
public Egg(FastFood fastFood) { super(fastFood, 1, "鸡蛋"); }
@Override public float cost() { return getPrice() + getFastFood().getPrice(); }
@Override public String getDesc() { return super.getDesc() + getFastFood().getDesc(); } }
public class Bacon extends Garnish {
public Bacon(FastFood fastFood) { super(fastFood, 2, "培根"); }
@Override public float cost() { return getPrice() + getFastFood().getPrice(); }
@Override public String getDesc() { return super.getDesc() + getFastFood().getDesc(); } }
|
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Client { public static void main(String[] args) { FastFood food = new FriedRice(); System.out.println(food.getDesc() + " " + food.cost() + "元"); System.out.println("========");
FastFood food1 = new FriedRice(); food1 = new Egg(food1); System.out.println(food1.getDesc() + " " + food1.cost() + "元"); System.out.println("========");
FastFood food2 = new FriedNoodles(); food2 = new Bacon(food2); System.out.println(food2.getDesc() + " " + food2.cost() + "元"); } }
|
结构型 - 享元(Flyweight)
我们可以将单例对象放到一个对象池中给大家共享,这样可以极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
享元模式中的外部状态相对独立,且不影响内部状态
但同时,也有缺点:为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂
示例:俄罗斯方块
下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。

俄罗斯方块有不同的形状,我们可以对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为。
1 2 3 4 5 6 7
| public abstract class AbstractBox { public abstract String getShape();
public void display(String color) { System.out.println("方块形状:" + this.getShape() + " 颜色:" + color); } }
|
接下来就是定义不同的形状了,IBox类、LBox类、OBox类等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class IBox extends AbstractBox {
@Override public String getShape() { return "I"; } }
public class LBox extends AbstractBox {
@Override public String getShape() { return "L"; } }
public class OBox extends AbstractBox {
@Override public String getShape() { return "O"; } }
|
提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法。
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 BoxFactory {
private static HashMap<String, AbstractBox> map;
public static final BoxFactory getInstance() { return SingletonHolder.INSTANCE; }
private static class SingletonHolder { private static final BoxFactory INSTANCE = new BoxFactory(); }
private BoxFactory() { map = new HashMap<String, AbstractBox>(); AbstractBox iBox = new IBox(); AbstractBox lBox = new LBox(); AbstractBox oBox = new OBox(); map.put("I", iBox); map.put("L", lBox); map.put("O", oBox); }
public AbstractBox getBox(String key) { return map.get(key); } }
|
JDK源码示例:Integer
Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象
对于下面的代码
1 2 3 4 5 6 7 8 9 10 11
| public class Demo { public static void main(String[] args) { Integer i1 = 127; Integer i2 = 127; System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));
Integer i3 = 128; Integer i4 = 128; System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4)); } }
|
编译后有:
1 2 3 4 5 6 7 8 9 10
| public class Demo { public static void main(String[] args) { Integer i1 = Integer.valueOf((int)127); Integer i2 Integer.valueOf((int)127); System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString()); Integer i3 = Integer.valueOf((int)128); Integer i4 = Integer.valueOf((int)128); System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString()); } }
|
上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的就是 valueOf():
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
| public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[];
static { int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); assert IntegerCache.high >= 127; }
private IntegerCache() {} } }
|
结构型 - 代理(Proxy)
代理模式的目的是为另一个对象提供一个替身或占位符以控制对这个对象的访问。
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
三种代理的对比:
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
JDK动态代理示例
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象
Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象:
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
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;
public interface SellTickets { void sell(); }
public class TrainStation implements SellTickets { public void sell() { System.out.println("火车站卖票"); } }
public class ProxyFactory { private TrainStation station = new TrainStation();
public SellTickets getProxyObject() {
SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance( station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); Object result = method.invoke(station, args); return result; } }); return sellTickets; } }
public class Client { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
|
请注意,ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具——Arthas查看代理类的结构:
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
| package com.sun.proxy;
import com.itheima.proxy.dynamic.jdk.SellTickets; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements SellTickets { private static Method m1; private static Method m2; private static Method m3; private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); }
static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]); m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); return; } catch (NoSuchMethodException noSuchMethodException) { throw new NoSuchMethodError(noSuchMethodException.getMessage()); } catch (ClassNotFoundException classNotFoundException) { throw new NoClassDefFoundError(classNotFoundException.getMessage()); } }
public final boolean equals(Object object) { try { return (Boolean)this.h.invoke(this, m1, new Object[]{object}); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final String toString() { try { return (String)this.h.invoke(this, m2, null); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final int hashCode() { try { return (Integer)this.h.invoke(this, m0, null); } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } }
public final void sell() { try { this.h.invoke(this, m3, null); return; } catch (Error | RuntimeException throwable) { throw throwable; } catch (Throwable throwable) { throw new UndeclaredThrowableException(throwable); } } }
|
从上面的类中,我们可以看到以下几个信息:
- 代理类
$Proxy0实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
- 代理类
$Proxy0将我们提供了的匿名内部类对象传递给了父类。
动态代理的执行流程是什么样的?
下面是摘取的重点代码:
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
| public final class $Proxy0 extends Proxy implements SellTickets { private static Method m3;
public $Proxy0(InvocationHandler invocationHandler) { super(invocationHandler); }
static { m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]); }
public final void sell() { this.h.invoke(this, m3, null); } }
public class Proxy implements java.io.Serializable { protected InvocationHandler h;
protected Proxy(InvocationHandler h) { this.h = h; } }
public class ProxyFactory {
private TrainStation station = new TrainStation();
public SellTickets getProxyObject() { SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(), station.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理点收取一些服务费用(JDK动态代理方式)"); Object result = method.invoke(station, args); return result; } }); return sellTickets; } }
public class Client { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(); SellTickets proxyObject = factory.getProxyObject(); proxyObject.sell(); } }
|
执行流程如下:
- 在测试类中通过代理对象调用
sell()方法
- 根据多态的特性,执行的是代理类
$Proxy0中的sell()方法
- 代理类
$Proxy0中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
invoke方法通过反射执行了真实对象所属类TrainStation中的sell()方法
CGLIB动态代理
同样是上面的案例,我们再次使用CGLIB代理实现。
如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
1 2 3 4 5
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
|
火车站:
1 2 3 4 5 6
| public class TrainStation {
public void sell() { 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
| public class ProxyFactory implements MethodInterceptor {
private TrainStation target = new TrainStation();
public TrainStation getProxyObject() { Enhancer enhancer =new Enhancer(); enhancer.setSuperclass(target.getClass()); enhancer.setCallback(this); TrainStation obj = (TrainStation) enhancer.create(); return obj; }
public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)"); TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args); return result; } }
|
测试类:
1 2 3 4 5 6 7 8 9 10
| public class Client { public static void main(String[] args) { ProxyFactory factory = new ProxyFactory(); TrainStation proxyObject = factory.getProxyObject();
proxyObject.sell(); } }
|
行为型 - 责任链(Chain Of Responsibility)
通过责任链模式, 你可以为某个请求创建一个对象链. 每个对象依序检查此请求并对其进行处理或者将它传给链中的下一个对象。
可以把责任链看做一条流水线,产品经过某个工序处理,或被中途丢弃,或被传到下一个工序处理。我们可以很方便的插入或去掉任意一道工序,而不影响其他工序的运行,就像链表一样。每道工序只需要接收一个固定的对象,也就是上下文
缺点:
- 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
示例:请假流程
现需要开发一个请假流程控制系统。请假一天以下的假只需要小组长同意即可;请假1天到3天的假还需要部门经理同意;请求3天到7天还需要总经理同意才行
上下文-请假条:
1 2 3 4 5 6 7 8 9
| @Data @NoArgsConstructor @AllArgsConstructor public class LeaveRequest { private String employeeName; private int days; private boolean approved = false; private String reason; }
|
审批者接口:
1 2 3 4 5 6
| public interface Approver { void setNextApprover(Approver nextApprover); void approveLeave(LeaveRequest request); }
|
审批者实现类:
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
| @Data @NoArgsConstructor @AllArgsConstructor public class TeamLeader implements Approver { private Approver nextApprover;
public void approveLeave(LeaveRequest request) { if (request.getDays() < 1) { request.setApproved(true); System.out.println("小组长同意" + request.getEmployeeName() + "的请假申请"); } else if (nextApprover != null) { nextApprover.approveLeave(request); } } }
@Data @NoArgsConstructor @AllArgsConstructor public class DepartmentManager implements Approver { private Approver nextApprover;
public void approveLeave(LeaveRequest request) { if (request.getDays() >= 1 && request.getDays() <= 3) { request.setApproved(true); System.out.println("部门经理同意" + request.getEmployeeName() + "的请假申请"); } else if (nextApprover != null) { nextApprover.approveLeave(request); } } }
@Data @NoArgsConstructor @AllArgsConstructor public class GeneralManager implements Approver { public void approveLeave(LeaveRequest request) { if (request.getDays() > 3 && request.getDays() <= 7) { request.setApproved(true); System.out.println("总经理同意" + request.getEmployeeName() + "的请假申请"); } else { request.setApproved(false); System.out.println("请假超过7天,需要进一步审批"); } } }
|
请假流程控制类:
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
| public class LeaveApprovalChain { private Approver chain;
public LeaveApprovalChain() { Approver teamLeader = new TeamLeader(); Approver departmentManager = new DepartmentManager(); Approver generalManager = new GeneralManager();
chain = teamLeader;
teamLeader.setNextApprover(departmentManager); departmentManager.setNextApprover(generalManager); }
public void processLeaveRequest(LeaveRequest request) { chain.approveLeave(request);
if (!request.isApproved()) { System.out.println(request.getEmployeeName() + "的请假申请被拒绝"); } } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Client { public static void main(String[] args) { LeaveRequest request1 = new LeaveRequest("张三", 0, "家庭原因"); LeaveRequest request2 = new LeaveRequest("李四", 2, "病假"); LeaveRequest request3 = new LeaveRequest("王五", 4, "探亲假"); LeaveRequest request4 = new LeaveRequest("赵六", 8, "年休假");
LeaveApprovalChain chain = new LeaveApprovalChain(); chain.processLeaveRequest(request1); chain.processLeaveRequest(request2); chain.processLeaveRequest(request3); chain.processLeaveRequest(request4); } }
|
运行客户端输出:
1 2 3 4 5
| 小组长同意张三的请假申请 部门经理同意李四的请假申请 总经理同意王五的请假申请 请假超过7天,需要进一步审批 赵六的请假申请被拒绝
|
行为型 - 策略(Strategy)
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机……
作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进行开发,也可以使用eclipse进行开发,也可以使用其他的一些开发工具。
上述例子的共同点是:我们可以自由选择策略来执行某个任务。
策略模式的主要角色如下:
- 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个策略类的引用,最终给客户端调用。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
示例:促销活动:
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。
促销活动接口:
1 2 3
| public interface Strategy { 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
| public class StrategyA implements Strategy { @Override public void show() { System.out.println("买一送一"); } }
public class StrategyB implements Strategy { @Override public void show() { System.out.println("满200元减50元"); } }
public class StrategyC implements Strategy { @Override public void show() { System.out.println("满1000元加一元换购任意200元以下商品"); } }
|
环境角色-推送者:
用于连接上下文,即把促销活动推销给客户,这里可以理解为销售员
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class SalesMan { private Strategy strategy;
public SalesMan(Strategy strategy) { this.strategy = strategy; }
public void salesManShow(){ strategy.show(); } }
|
客户端调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Client { public static void main(String[] args) { Strategy strategyA = new StrategyA(); Strategy strategyB = new StrategyB(); Strategy strategyC = new StrategyC();
SalesMan salesMan1 = new SalesMan(strategyA); SalesMan salesMan2 = new SalesMan(strategyB); SalesMan salesMan3 = new SalesMan(strategyC);
salesMan1.salesManShow(); salesMan2.salesManShow(); salesMan3.salesManShow(); } }
|
客户端输出:
1 2 3
| 买一送一 满200元减50元 满1000元加一元换购任意200元以下商品
|
行为型 - 模板方法(Template Method)
在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤。
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
优点:
缺点:
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
示例:炒菜
注意:为防止恶意操作,一般模板方法都加上 final 关键词。
炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。
抽象类:
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
| public abstract class AbstractCook {
public final void cookProcess() { this.pourOil(); this.heatOil(); this.pourVegetable(); this.pourSauce(); this.fry(); }
public void pourOil() { System.out.println("倒油"); }
public void heatOil() { System.out.println("热油"); }
public abstract void pourVegetable();
public abstract void pourSauce();
public void fry(){ 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
| public class BaoCaiCook extends AbstractCook {
@Override public void pourVegetable() { System.out.println("下锅的蔬菜是包菜"); }
@Override public void pourSauce() { System.out.println("下锅的酱料是辣椒"); } }
public class CaiXinCook extends AbstractCook { @Override public void pourVegetable() { System.out.println("下锅的蔬菜是菜心"); }
@Override public void pourSauce() { System.out.println("下锅的酱料是蒜蓉"); } }
|
调用示例:
1 2 3 4 5 6 7 8 9 10 11
| public class CookClient { public static void main(String[] args) { BaoCaiCook baoCai = new BaoCaiCook(); baoCai.cookProcess();
CaiXinCook caiXin = new CaiXinCook(); caiXin.cookProcess(); } }
|
调用示例输出:
1 2 3 4 5 6 7 8 9 10 11
| 倒油 热油 下锅的蔬菜是包菜 下锅的酱料是辣椒 炒啊炒啊炒到熟啊
倒油 热油 下锅的蔬菜是菜心 下锅的酱料是蒜蓉 炒啊炒啊炒到熟啊
|
行为型 - 命令模式(Command)
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
命令模式包含以下主要角色:
- 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
示例:饭店吃饭
点单
订单交给厨师
厨师备菜
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。
服务员: 就是调用者角色,由她来发起命令。
资深大厨: 就是接收者角色,真正命令执行的对象。
订单: 命令中包含订单。
pojo类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @Getter @Setter @AllArgsConstructor public class Order { private String dishName; }
public class Waiter { public void placeOrder(Command command) { System.out.println("服务员接收到订单"); command.execute(); } }
public class SeniorChef { public void cookDish(String dishName) { System.out.println("资深大厨开始烹饪:" + dishName); System.out.println("资深大厨完成烹饪:" + dishName); } }
|
命令接口Command和具体命令类CookingCommand:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public interface Command { void execute(); }
@AllArgsConstructor public class CookingCommand implements Command { private SeniorChef chef; private Order order;
@Override public void execute() { chef.cookDish(order.getDishName()); } }
|
测试输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class CommandPatternExample { public static void main(String[] args) { Waiter waiter = new Waiter(); SeniorChef chef = new SeniorChef();
Order order = new Order("红烧肉");
Command cookingCommand = new CookingCommand(chef, order);
waiter.placeOrder(cookingCommand); } }
|
输出结果:
1 2 3
| 服务员接收到订单 资深大厨开始烹饪:红烧肉 资深大厨完成烹饪:红烧肉
|
行为型 - 观察者(Observer)
又被称为发布-订阅(Publish/Subscribe)模式。在对象之间定义一对多的依赖, 这样一来, 当一个对象改变状态, 依赖它的对象都会收到通知, 并自动更新。没错,就像公众号订阅。
示例:微信公众号
使用微信公众号时,当公众号有新内容更新,订阅者就会收到通知。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,一个公众号被多个微信用户订阅。
发布者接口Observable:
1 2 3 4 5 6 7 8 9 10
| public interface Observable { void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(); }
|
发布者实现类WechatAccount:
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
| @Getter @Setter public class WechatAccount implements Observable { private String accountName; private List<Observer> observers; private String latestContent;
public WechatAccount(String accountName) { this.accountName = accountName; this.observers = new ArrayList<>(); }
@Override public void addObserver(Observer observer) { observers.add(observer); }
@Override public void removeObserver(Observer observer) { observers.remove(observer); }
@Override public void notifyObservers() { for (Observer observer : observers) { observer.update(this); } }
public void publishNewContent(String content) { this.latestContent = content; notifyObservers(); } }
|
订阅者接口Observer:
1 2 3 4
| public interface Observer { void update(Observable observable); }
|
订阅者实现类WechatUser:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Getter public class WechatUser implements Observer { private String userName;
public WechatUser(String userName) { this.userName = userName; }
@Override public void update(Observable observable) { if (observable instanceof WechatAccount) { WechatAccount wechatAccount = (WechatAccount) observable; String latestContent = wechatAccount.getLatestContent(); System.out.println("用户 " + userName + " 收到了公众号 " + wechatAccount.getAccountName() + " 的新内容:" + latestContent); } } }
|
客户端示例:
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
| public class ObserverPatternExample { public static void main(String[] args) { WechatAccount accountA = new WechatAccount("公众号A"); WechatAccount accountB = new WechatAccount("公众号B");
WechatUser user1 = new WechatUser("用户A"); WechatUser user2 = new WechatUser("用户B");
accountA.addObserver(user1); accountB.addObserver(user1); accountB.addObserver(user2);
accountA.publishNewContent("新内容A1"); accountB.publishNewContent("新内容B1");
accountA.removeObserver(user1);
accountA.publishNewContent("新内容A2"); accountB.publishNewContent("新内容B2"); } }
|
输出结果:
1 2 3 4 5
| 用户 用户A 收到了公众号 公众号A 的新内容:新内容A1 用户 用户A 收到了公众号 公众号B 的新内容:新内容B1 用户 用户B 收到了公众号 公众号B 的新内容:新内容B1 用户 用户A 收到了公众号 公众号A 的新内容:新内容A2 用户 用户B 收到了公众号 公众号B 的新内容:新内容B2
|
行为型 - 访问者(Visitor)
当你想要为一个对象的组合增加新的能力, 且封装并不重要时, 就使用访问者模式
示例:喂宠物
现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食
被访问元素抽象类Animal:
1 2 3 4 5 6 7
| @Data public abstract class Animal { private String name; private int age;
public abstract void accept(PetVisitor visitor); }
|
被访问元素——宠物狗类Dog和宠物猫类Cat:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Getter @AllArgsConstructor public class Dog extends Animal { private String breed;
@Override public void accept(PetVisitor visitor) { visitor.visit(this); } }
@Getter @AllArgsConstructor public class Cat extends Animal { private String color;
@Override public void accept(PetVisitor visitor) { visitor.visit(this); } }
|
访问者接口PetVisitor:
1 2 3 4
| public interface PetVisitor { void visit(Dog dog); void visit(Cat cat); }
|
访问者实现类:
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
| public class Owner implements PetVisitor { @Override public void visit(Dog dog) { System.out.println("主人给狗狗 " + dog.getName() + " 喂食"); }
@Override public void visit(Cat cat) { System.out.println("主人给猫咪 " + cat.getName() + " 喂食"); } }
public class Others implements PetVisitor { @Override public void visit(Dog dog) { System.out.println("其他人给狗狗 " + dog.getName() + " 喂食"); }
@Override public void visit(Cat cat) { System.out.println("其他人给猫咪 " + cat.getName() + " 喂食"); } }
|
调用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class VisitorPatternExample { public static void main(String[] args) { Animal dog = new Dog("旺财", "金毛"); Animal cat = new Cat("小花", "橘猫");
PetVisitor owner = new Owner(); PetVisitor others = new Others();
dog.accept(owner); cat.accept(others); } }
|
运行上述代码,输出将是:
1 2
| 主人给狗狗 旺财 喂食 其他人给猫咪 小花 喂食
|
行为型 - 状态(State)
允许对象在内部状态改变时改变它的行为
示例:自动售货机
自动售货机根据当前的状态来决定如何响应用户的操作。例如,在待机状态下,用户插入硬币可能会触发状态转换到选择商品的状态;而在售出商品状态下,用户取出商品后,状态会转换回待机状态。状态模式可以用于管理自动售货机的各种状态,并根据不同状态执行相应的操作。
自动售货机类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Data public class VendingMachine { private VendingMachineState state;
public void insertCoin() { state.insertCoin(); }
public void selectProduct() { state.selectProduct(); } public void dispenseProduct() { state.dispenseProduct(); } }
|
售货机状态接口:
1 2 3 4 5
| public interface VendingMachineState { void insertCoin(); void selectProduct(); void dispenseProduct(); }
|
状态实现类:
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
| @RequiredArgsConstructor class IdleState implements VendingMachineState { private VendingMachine vendingMachine;
@Override public void insertCoin() { System.out.println("已插入硬币"); vendingMachine.setState(new SelectProductState(vendingMachine)); }
@Override public void selectProduct() { System.out.println("请先插入硬币"); }
@Override public void dispenseProduct() { System.out.println("请先选择商品"); } }
@RequiredArgsConstructor class SelectProductState implements VendingMachineState { private VendingMachine vendingMachine;
@Override public void insertCoin() { System.out.println("已插入硬币,请选择商品"); }
@Override public void selectProduct() { System.out.println("已选择商品"); vendingMachine.setState(new SoldOutState(vendingMachine)); }
@Override public void dispenseProduct() { System.out.println("请先选择商品"); } }
@RequiredArgsConstructor class SoldOutState implements VendingMachineState { private VendingMachine vendingMachine;
@Override public void insertCoin() { System.out.println("商品已售罄"); }
@Override public void selectProduct() { System.out.println("商品已售罄"); }
@Override public void dispenseProduct() { System.out.println("商品已售罄"); } }
|
调用示例:
1 2 3 4 5 6 7 8 9 10
| public class VendingMachineExample { public static void main(String[] args) { VendingMachine vendingMachine = new VendingMachine(); vendingMachine.setState(new IdleState(vendingMachine));
vendingMachine.insertCoin(); vendingMachine.selectProduct(); vendingMachine.dispenseProduct(); } }
|
输出:
行为型 - 解释器(Interpreter)
使用解释器模式为语言创建解释器,通常由语言的语法和语法分析来定义
示例:正则表达式引擎
正则表达式是一种强大的文本模式匹配工具,常用于字符串处理、文本搜索和替换等任务。正则表达式引擎使用解释器模式来解析和执行正则表达式。
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
| interface Expression {
boolean interpret(String input); }
@Getter @AllArgsConstructor class CharacterExpression implements Expression { private final char character;
@Override public boolean interpret(String input) { return input.contains(Character.toString(character)); } }
@AllArgsConstructor class OrExpression implements Expression { private final Expression expression1; private final Expression expression2;
@Override public boolean interpret(String input) { return expression1.interpret(input) || expression2.interpret(input); } }
public class RegularExpressionEngine { public static void main(String[] args) { Expression expression = new OrExpression( new CharacterExpression('b'), new CharacterExpression('c') );
String input = "abcd";
boolean isMatch = expression.interpret(input); System.out.println("Input: " + input); System.out.println("Match result: " + isMatch); } }
|
行为型 - 迭代器(Iterator)
提供一种方法顺序访问一个聚合对象中的各个元素, 而又不暴露其内部的表示
直接用for循环替代吧,没啥好说的
中介者模式(Mediator Pattern)用于解耦一组对象之间的交互关系。它通过引入一个中介者对象,将各个对象之间的交互逻辑集中处理。中介者模式的重点在于对象之间的通信和协调。
中介者vs外观
中介者模式(Mediator Pattern)用于解耦一组对象之间的交互关系。它通过引入一个中介者对象,将各个对象之间的交互逻辑集中处理。中介者模式的重点在于对象之间的通信和协调。
外观模式(Facade Pattern)主要用于简化复杂系统的接口。它提供了一个统一的接口,隐藏系统的复杂性,使得客户端可以更方便地访问子系统。外观模式的重点在于封装和简化接口。
简单来说,中介者更像是一个中转站,而外观模式则是起点(入口)
示例:GUI开发
在GUI中,有多个组件(如按钮、文本框、下拉列表等),它们之间需要进行交互和通信。使用中介者模式可以将这些交互逻辑集中处理,并避免组件之间的紧耦合。
中介者接口Mediator,它包含了组件之间的交互方法:
1 2 3
| public interface Mediator { void notify(Component sender, String event); }
|
中介者实现类GUIManager,实现中介者接口,负责协调组件之间的交互:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @AllArgsConstructor public class GUIManager implements Mediator { private Button button; private TextBox textBox; private ComboBox comboBox;
@Override public void notify(Component sender, String event) { if (sender == button && event.equals("click")) { String selectedValue = comboBox.getSelectedValue(); textBox.setText("Selected: " + selectedValue); } else if (sender == textBox && event.equals("change")) { String text = textBox.getText(); comboBox.filterOptions(text); } } }
|
组件类接口及其实现:
在中介者模式中,将Component定义为一个接口是为了强调组件的行为和协议,而不关注具体实现细节。
使用接口的好处是可以实现多重继承,即一个类可以同时实现多个接口。这样,在需要与其他组件进行交互时,组件类可以通过实现Component接口来获得中介者的引用,并实现中介者模式所需的方法。接口的使用还可以帮助实现松耦合,使得组件之间更加灵活和可替换。
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
| public interface Component { void setMediator(Mediator mediator); void notify(String event); }
public class Button implements Component { private Mediator mediator;
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override public void notify(String event) { mediator.notify(this, event); }
public void click() { notify("click"); } }
public class TextBox implements Component { private Mediator mediator;
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override public void notify(String event) { mediator.notify(this, event); }
public void change() { notify("change"); } }
public class ComboBox implements Component { private Mediator mediator;
@Override public void setMediator(Mediator mediator) { this.mediator = mediator; }
@Override public void notify(String event) { mediator.notify(this, event); }
public void filterOptions(String text) { }
public String getSelectedValue() { return null; } }
|
测试示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MediatorExample { public static void main(String[] args) { Button button = new Button(); TextBox textBox = new TextBox(); ComboBox comboBox = new ComboBox();
Mediator mediator = new GUIManager(button, textBox, comboBox);
button.setMediator(mediator); textBox.setMediator(mediator); comboBox.setMediator(mediator);
button.click();
textBox.change(); } }
|
在这个示例中,各个组件通过中介者接口进行交互,而不是直接相互引用。中介者负责协调和处理组件之间的交互逻辑,从而实现了解耦和集中管理的效果。这样,当需要新增、修改或删除交互逻辑时,只需修改中介者类,而不需要修改多个组件类,提高了代码的可维护性和可扩展性。
行为型 - 备忘录(Memento)
当你需要让对象返回之前的状态时,可以使用备忘录模式
示例:恢复游戏存档
Originator(发起人)——游戏类:包含了当前游戏的状态数据,并提供了保存和恢复状态的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @AllArgsConstructor public class Game { private int level; private String checkpoint;
public void play() { level++; checkpoint = "Check Point " + level; System.out.println("Playing game at Level " + level); }
public GameState save() { return new GameState(level, checkpoint); }
public void restore(GameState gameState) { this.level = gameState.getLevel(); this.checkpoint = gameState.getCheckpoint(); System.out.println("Restoring game at Level " + level + ", Check Point: " + checkpoint); } }
|
Memento(备忘录)——游戏状态类:它存储了游戏的状态数据。
1 2 3 4 5 6
| @Data public class GameState { private final int level; private final String checkpoint; }
|
Caretaker(管理者)——游戏管理类:负责保存和提供备忘录对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class GameCareTaker { private List<GameState> gameStateList = new ArrayList<>();
public void saveState(GameState gameState) { gameStateList.add(gameState); }
public GameState restoreState(int index) { if (index >= 0 && index < gameStateList.size()) { return gameStateList.get(index); } return null; } }
|
调用示例:
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
| public class GameSaveDemo { public static void main(String[] args) { Game game = new Game(); GameCareTaker careTaker = new GameCareTaker();
game.play(); careTaker.saveState(game.save());
game.play(); careTaker.saveState(game.save());
GameState savedState1 = careTaker.restoreState(0); if (savedState1 != null) { game.restore(savedState1); game.printState(); }
GameState savedState2 = careTaker.restoreState(1); if (savedState2 != null) { game.restore(savedState2); game.printState(); } } }
|