Java 版设计模式代码案例 (三):行为型设计模式

Java 版设计模式代码案例 (一):创建型设计模式
Java 版设计模式代码案例 (二):结构型设计模式
Java 版设计模式代码案例 (三):行为型设计模式

1. 策略模式(Strategy)

策略模式定义了一系列算法,并将每个算法封装起来,使他们可以相互替换,且算法的变化不会影响到使用算法的客户。

  • 主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
  • 如何解决:将这些算法封装成一个一个的类,任意地替换。
  • 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。

举例实现一个加减的功能:

定义抽象策略角色:这个是一个抽象的角色,通常情况下使用接口或者抽象类去实现。

java 复制代码
public interface Strategy {  
  
    int calc(int num1, int num2);  
  
}

定义具体策略角色:包装了具体的算法和行为。就是实现了 Strategy 接口的实现一组实现类。

java 复制代码
public class AddStrategy implements Strategy {  
  
    @Override  
    public int calc(int num1, int num2) {  
        return num1 + num2;  
    }  
  
}
java 复制代码
public class SubtractStrategy implements Strategy {  
  
    @Override  
    public int calc(int num1, int num2) {  
        return num1 - num2;  
    }  
  
}

定义环境角色: 内部会持有一个抽象角色的引用,给客户端调用。

JAVA 复制代码
public enum CalcEnum {  
  
    ADD(AddStrategy.class),  
    SUBTRACT(SubtractStrategy.class);  

    private Class<? extends Strategy> clazz;  

    CalcEnum(Class<? extends Strategy> clazz) {  
        this.clazz = clazz;  
    }  

    public Class<?> getClazz() {  
        return clazz;  
    }  
  
}
java 复制代码
public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        Strategy strategy1 = (Strategy) CalcEnum.ADD.getClazz().newInstance();  
        int result1 = strategy1.calc(1, 2);  
        System.out.println("1 + 2 = " + result1);  

        Strategy strategy2 = (Strategy) CalcEnum.SUBTRACT.getClazz().newInstance();  
        int result2 = strategy2.calc(5, 3);  
        System.out.println("5 - 3 = " + result2);  
    }  
  
}

运行后输出结果:

java 复制代码
1 + 2 = 3
5 - 3 = 2

策略模式的优点:1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。

策略模式的缺点:1、策略类会增多。 2、所有策略类都需要对外暴露。

2. 模版模式(Template)

定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。

完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。

模版模式涉及的角色:

  • 抽象父类 (AbstractClass):实现了模板方法,定义了算法的骨架。
  • 具体实现类 (ConcreteClass):实现抽象类中的抽象方法,即不同的对象的具体实现细节。

举例一个下厨的抽象模版类:

java 复制代码
public abstract class CookingTemplate {  
  
    protected void doCooking() {  
        this.preHandle();  
        this.doSomething();  
        this.postHandle();  
    }  

    public abstract void preHandle();  

    public abstract void doSomething();  

    public abstract void postHandle();  
  
}

宫爆鸡丁 和 红烧排骨 具体实现了菜该怎么做:

java 复制代码
public class KungPaoChicken extends CookingTemplate {  
  
    @Override  
    public void preHandle() {  
        System.out.println("宫爆鸡丁 做菜前...");  
    }  

    @Override  
    public void doSomething() {  
        System.out.println("宫爆鸡丁 做菜中...");  
    }  

    @Override  
    public void postHandle() {  
        System.out.println("宫爆鸡丁 做菜后...");  
    }  
  
}
java 复制代码
public class StewedSpareRibs extends CookingTemplate {  
  
    @Override  
    public void preHandle() {  
        System.out.println("红烧排骨 做菜前...");  
    }  

    @Override  
    public void doSomething() {  
        System.out.println("红烧排骨 做菜中...");  
    }  

    @Override  
    public void postHandle() {  
        System.out.println("红烧排骨 做菜后...");  
    }  
  
}

运行后输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  
        KungPaoChicken kungPaoChicken = new KungPaoChicken();  
        kungPaoChicken.doCooking();  

        System.out.println("-----------------------------");  

        StewedSpareRibs stewedSpareRibs = new StewedSpareRibs();  
        stewedSpareRibs.doCooking();  
    }  
  
}
java 复制代码
宫爆鸡丁 做菜前...
宫爆鸡丁 做菜中...
宫爆鸡丁 做菜后...
-----------------------------
红烧排骨 做菜前...
红烧排骨 做菜中...
红烧排骨 做菜后...

