欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

设计模式下篇-行为型

程序员文章站 2022-06-17 17:21:43
...

行为型主要解决类或对象之间交互的经典结构。行为型的设计模式有观察者模式,模板模式,策略模式,职责链模式,状态模式,迭代器模式,访问者模式,备忘录模式,命令模式,解释器模式和中介模式。

接下来以3个W和1个H来学习下这十一种设计模式

十一种模式介绍

观察者模式-Observer

什么是观察者模式

在对象之间定义一个一对多的依赖,当一个对象(被观察者)状态改变的时候,所有依赖的对象(观察者)都会自动收到通知。

为什么使用观察者模式

降低被观察者和观察者的耦合性,提高易用性。

如何使用观察者模式

被观察者,该被观察者有一组注册的观察者,当被观察者改变时,通知每一个观察者

// 观察者
public interface Observer {
    
    void update(Observable o, Object arg);
}

// 被观察者的封装类
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    
    // something
    
    public void notifyObservers(Object arg) {
        
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    // something
    
    protected synchronized void setChanged() {
        changed = true;
    }

}

// 被观察者
public class MyObservable extends Observable {

    public synchronized void setChanged() {
        super.setChanged();
    }
}

// 调用类
public class Main {

    public static void main(String[] args) {
        // java.util.Observable
        MyObservable observable = new MyObservable();
        observable.addObserver(new java.util.Observer() {
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("A"+arg.toString());
                o.deleteObserver(this);
            }
        });

        observable.addObserver(new java.util.Observer() {
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("B"+arg.toString());
            }
        });

        observable.setChanged();
        observable.notifyObservers("message1");
        observable.setChanged();
        observable.notifyObservers("message2");

    }
}

// 打印如下:
Bmessage1
Amessage1
Bmessage2

额外学习

在jdk9,Observable被废弃,使用java.beans包下的PropertyChangeEvent和PropertyChangeListener来代替Observer和Observable的功能。

使用观察者模式实现简单的EventBus,git仓库:https://github.com/CNXMBuyu/design-pattern-study.git

  • 改进方案-消息队列

设计模式核心是对程序进行解耦,在观察者模式中被观察者需要添加观察者,且当发起通知时要遍历所有的观察者。为了使解耦的更加彻底,就出现了消息队列。

模板模式-Template

什么是模板模式

将算法整体架构定义好,让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。

为什么使用模板模式

代码复用

如何使用模板模式

定义算法整体架构(抽象类),将需要子类来实现的方法定义为抽象方法。

public abstract class Player {

    public void initPlayer() {
        // 保存玩家信息
        savePlayer();
        // 初始化玩家数据,因为每款游戏都不同,所以需要有子类具体实现
        initData();
    }

    protected void savePlayer() {
        System.out.println("已保存玩家信息");
    }

    /**
     * 初始化玩家数据
     */
    protected abstract void initData();

}

public class GamePlayer extends Player {

    @Override
    protected void initData() {
        System.out.println("初始化游戏玩家");
    }
}

相似功能:回调

A类事先注册某个函数F到B类,A类在调用B类的P函数的时候,B类反过来调用A类注册给它的F函数。

public class Player {

    public void initPlayer(PlayerCallback playerCallback){
        // 保存玩家信息
        savePlayer();
        // 初始化玩家数据,因为每款游戏都不同,所以需要有子类具体实现
        playerCallback.initData();
    }

    protected void savePlayer(){
        System.out.println("已保存玩家信息");
    }
}

public interface PlayerCallback {

    /**
     * 初始化玩家数据
     */
    void initData();
}

// 调用类
public class Main {

    public static void main(String[] args) {
        Player player = new Player();
        player.initPlayer(new PlayerCallback() {
            @Override
            public void initData() {
                System.out.println("初始化游戏玩家数据");
            }
        });
    }
}

回调函数的经典案例:spring的JdbcTemplate

模板模板是使用继承来实现,而回调函数是使用组合来实现。

策略模式-Strategy

什么是策略模式

策略模式就是定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。

为什么使用策略模式

解耦策略的定义、创建、使用这三部分

如何使用策略模式

分为定义、创建、使用三个步骤。


// 以下为策略的定义

// 策略接口
public interface Cache {

    void addCache(String key, String content);

    String getCache(String key);
}


// 策略的实现者
public class MemoryCache implements Cache {

    private ConcurrentMap<String, String> cache = new ConcurrentHashMap<>();

    @Override
    public void addCache(String key, String content) {
        cache.put(key, content);
        System.out.println("memory add cache; key:" + key + ", content:" + content);
    }

    @Override
    public String getCache(String key) {
        System.out.println("memory get key:" + key + ", content:" + cache.get(key));
        return cache.get(key);
    }
}

// 策略的实现者
public class RedisCache implements Cache {