模板模式的优点:

  1. 具体细节步骤实现定义在子类中,子类定义详细处理算法是不会改变算法整体结构。
  2. 代码复用的基本技术,在数据库设计中尤为重要。
  3. 存在一种反向的控制结构,通过一个父类调用其子类的操作,通过子类对父类进行扩展增加新的行为,符合"开闭原则"。

模板模式的缺点:

  1. 每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。

3. 观察者模式(Observer)

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

  • 主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
  • 如何解决:使用面向对象技术,可以将这种依赖关系弱化。
  • 何时使用:一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

举例一个微信公众号服务,不定时发布一些消息,关注公众号就可以收到推送消息,取消关注就收不到推送消息:

定义一个抽象观察者角色:为所有的具体观察者定义一个接口,在得到主题通知时更新自己。

java 复制代码
public interface Observer {  
  
    void update(String message);  
  
}

定义一个抽象被观察者接口:也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。

java 复制代码
public interface Subject {  
  
    void registerObserver(Observer o);  

    void removeObserver(Observer o);  

    void notifyObserver();  
  
}

定义一个具体被观察者角色:也就是一个具体的主题,在集体主题的内部状态改变时,所有登记过的观察者发出通知。

java 复制代码
public class WechatServer implements Subject {  
  
    private List<Observer> list;  
    private String message;  

    public WechatServer() {  
        list = new ArrayList<Observer>();  
    }  

    @Override  
    public void registerObserver(Observer o) {  
        list.add(o);  
    }  

    @Override  
    public void removeObserver(Observer o) {  
        if (!list.isEmpty()) {  
            list.remove(o);  
        }  
    }  

    @Override  
    public void notifyObserver() {  
        for (Observer o : list) {  
            o.update(message);  
        }  
    }  

    public void sendInformation(String s) {  
        this.message = s;  
        System.out.println("微信服务发送消息: " + s);  
        // 消息更新,通知所有观察者  
        notifyObserver();  
    }  
  
}

定义一个具体观察者角色:实现抽象观察者角色所需要的更新接口,使本身的状态与制图的状态相协调。

java 复制代码
public class WechatUser implements Observer {  
  
    private String name;  
    private String message;  

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

    @Override  
    public void update(String message) {  
        this.message = message;  
        read();  
    }  

    public void read() {  
        System.out.println(name + " 收到推送消息: " + message);  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  

        WechatServer server = new WechatServer();  

        Observer observer1 = new WechatUser("Han Mei");  
        Observer observer2 = new WechatUser("Li Lei");  

        server.registerObserver(observer1);  
        server.registerObserver(observer2);  
        server.sendInformation("微信小程序获取手机号即将收费!");  

        System.out.println("----------------------------------------------");  
        server.removeObserver(observer2);  
        server.sendInformation("JAVA 是世界上最好用的语言!");  

    }  
  
}
java 复制代码
微信服务发送消息: 微信小程序获取手机号即将收费!
Han Mei 收到推送消息: 微信小程序获取手机号即将收费!
Li Lei 收到推送消息: 微信小程序获取手机号即将收费!
----------------------------------------------
微信服务发送消息: JAVA 是世界上最好用的语言!
Han Mei 收到推送消息: JAVA 是世界上最好用的语言!

观察者模式的优点:

  1. 建立一套触发机制,使观察者和被观察者是抽象关联的。

观察者模式的缺点:

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

4. 迭代器模式(Iterator)

提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。

简单来说,不同种类的对象可能需要不同的遍历方式,我们对每一种类型的对象配一个迭代器,最后多个迭代器合成一个。

  • 主要解决:不同的方式来遍历整个整合对象。
  • 如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。
  • 何时使用:遍历一个聚合对象。

举例我们新建一个迭代器接口:

java 复制代码
public interface Iterator {  
  
    boolean hasNext();  

    Object next();  
  
}

建立一个店铺的菜单,利用迭代器进行遍历:

java 复制代码
public class MenuItem {  
  
    private final String name;  
    private final String description;  
    private final float price;  

    public MenuItem(String name, String description, float price) {  
        this.name = name;  
        this.description = description;  
        this.price = price;  
    }  

    public String getName() {  
        return name;  
    }  

    public String getDescription() {  
        return description;  
    }  

    public float getPrice() {  
        return price;  
    }  
  
}
java 复制代码
public class MenuIterator implements Iterator {  
  
    private int position;  
    private final ArrayList<MenuItem> menuItems;  

    public MenuIterator(ArrayList<MenuItem> menuItems) {  
        position = 0;  
        this.menuItems = menuItems;  
    }  

    @Override  
    public boolean hasNext() {  
        return position < menuItems.size();  
    }  

    @Override  
    public Object next() {  
        MenuItem menuItem = menuItems.get(position);  
        position++;  
        return menuItem;  
    }  
  
}
java 复制代码
public class ShopMenu {  
  
    private final ArrayList<MenuItem> menuItems;  

    public ShopMenu() {  
        menuItems = new ArrayList<>();  

        addItem("KFC Cake Breakfast", "boiled eggs & toast&cabbage", 3.99f);  
        addItem("MDL Cake Breakfast", "fried eggs & toast", 3.59f);  
        addItem("Strawberry Cake", "fresh strawberry", 3.29f);  
        addItem("Regular Cake Breakfast", "toast & sausage", 2.59f);  
    }  

    private void addItem(String name, String description, float price) {  
        MenuItem menuItem = new MenuItem(name, description, price);  
        menuItems.add(menuItem);  
    }  

    public ArrayList<MenuItem> getMenuItems() {  
        return menuItems;  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  
        ShopMenu shopMenu = new ShopMenu();  
        MenuIterator iterator = new MenuIterator(shopMenu.getMenuItems());  
        while (iterator.hasNext()) {  
            MenuItem menuItem = (MenuItem) iterator.next();  
            System.out.println(menuItem.getName() + " *** " + menuItem.getPrice() + "$ *** " + menuItem.getDescription());  
        }  
    }  
  
}
java 复制代码
KFC Cake Breakfast *** 3.99$ *** boiled eggs & toast&cabbage
MDL Cake Breakfast *** 3.59$ *** fried eggs & toast
Strawberry Cake *** 3.29$ *** fresh strawberry
Regular Cake Breakfast *** 2.59$ *** toast & sausage

迭代器模式的优点:

  1. 它支持以不同的方式遍历一个聚合对象。
  2. 迭代器简化了聚合类。
  3. 在同一个聚合上可以有多个遍历。
  4. 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

迭代器模式的缺点:

  1. 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

5. 责任链模式(Chain)

如果有多个对象有机会处理请求,责任链可使请求的发送者和接受者解耦,请求沿着责任链传递,直到有一个对象处理了它为止。

  • 主要解决:责任链上的处理者负责处理请求,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递,所以责任链将请求的发送者和请求的处理者解耦了。
  • 如何解决:拦截的类都实现统一接口。
  • 何时使用:在处理消息的时候以过滤很多道。

模拟 tomcat 的 filter 设计一个责任链:

java 复制代码
public interface Filter {  

    void doFilter(FilterChain chain);  
  
}
java 复制代码
public abstract class AbsFilter implements Filter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        chain.doFilter();  
    }  
  
}
java 复制代码
public interface FilterChain {  
  
    void doFilter();  
  
}

我们来实现几个具体的 Filter,并用责任链串起来:

java 复制代码
public class FilterA extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("A过滤器开始工作");  
        super.doFilter(chain);  
    }  
  
}
java 复制代码
public class FilterB extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("B过滤器开始工作");  
        super.doFilter(chain);  
    }  
  
}
java 复制代码
public class FilterC extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("C过滤器开始工作");  
        super.doFilter(chain);  
    }  

}
java 复制代码
public class FilterChainImpl implements FilterChain {  
  
    private List<Filter> filters = new ArrayList<>();  
    private int pos = 0;  

    public FilterChainImpl addFilter(Filter filter) {  
        filters.add(filter);  
        return this;  
    }  

    @Override  
    public void doFilter() {  
        if (pos < filters.size()) {  
        Filter filter = filters.get(pos++);  
        filter.doFilter(this);  
        }  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  
        FilterChain filterChain = new FilterChainImpl()  
            .addFilter(new FilterA())  
            .addFilter(new FilterB())  
            .addFilter(new FilterC());  
        filterChain.doFilter();  
    }  
  
}
java 复制代码
A过滤器开始工作
B过滤器开始工作
C过滤器开始工作