    @Override
    public void addCache(String key, String content) {
        System.out.println("redis add cache; key:" + key + ", content:" + content);
    }

    @Override
    public String getCache(String key) {
        String content = "redis get key:" + key;
        System.out.println(content);
        return content;
    }
}

// 策略的创建
public class CacheFactory {

    public Cache getCache(String type){
        String redisType = "redis";
        if(redisType.equals(type)){
            return RedisCacheSingleton.INSTANCE;
        } else {
            return MemoryCacheSingleton.INSTANCE;
        }
    }

    /**
     * 使用内部类的单例
     */
    private static class RedisCacheSingleton{
        private static RedisCache INSTANCE = new RedisCache();
    }

    /**
     * 使用内部类的单例
     */
    private static class MemoryCacheSingleton{
        private static MemoryCache INSTANCE = new MemoryCache();
    }
}

// 策略的使用
public class Main {

    public static void main(String[] args) {
        Cache redisCache = new CacheFactory().getCache("redis");
        redisCache.addCache("a", "a content");
        redisCache.getCache("a");

        Cache memoryCache = new CacheFactory().getCache("memory");
        memoryCache.addCache("a", "a content");
        memoryCache.getCache("a");
    }
}

职责链模式-Chain

什么是职责链模式

职责链模式是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。

为什么使用职责链模式

将请求的发送和接收解耦。

如何使用职责链模式

public abstract class AbstractScript{

    /**
     * 执行初始化
     */
    public abstract void doInit();
}

public class ScriptA extends  AbstractScript {

    @Override
    public void doInit() {
        System.out.println(this.getClass().getName());
    }
}

public class ScriptB extends  AbstractScript {

    @Override
    public void doInit() {
        System.out.println(this.getClass().getName());
    }
}


// 这里对职责器模式进行了一个小的修改,职责器模式定义“直到链上的某个接收对象能够处理它为止”,而我们处理它之后依然没有停止,知道执行结束
public class ScriptChain {

    public void init(List<AbstractScript> initScriptList) {
        // 排序
        Collections.sort(initScriptList);
        // 遍历执行所有脚本
        for (AbstractScript script : initScriptList) {
            if (script.filter(predicate -> {
                return true;
            })) {
                script.doInit();
            }
        }
    }
}

状态模式-State

什么是状态模式

状态机又叫有限状态机,由3个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。

为什么使用状态模式

当状态条状触发的动作比较复杂时,使用状态模式,可以提高代码的可维护性。

如何使用状态模式

订单有5个状态,分别为待付款,已关闭,已付款,已退款,已完成。事件有同意和拒绝。示例代码简单没有动作。

public class Order {

    private OrderStateEnum status =  OrderStateEnum.unpaid;

    public OrderStateEnum getStatus() {
        return status;
    }

    public void setStatus(OrderStateEnum status) {
        this.status = status;
    }
}

public enum OrderStateEnum {

    unpaid, closed, paid, refund, finish
}


// 状态接口
public interface OrderState {

    default void agree(OrderStateMachine orderStateMachine){
        // nothing
    }

    default void reject(OrderStateMachine orderStateMachine){
        // nothing
    }
}

// 待付款的状态改变
public class UnpaidOrderState implements OrderState {

    @Override
    public void agree(OrderStateMachine orderStateMachine) {
        // 处理已付款的相关事项
        // .....
        // 状态调整
        orderStateMachine.setState(new PaidOrderState());
    }

    @Override
    public void reject(OrderStateMachine orderStateMachine) {
        orderStateMachine.setState(new ClosedOrderState());
    }
}

// 已付款的状态改变
public class PaidOrderState implements OrderState {

    @Override
    public void agree(OrderStateMachine orderStateMachine) {
        orderStateMachine.setState(new FinishOrderState());
    }

    @Override
    public void reject(OrderStateMachine orderStateMachine) {
            orderStateMachine.setState(new RefundedOrderState());
    }
}

// 已退款-最终状态,没有任何操作
public class RefundedOrderState implements OrderState {}

// 已关闭-最终状态,没有任何操作
public class ClosedOrderState implements OrderState {}

// 已完成-最终状态,没有任何操作
public class FinishOrderState implements OrderState {}

// 状态机
public class OrderStateMachine {

    private OrderState state;

    private static Map<OrderStateEnum, OrderState> orderStateMap = new HashMap<>(10);

    static{
        orderStateMap.put(OrderStateEnum.unpaid, new UnpaidOrderState());
        orderStateMap.put(OrderStateEnum.closed, new ClosedOrderState());
        orderStateMap.put(OrderStateEnum.paid, new UnpaidOrderState());
        orderStateMap.put(OrderStateEnum.refund, new RefundedOrderState());
        orderStateMap.put(OrderStateEnum.finish, new FinishOrderState());
    }

    public OrderStateMachine(Order order){
        state = orderStateMap.get(order.getStatus());
    }