如果我在链条中间中断呢?下面我们来略微修改下 FilterB 的代码:

java 复制代码
public class FilterB extends AbsFilter {  
  
    @Override  
    public void doFilter(FilterChain chain) {  
        System.out.println("B过滤器停止工作");
        // super.doFilter(chain);  
    }  
  
}

是否执行后边的 Filter,取决于当前过滤器处理完成后 chain 的处理方式。

java 复制代码
A过滤器开始工作
B过滤器停止工作

责任链模式的优点:

降低了对象之间的耦合度增强了系统的可扩展性增强了给对象指派职责的灵活性责任链简化了对象之间的连接责任分担。

责任链模式的缺点:

对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

6. 命令模式(Command)

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

  • 主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
  • 如何解决:通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。

举例设计一个开关灯的命令模式代码:

java 复制代码
public class Light {  
  
    String loc = "";  

    public Light(String loc) {  
        this.loc = loc;  
    }  

    public void On() {  
        System.out.println(loc + " On");  
    }  

    public void Off() {  
        System.out.println(loc + " Off");  
    }  
  
}
java 复制代码
public interface Command {  
  
    public void exec();  

    public void undo();  
  
}

我们来实现一个命令,然后设计一个控制器,来控制命令的执行:

java 复制代码
public class TurnOnCommand implements Command {  
  
    private Light light;  

    public TurnOnCommand(Light light) {  
        this.light = light;  
    }  

    @Override  
    public void exec() {  
        light.On();  
    }  

    @Override  
    public void undo() {  
        light.Off();  
    }  
  
}
java 复制代码
public class Controller {  
  
    public void execCommand(Command command) {  
        command.exec();  
    }  

    public void undoCommand(Command command) {  
        command.undo();  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  
        Light light = new Light("energy-saving light");  
        TurnOnCommand command = new TurnOnCommand(light);  
        command.exec();  
        command.undo();  
    }  
  
}
java 复制代码
energy-saving light On
energy-saving light Off

7. 状态模式(State)

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。简单理解,一个拥有状态的 context 对象,在不同的状态下,其行为会发生改变。

  • 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
  • 如何解决:将各种具体的状态类抽象出来。
  • 何时使用:代码中包含大量与对象状态有关的条件语句。

状态模式一般和对象的状态有关,实现类的方法有不同的功能,覆盖接口中的方法。状态模式和命令模式一样,也可以用于消除 if...else 等条件选择语句。

State 抽象状态角色:接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换。

java 复制代码
public interface State {  
  
    void stateA2B(StateContext context);  

    void stateB2C(StateContext context);  

    void stateC2B(StateContext context);  
  
}
java 复制代码
public class StateA implements State {  
  
    @Override  
    public void stateA2B(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态转为: " + StateEnum.B.name());  
        context.setCurrent(StateEnum.B);  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态不匹配,无法转变! ");  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态不匹配,无法转变! ");  
    }  
  
}
java 复制代码
public class StateB implements State {  
  
    @Override  
    public void stateA2B(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态不匹配,无法转变! ");  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态转为: " + StateEnum.C.name());  
        context.setCurrent(StateEnum.C);  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态不匹配,无法转变! ");  
    }  
  
}
java 复制代码
public class StateC implements State {  
  
    @Override  
    public void stateA2B(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态不匹配,无法转变! ");  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态不匹配,无法转变! ");  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        System.out.println("当前状态: " + context.getCurrent().name() + " 状态转为: " + StateEnum.B.name());  
        context.setCurrent(StateEnum.B);  
    }  
  
}
java 复制代码
public enum StateEnum {  
  
    A ("已付款", StateA.class),  
    B ("已发货", StateB.class),  
    C ("已退货", StateC.class);  

    private String desc;  
    private Class<? extends State> clazz;  

    StateEnum(String desc, Class<? extends State> clazz) {  
        this.desc = desc;  
        this.clazz = clazz;  
    }  

    public String getDesc() {  
        return desc;  
    }  

    public Class<? extends State> getClazz() {  
        return clazz;  
    }  
  
}

Context 环境角色:定义客户端需要的接口,并且负责具体状态的切换。

java 复制代码
public class StateContext {  
  
    private StateEnum current;  

    public StateContext(StateEnum current) {  
        this.current = current;  
    }  

    public StateEnum getCurrent() {  
        return current;  
    }  

    public void setCurrent(StateEnum current) {  
        this.current = current;  
    }  
  
}
java 复制代码
public class StateHandler implements State {  
  
    public static final Map<String, State> stateMap = new HashMap<>(StateEnum.values().length);  

    public static StateEnum initState() {  
        return StateEnum.A;  
    }  

    public StateHandler() throws Exception {  
        for (StateEnum stateEnum : StateEnum.values()) {  
            stateMap.put(stateEnum.name(), stateEnum.getClazz().newInstance());  
        }  
    }  

    @Override  
    public void stateA2B(StateContext context) {  
        State state = stateMap.get(context.getCurrent().name());  
        state.stateA2B(context);  
    }  

    @Override  
    public void stateB2C(StateContext context) {  
        State state = stateMap.get(context.getCurrent().name());  
        state.stateB2C(context);  
    }  

    @Override  
    public void stateC2B(StateContext context) {  
        State state = stateMap.get(context.getCurrent().name());  
        state.stateC2B(context);  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) throws Exception {  
        StateContext context = new StateContext(StateHandler.initState());  
        StateHandler stateHandler = new StateHandler();  
        stateHandler.stateA2B(context);  
        stateHandler.stateA2B(context);  
        stateHandler.stateB2C(context);  
        stateHandler.stateC2B(context);  
    }  
  
}
java 复制代码
当前状态: A 状态转为: B
当前状态: B 状态不匹配,无法转变! 
当前状态: B 状态转为: C
当前状态: C 状态转为: B

状态模式的优点:

  1. 封装了转换规则。
  2. 枚举可能的状态,在枚举状态之前需要确定状态种类。
  3. 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  4. 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  5. 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

状态模式的缺点:

  1. 状态模式的使用必然会增加系统类和对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  3. 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

状态模式 和 状态机 的区别

状态模式 :一种程序设计模式。
状态机:通常指 FSM (有限状态机),是一个数学模型,是一种抽象机器,用状态图、状态转换图) 表示。

数学领域中的状态机:

  • 状态机如同大多数数学模型一样,并不是编程领域特有的名词,而是为了解决某些问题而提出的数学模型。
  • 状态机描述了在任何给定时间都处于某一个状态的计算机,它可以响应某些输入/触发/事件而从一种状态更改为另一种状态,它的关注点是状态及其转换。
  • 许多工程和数学专业的学生已经了解了状态机,但他们在编程领域几乎没有受过教育,在他们看来,状态机是一个用"状态图"表示的数学模型、抽象机器。

编程领域中的状态模式:

  • 状态模式是编程领域特有的名词,是一种设计模式。设计模式是为了更好地规范代码设计结构,以便于封装、复用、易于扩展;其中状态模式用来解决 对象根据自己的状态来展现出不同的行为。
  • 状态模式是状态机的一种实现方式,但通常都是以状态模式的思路来实现状态机。
  • 当你要用状态模式实现一个功能的时候,这个功能结构肯定不适合被称为"状态模式",而更适合称为"状态机"。这只是一个词性的区别,就像"一碗米饭"要"用碗盛"一样。
  • 然而状态模式和状态机的存在着很强的关联性,就像"一碗米饭"必须"用碗盛"一样,目前没看到有其它好的实现方式。
  • 所以有些程序员认为状态机是"运行中的状态模式",这无可厚非。

8. 备忘录模式(Memento)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

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

java 复制代码
public class Memento {  

    private String state;  

    public Memento(String state) {  
        this.state = state;  
    }  

    public String getState() {  
        return state;  
    }  

    public void setState(String state) {  
        this.state = state;  
    }  
  
}

发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。

java 复制代码
public class Originator {  
  
    private String state;  

    public Memento createMemento() {  
        return new Memento(state);  
    }  

    public void restoreMemento(Memento memento) {  
        this.state = memento.getState();  
    }  

    public String getState() {  
        return state;  
    }  

    public void setState(String state) {  
        this.state = state;  
    }  
  
}

管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

java 复制代码
public class Caretaker {  
  
    private List<Memento> mementoList = new ArrayList<>();  