    public void agree(){
        state.agree(this);
    }

    public void reject(){
        state.reject(this);
    }

    public OrderState getState() {
        return state;
    }

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

// 调用代码
public class Main {

    public static void main(String[] args) {
        Order order = new Order();
        OrderStateMachine orderStateMachine = new OrderStateMachine(order);
        orderStateMachine.agree();
        System.out.println(orderStateMachine.getState().toString());
        orderStateMachine.agree();
        System.out.println(orderStateMachine.getState().toString());
    }
}

// 打印日志如下:
cn.hgy.state.PaidOrderState@136432db
cn.hgy.state.FinishOrderState@7382f612

迭代器模式-Iterator

什么是迭代器模式

迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。

为什么使用迭代器模式

  • 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
  • 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
  • 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。

如何使用迭代器模式

定义迭代器接口和具体的实现。

public interface Iterator<E> {
    
    boolean hasNext();
    
    E next();
}

访问者模式-Visitor

什么是访问者模式

访问者模式允许一个或者多个操作应用到一组对象上。

为什么使用访问者模式

解耦操作和对象本身,保持类职责单一、满足开闭原则以及应对代码的复杂性。

如何使用访问者模式

public abstract class AbstractFile {

    public abstract void read(IRead read);
}

public class Doc extends AbstractFile {
    @Override
    public void read(IRead read) {
        read.read(this);
    }
}

public class Pdf extends  AbstractFile {
    @Override
    public void read(IRead read) {
        read.read(this);
    }
}

public interface IRead {

    void read(Doc doc);

    void read(Pdf pdf);
}

public class ReadImpl implements IRead {
    @Override
    public void read(Doc doc) {
        System.out.println("doc");
    }

    @Override
    public void read(Pdf pdf) {
        System.out.println("pdf");
    }
}

public class Main {

    public static void main(String[] args) {
        List<AbstractFile> fileList = new ArrayList<>();
        fileList.add(new Doc());
        fileList.add(new Pdf());
        IRead read = new ReadImpl();
        fileList.forEach(abstractFile -> {
            // 编译会失败,所以改用abstractFile.read(read);
            // read.read(abstractFile);
            abstractFile.read(read);
        });
    }
}

备忘录模式-Memento

什么是备忘录模式

备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。

命令模式-Command

什么是命令模式

命令模式将请求封装为一个对象,这样可以使用不同的请求参数化其他对象,并且能够支持请求的排队执行、记录日志、撤销等附加功能。

为什么使用命令模式

解耦行为请求者与行为实现者。

在大部分编程语言中,函数是没法作为参数传递给其他函数的,也没法赋值给变量。借助命令模式,我们将函数封装成对象,这样就可以实现把函数像对象一样使用。

如何使用命令模式


public interface Command {
    
    void execute();
}

// 灯
public class Light {

    public void on() {
        System.out.println("灯亮了");
    }
    
    public void off() {
        System.out.println("灯暗了");
    }
}

// 开灯的命令
public class LightOnCommand implements Command {

    private Light light;

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

    @Override
    public void execute() {
        light.on();
    }
}

// 关灯的命令
public class LightOffCommand implements Command {

    private Light light;

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

    @Override
    public void execute() {
        light.off();
    }
}

// 使用类
public class Main {

    public static void main(String[] args) {
        Queue<Command> request = new LinkedList<>();
        Light light = new Light();
        request.add(new LightOnCommand(light));
        request.add(new LightOffCommand(light));

        request.forEach(command -> {
            command.execute();
        });
    }
}

解释器模式-Interpreter

什么是解释器模式

解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。

中介模式-Mediator

什么是中介模式

中介模式定义了一个单独的对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。

总结

观察者模式

观察者将自己注册到被观察者中,当被观察者发生改变时,将改变推送给每一个被观察中。

可以通过框架或者反射解决注册到被观察者中,或者使用消息队列解耦观察者和被观察者;可以通过异步执行观察者函数。

模板模式

定义一套流程,将其中部分功能放在子类实现。

模板模式需要继承实现,可以通过回调来实现类似功能,解决继承问题。

策略模式

定义一组功能相似的算法实现,可以互相替换。

职责链模式

职责链模式是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。

状态模式

适用于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机。

迭代器模式

迭代器模式用来遍历集合对象。解耦容器代码和遍历代码。

访问者模式

访问者模式允许一个或者多个操作应用到一组对象上。

备忘录模式

备忘录模式也叫快照模式,具体来说,就是在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。

命令模式

命令模式将请求封装为一个对象,这样可以使用不同的请求参数化其他对象,并且能够支持请求的排队执行、记录日志、撤销等附加功能。

解释器模式

解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法

中介模式

中介模式定义了一个单独的对象,来封装一组对象之间的交互。将这组对象之间的交互委派给与中介对象交互,来避免对象之间的直接交互。

设计模式下篇-行为型