    public void setMemento(Memento memento) {  
        mementoList.add(memento);  
    }  

    public Memento getMemento() {  
        Memento memento = null;  
        if (mementoList.size() > 0) {  
            memento = mementoList.get(mementoList.size() - 1);  
            // 恢复后移除这个状态  
            mementoList.remove(mementoList.size() - 1);  
        }  
        return memento;  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  

        Caretaker caretaker = new Caretaker();  
        Originator originator = new Originator();  

        // 管理者使用备忘录记录状态  
        originator.setState("状态1");  
        caretaker.setMemento(originator.createMemento());  

        originator.setState("状态2");  
        caretaker.setMemento(originator.createMemento());  

        // 修改原发器的状态  
        originator.setState("状态3");  
        System.out.println("当前合同状态:" + originator.getState());  

        // 恢复备忘录中存的状态  
        originator.restoreMemento(caretaker.getMemento());  
        System.out.println("回退上一合同状态:" + originator.getState());  

        originator.restoreMemento(caretaker.getMemento());  
        System.out.println("回退上一合同状态:" + originator.getState());  
    }  
  
}
java 复制代码
当前合同状态:状态3
回退上一合同状态:状态2
回退上一合同状态:状态1

9. 访问者模式(Visitor)

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。

访问者模式 的基本想法是,软件系统中拥有一个由许多对象构成的、比较稳定的对象结构,这些对象的类都拥有一个 accept 方法用来接受访问者对象的访问。访问者是一个接口,它拥有一个 visit 方法,这个方法对访问到的对象结构中不同类型的元素做出不同的处理。

举例,对于元素资源纸张和金属铜,不同的访问者公司利用,会使用不同的用途:

抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。

java 复制代码
public interface Element {  

    String accept(Visitor visitor);  
  
}

具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。

java 复制代码
public class Paper implements Element {  
  
    @Override  
    public String accept(Visitor visitor) {  
        return visitor.create(this);  
    }  
  
}
java 复制代码
public class Copper implements Element {  
  
    @Override  
    public String accept(Visitor visitor) {  
        return visitor.create(this);  
    }  
  
}

抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。

java 复制代码
public interface Visitor {  
  
    String create(Paper element);  

    String create(Copper element);  
  
}

具体访问者(ConcreteVisitor)角色: 具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。

java 复制代码
public class ArtVisitor implements Visitor {  
  
    @Override  
    public String create(Paper element) {  
        return "打印广告";  
    }  

    @Override  
    public String create(Copper element) {  
        return "制作铜像";  
    }  
  
}
java 复制代码
public class MintVisitor implements Visitor {  
  
    @Override  
    public String create(Paper element) {  
        return "铸造纸币";  
    }  

    @Override  
    public String create(Copper element) {  
        return "铸造铜币";  
    }  
  
}

在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施 accept 方法,在每一个元素的 accept 方法中会调用访问者的 visit 方法,从而使访问者得以处理对象结构的每一个元素,我们可以针对对象结构设计不同的访问者类来完成不同的操作,达到区别对待的效果。

java 复制代码
public class ElementSet {  
  
    private List<Element> list = new ArrayList<>();  

    public String accept(Visitor visitor) {  
        Iterator<Element> iterator = list.iterator();  
        StringBuilder result = new StringBuilder();  
        while (iterator.hasNext()) {  
            result.append(iterator.next().accept(visitor)).append(" ");  
        }  
        return result.toString();  
    }  

    public void add(Element element) {  
        list.add(element);  
    }  

    public void remove(Element element) {  
        list.remove(element);  
    }  
  
}
java 复制代码
public class MainTest {  
  
    public static void main(String[] args) {  
        ElementSet es = new ElementSet();  
        es.add(new Paper());  
        es.add(new Copper());  

        Visitor artVisitor = new ArtVisitor();  
        System.out.println(artVisitor.getClass().getSimpleName() + " " + es.accept(artVisitor));  

        System.out.println("==========================");  

        Visitor mintVisitor = new MintVisitor();  
        System.out.println(mintVisitor.getClass().getSimpleName() + " " + es.accept(mintVisitor));
    }  
  
}
java 复制代码
ArtVisitor 打印广告 制作铜像 
==========================
MintVisitor 铸造纸币 铸造铜币 

访问者模式的优点:

  1. 扩展性好,能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好,可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好,访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则,访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者模式的缺点:

  1. 增加新的元素类很困难,在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了"开闭原则"。
  2. 破坏封装,访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则,访问者模式依赖了具体类,而没有依赖抽象类。

10. 中介者模式(Mediator)

定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

在生活中,当我们租房获取房源信息,假设是房东直接和租户联系,那么,当某一个房东有房源信息时,就需要通知所有的租户当某一个人需要租房时租房时,就要就要咨询每一个房东相关房源信息,这样子一个发生变化时,很多都会发生变化。

这个时候就可以想想可不可以有一个房屋中介,所有房东将所有的房源信息告诉中介,这样子当有租客想要租房时直接找房屋中介,就可以获取所有的房源信息了,就不需要咨询每一个房东了。同样的,当某个房东有新的房源信息时,就可以直接告诉房屋中介,就不需要一次告诉所有的需要租房的人了。

抽象工作者(Mediator):定义了中介者对象的接口,负责定义对象间的通信协议。

java 复制代码
public interface Mediator {  
  
    void publicHouse(String message);  

    void needHouse(String message);  
  
}

抽象同事类(Colleague):定义了同事对象的接口,负责定义对象间的行为。

java 复制代码
public abstract class Person {  
  
    private String name;  
    private Mediator mediator;  

    public String getName() {  
    return name;  
    }  

    public void setName(String name) {  
    this.name = name;  
    }  

    public Mediator getMediator() {  
    return mediator;  
    }  

    public void setMediator(Mediator mediator) {  
    this.mediator = mediator;  
    }  

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

    /**  
    * 发布信息抽象方法,具体的实现有继承的具体同事声明  
    **/  
    public abstract void publishMessage(String msg);  
  
}

具体中介者(Concrete Mediator):实现了中介者接口,负责协调各个同事对象之间的交互关系。

java 复制代码
public class HouseAgent implements Mediator {  
  
    private List<HouseOwner> houseOwners = new ArrayList<>();  
    private List<HouseTenant> houseTenants = new ArrayList<>();  

    /**  
    * 中介发布房屋信息时,就向所有的租客传递房源信息  
    **/  
    @Override  
    public void publicHouse(String message) {  
        if (!this.houseTenants.isEmpty()) {  
            for (HouseTenant tenant : this.houseTenants) {  
                tenant.contact(message);  
            }  
        }  
    }  

    /**  
    * 当有人需要房子时,中介就向所有的房东传递信息  
    **/  
    @Override  
    public void needHouse(String message) {  
        if (!this.houseOwners.isEmpty()) {  
            for (HouseOwner owner : this.houseOwners) {  
                owner.contact(message);  
            }  
        }  
    }  

    public void setHouseOwners(HouseOwner... houseOwners) {  
        this.houseOwners.addAll(Arrays.asList(houseOwners));  
    }  

    public void setHouseTenants(HouseTenant... houseTenants) {  
        this.houseTenants.addAll(Arrays.asList(houseTenants));  
    }  
  
}

具体同事类(Concrete Colleague):实现了同事接口,负责实现具体的行为。

java 复制代码
public class HouseOwner extends Person {  
  
    public HouseOwner(String name) {  
        super(name);  
    }  

    @Override  
    public void publishMessage(String msg) {  
        getMediator().publicHouse(msg);  
    }  

    public void contact(String message) {  
        System.out.println("中介通知房东," + getName() + ":" + message);  
    }  
  
}
java 复制代码
public class HouseTenant extends Person {  
  
    public HouseTenant(String name) {  
        super(name);  
    }  

    @Override  
    public void publishMessage(String msg) {  
        getMediator().needHouse(msg);  
    }  

    public void contact(String message) {  
        System.out.println("中介通知租户," + getName() + ":" + message);  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  

    public static void main(String[] args) {  
        // 创建一个中介者  
        HouseAgent mediator = new HouseAgent();  
        // 创建两个房东信息  
        HouseOwner owner1 = new HouseOwner("房东1");  
        HouseOwner owner2 = new HouseOwner("房东2");  

        // 创建三个租户信息  
        HouseTenant tenant1 = new HouseTenant("租户1");  
        HouseTenant tenant2 = new HouseTenant("租户2");  
        HouseTenant tenant3 = new HouseTenant("租户3");  

        // 向中介者添加房东信息  
        mediator.setHouseOwners(owner1, owner2);  
        // 向中介者添加租户信息  
        mediator.setHouseTenants(tenant1, tenant2, tenant3);  
        // 中介者发布房源信息,会依次通知每一个租户  
        mediator.publicHouse("在清水河有一个一居室,500元 每月");  
        // 中介者发布租户需求,会依次通知每一个房东  
        mediator.needHouse("我需要一个清水河的房子");  
    }  

}
java 复制代码
中介通知租户,租户1:在清水河有一个一居室,500元 每月
中介通知租户,租户2:在清水河有一个一居室,500元 每月
中介通知租户,租户3:在清水河有一个一居室,500元 每月
中介通知房东,房东1:我需要一个清水河的房子
中介通知房东,房东2:我需要一个清水河的房子

中介者模式的优点:

  1. 降低了对象之间的耦合性,使得对象易于独立地被复用。
  2. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

中介者模式的缺点:

  1. 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。

11. 解释器模式(Interpreter)

提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。

  • 主要解决:对于一些固定文法构建一个解释句子的解释器。
  • 如何解决:构建语法树,定义终结符与非终结符。
  • 何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

创建一个表达式接口,并创建实现了上述接口的实体类。

java 复制代码
public interface Expression {  
  
    boolean interpret(String context);  
  
}
java 复制代码
public class TerminalExpression implements Expression {  
  
    private String data;  

    public TerminalExpression(String data) {  
        this.data = data;  
    }  

    @Override  
    public boolean interpret(String context) {  
        return context.contains(data);  
    }  
  
}
java 复制代码
public class AndExpression implements Expression {  
  
    private Expression expr1;  
    private Expression expr2;  

    public AndExpression(Expression expr1, Expression expr2) {  
        this.expr1 = expr1;  
        this.expr2 = expr2;  
    }  

    @Override  
    public boolean interpret(String context) {  
        return expr1.interpret(context) && expr2.interpret(context);  
    }  
  
}
java 复制代码
public class OrExpression implements Expression {  
  
    private Expression expr1;  
    private Expression expr2;  

    public OrExpression(Expression expr1, Expression expr2) {  
        this.expr1 = expr1;  
        this.expr2 = expr2;  
    }  

    @Override  
    public boolean interpret(String context) {  
        return expr1.interpret(context) || expr2.interpret(context);  
    }  
  
}

编写一个测试类,观察输出结果:

java 复制代码
public class MainTest {  
  
    // 规则:Robert 和 John 是男性  
    public static Expression getMaleExpression() {  
        Expression robert = new TerminalExpression("Robert");  
        Expression john = new TerminalExpression("John");  
        return new OrExpression(robert, john);  
    }  

    // 规则:Julie 是一个已婚的女性  
    public static Expression getMarriedWomanExpression() {  
        Expression julie = new TerminalExpression("Julie");  
        Expression married = new TerminalExpression("Married");  
        return new AndExpression(julie, married);  
    }  

    public static void main(String[] args) {  
        Expression isMale = getMaleExpression();  
        Expression isMarriedWoman = getMarriedWomanExpression();  

        System.out.println("John 是男性吗? " + isMale.interpret("John"));  
        System.out.println("Julie 是已经女性吗? " + isMarriedWoman.interpret("Married Julie"));  
    }  
  
}
java 复制代码
John 是男性吗? true
Julie 是已经女性吗? true

解释器模式的优点:1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。

解释器模式的缺点:1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。

Java 版设计模式代码案例 (一):创建型设计模式
Java 版设计模式代码案例 (二):结构型设计模式
Java 版设计模式代码案例 (三):行为型设计模式

相关推荐
hai4058720 分钟前
Spring Boot中的响应与分层解耦架构
spring boot·后端·架构
陈大爷(有低保)39 分钟前
UDP Socket聊天室(Java)
java·网络协议·udp
nakyoooooo43 分钟前
【设计模式】工厂模式、单例模式、观察者模式、发布订阅模式
观察者模式·单例模式·设计模式
kinlon.liu1 小时前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
王哲晓1 小时前
Linux通过yum安装Docker
java·linux·docker
java6666688881 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存1 小时前
源码分析:LinkedList
java·开发语言
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
Jarlen2 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽2 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode