设计模式 四、行为设计模式(1)

        在设计模式的世界里,23种经典设计模式通常被分为三大类:创建型、结构型和行为型。创建型设计模式关注对象创建的问题,结构性设计模式关注于类或对象的组合和组装的问题,行为型设计模式则主要关注于类或对象之间的交互问题。

        行为设计模式 的数量较多,共有11种,几乎占据了23种经典设计模式的一半。这些模式分别为:观察者模式、模板模式、策略模式、职责链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式和中介模式。

一、观察者模式

        1、概述

        观察者模式是一种行为设计模式,允许对象间存在一对多的依赖关系当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新,在这种设计模式种,发生状态改变的对象被称为“主题”(Subject),依赖它的对象成为“观察者”(Observer)。

        观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)。在GoF的设计模式书中,它的定义是这样的:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

        翻译成中文就是说:在对象之间定义一个一对多的依赖,当一个对象状态改变的时候,所有依赖的对象都会自动收到通知。

        一般情况下,被依赖的对象叫作被观察者(Observable),依赖的对象叫作观察者(Observer)。不过,在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer 、Publisher-Subscriber、Producer-Consumer等等。不管怎么称呼,只要应用场景符合刚刚给出的定义,都可以看作观察者模式。

        简单例子:假设我们有一个气象站,需要向许多不同的显示设备(如手机App、网站、电子屏幕等)提供实施天气数据。

         首先我们创建一个Subject接口,表示主题:

    public interface Subject {
        void registerObserver(Observer o);
        void removeObserver(Observer o);
        void notifyObservers();
    }

        接下来,创建一个Observer接口,表示观察者:

public interface Observer {
    void update(float temperature, float humidity, float pressure);
}

        创建一个具体的主体,如WeatherStation,实现Subject接口:

public class WeatherStation implements Subject {
    private ArrayList<Observer> observers;
    // 温度
    private float temperature;
    // 湿度
    private float humidity;
    // 大气压
    private float pressure;
    public WeatherStation() {
        observers = new ArrayList<>();
    }
    // 注册一个观察者的方法
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    // 移除一个观察者的方法
    @Override
    public void removeObserver(Observer o) {
        int index = observers.indexOf(o);
        if (index >= 0) {
            observers.remove(index);
        }
    }
    // 通知所有的观察者
    @Override
    public void notifyObservers() {
        // 循环所有的观察者,通知其当前的气象信息
        for (Observer o : observers) {
            o.update(temperature, humidity, pressure);
        }
    }
    // 修改气象内容
    public void measurementsChanged() {
        notifyObservers();
    }
    // 当测量值发生了变化的时候
    public void setMeasurements(float temperature, float humidity, float
            pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        // 测量值发生了变化
        measurementsChanged();
    }
}

         最后我们创建一个具体的观察者,如PhoneAPP,实现Observer接口:

public class PhoneApp implements Observer {
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherStation;
    public PhoneApp(Subject weatherStation) {
        this.weatherStation = weatherStation;
        weatherStation.registerObserver(this);
    }
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
    public void display() {
        System.out.println("PhoneApp: Temperature: " + temperature + "°C,
                Humidity: " + humidity + "%, Pressure: " + pressure + " hPa");
    }
}

        现在我们可以创建一个WeatherStation实例并向其注册PhoneApp观察者。当WeatherStation的数据发生变化时,PhoneApp会收到通知并更新自己的显示。

public class Main {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();
        PhoneApp phoneApp = new PhoneApp(weatherStation);
        // 模拟气象站数据更新
        weatherStation.setMeasurements(25, 65, 1010);
        weatherStation.setMeasurements(22, 58, 1005);
        // 添加更多观察者 网站上显示-电子大屏
        WebsiteDisplay websiteDisplay = new WebsiteDisplay(weatherStation);
        ElectronicScreen electronicScreen = new ElectronicScreen(weatherStation);
        // 再次模拟气象站数据更新
        weatherStation.setMeasurements(18, 52, 1008);
    }
}

        这个例子中,我们创建了一个WeatherStation实例,并向其注册了PhoneApp、WebsiteDisplay和ElectronicScreen观察者,当WeatherStation的数据发生变化这个例子展示了观察者模式的优点:
        1)观察者和主题之间解耦:主题只需要知道观察者实现了Observer接口,而无需了解具体的实现细节。
        2)可以动态的添加和删除观察者:通过调用registerObserver 和 removeObserver方法,可以在运行时添加和删除观察者。
        3)主题和观察者之间的通信时自动的:当主题的状态发生变化时,观察者会自动得到通知并更新自己的状态。

        观察者广泛应用于各种场景,例如事件处理系统、数据同步和更新通知等。上面例子算是观察者模式的 “模板代码”,可以反应该模式大体得设计思路,在真实得软件开发中,并不需要照搬相面的模板代码。观察者模式的实现方法各式各样, 函数、类的命名等会根据业务场景的不同有很大的差别,比如 register 函数还可以 叫作 attachremove 函数还可以叫作 detach 等等。不过,万变不离其宗,设计思 路都是差不多的。

        了解了观察者设计模式的基本使用方式,我们接下来看看他的具体使用场景

        简单理解: Subject 代表主动要做的事,比如要更新某个状态,主动触发某个行为。实现Observer 接口,代表被动触发得事,也可以理解为待办事项,比如我修改了某个状态,就要被动得触发一些事情,比如支付成功,就要将订单状态置为已完成等等。我们通过 Observe 中的registerObserver方法将待办事项加入到 Subject 中,然后当 Subject 主动做某项事情时,就会调用notifyObservers方法 将已经添加的待办事项都调用一下。

        2、使用场景

        1)股票行情应用:股票行情应用中,当股票价格发生变化时,需要通知订阅了该股票得投资者。这里,股票价格更新可以作为被观察者,投资者可以作为观察者。当股票发生变化时,所有订阅了该股票的投资者都会收到通知并更新自己的投资策略。

        2)网络聊天室:在网络聊天室中,当有新消息时,需要通知所有在线用户。聊天室服务器可以作为被观察者,用户可以作为观察者。当有新消息时,聊天室服务会通知所有在线用户更新聊天记录。

        3)拍卖系统:在拍卖系统中,当出价发生变化时,需要通知所有关注该拍品得用户。这里,拍卖系统可以作为被观察者,用户可以作为观察者。当出价发生变化时,所有关注该拍品得用户都会收到通知并更新自己的出价策略。

        4)订阅系统:在订阅系统中,当有新的内容发布时,需要通知所有订阅了该内容得用户,这里,内容发布可以作为被观察者,用户可以作为观察者,当有新内容发布时,所有订阅了该内容得用户都会收到通知并获取最新内容。

        5)游戏中的事件系统:在游戏中,当某个事件发生时(如角色升级、道具获得等),可能需要通知多个游戏模块进行相应的处理。这里,游戏事件可以作为被观察者,游戏模块可以作为观察者。当游戏事件发生时,所有关注该事件的游戏模块都会收到通知并执行相应的逻辑。

        6)运动比赛实时更新:在体育比赛中,实时更新比分、技术统计等信息对于球迷和分析师非常重要。在这种场景下,比赛数据更新可以作为被观察者,球迷和分析师可以作为观察者。当比赛数据发生变化时,所有关注比赛的的球迷和分析师都会收到通知并更新数据。

        7)物联网传感器系统:在物联网(IoT)系统中,有很多传感器不断采集数据,当数据发生变化时,需要通知相关联的设备或系统。在这种场景下,传感器可以作为被观察者,关联的设备或系统可以作为观察者,当传感器发生变化时,所有关联的设备或系统都会收到通知并执行相应的操作。

        8)电子邮件通知系统:在一个任务管理系统中,当任务的状态发生变化(如:新任务分配、任务完成等)时,需要通知相关的人员。这里,任务状态变更可以作为被观察者,相关人员可以作为观察者。当任务状态发生变化时,所有关注者都会收到通知并查看任务详情。

        9)社交网络:在社交网络中,用户关注其他用户以获取实时动态。当被关注的用户发布新动态时,需要通知所有关注者,在这种场景下,被关注的用户可以作为被观察者,关注者可以作为观察者,当被关注用户发布新动态时,所有关注者都会收到通知并查看动态。

        2.1 电商系统的应用

        假设有一个电商,当某件商品有促销活动时,需要通知所有订阅了该商品的用户,商品时主题,用户时时观察者。

        首先,创建一个Subject接口表示主题:

    public interface Subject {
        void registerObserver(Observer o);
        void removeObserver(Observer o);
        void notifyObservers();
    }

        接下来,创建一个Observer接口:

    public interface Observer {
        void update(String discountInfo);
    }

        现在,创建一个具体的主题,如Product,实现Subject接口:

    public class Product implements Subject {
        private ArrayList<Observer> observers;
        // 折扣消息
        private String discountInfo;
        public Product() {
            observers = new ArrayList<>();
        }
        public void registerObserver(Observer o) {
            observers.add(o);
        }
        public void removeObserver(Observer o) {
            int index = observers.indexOf(o);
            if (index >= 0) {
                observers.remove(index);
            }
        }
        public void notifyObservers() {
            for (Observer o : observers) {
                o.update(discountInfo);
            }
        }
        public void discountChanged() {
            notifyObservers();
        }
        public void setDiscountInfo(String discountInfo) {
            this.discountInfo = discountInfo;
            discountChanged();
        }
    }

        接着创建一个具体的观察,如User,实现Observer接口:

    public class User implements Observer {
        private String userName;
        private String discountInfo;
        private Subject product;
        public User(String userName, Subject product) {
            this.userName = userName;
            this.product = product;
            product.registerObserver(this);
        }
        public void update(String discountInfo) {
            this.discountInfo = discountInfo;
            display();
        }
        public void display() {
            System.out.println("用户 " + userName + " 收到促销通知: " + discountInfo);
        }
    }

        现在可以创建一个Product实例并向其注册User观察者。当Product的促销信息发生变化时,User会收到通知并显示促销信息。

    public class Main {
        public static void main(String[] args) {
            Product product = new Product();
            User user1 = new User("张三", product);
            User user2 = new User("李四", product);
            // 模拟商品促销信息更新
            product.setDiscountInfo("本周末满100减50");
            product.setDiscountInfo("双十一全场5折");
        }
    }

        这个例子中,我们创建了一个Product实例并向其注册了两个User观察者。当Product的促销信息发生变化时,所有观察者都会收到通知并更新自己的显示。

        2.2 erp

        在erp系统中,观察者也有很多应用场景,例如库存管理、生产计划等。假设有一个erp系统,当某个产品的库存低于安全库存时,需要通知采购部门、销售部门和仓库管理员。在这个例子中,产品库存是主题,采购部门、销售部门和仓库管理员是观察者,
        我们可以沿用之前定义的Subject 和 Observe 接口:
        创建一个具体的主题,如Inventory,实现Subject接口:

    public class Inventory implements Subject {
        private ArrayList<Observer> observers;
        private int stock;
        public Inventory() {
            observers = new ArrayList<>();
        }
        public void registerObserver(Observer o) {
            observers.add(o);
        }
        public void removeObserver(Observer o) {
            int index = observers.indexOf(o);
            if (index >= 0) {
                observers.remove(index);
            }
        }
        public void notifyObservers() {
            for (Observer o : observers) {
                o.update(String.valueOf(stock));
            }
        }
        public void stockChanged() {
            notifyObservers();
        }
        public void setStock(int stock) {
            this.stock = stock;
            stockChanged();
        }
    }

        接着创建一个具体的观察者,如PurchaseDepartment、SalesDepartment和WarehouseManager,分别实现Observer接口:

    public class PurchaseDepartment implements Observer {
        private int stock;
        private Subject inventory;
        public PurchaseDepartment(Subject inventory) {
            this.inventory = inventory;
            inventory.registerObserver(this);
        }
        public void update(String stock) {
            this.stock = Integer.parseInt(stock);
            display();
        }
        public void display() {
            System.out.println("采购部门收到库存更新: " + stock);
        }
    }
    public class SalesDepartment implements Observer {
        private int stock;
        private Subject inventory;
        public SalesDepartment(Subject inventory) {
            this.inventory = inventory;
            inventory.registerObserver(this);
        }
        public void update(String stock) {
            this.stock = Integer.parseInt(stock);
            display();
        }
        public void display() {
            System.out.println("销售部门收到库存更新: " + stock);
        }
    }
    public class WarehouseManager implements Observer {
        private int stock;
        private Subject inventory;
        public WarehouseManager(Subject inventory) {
            this.inventory = inventory;
            inventory.registerObserver(this);
        }
        public void update(String stock) {
            this.stock = Integer.parseInt(stock);
            display();
        }
        public void display() {
            System.out.println("仓库管理员收到库存更新: " + stock);
        }
    }
        现在我们可以创建一个Inventory 实例并向其注册 PurchaseDepartment 、 SalesDepartment和 WarehouseManager 观察者。当 Inventory 的库存发生变化时, 所有观察者会收到通知并更新自己的显示。
    public class Main {
        public static void main(String[] args) {
            Inventory inventory = new Inventory();
            PurchaseDepartment purchaseDepartment = new
                    PurchaseDepartment(inventory);
            SalesDepartment salesDepartment = new SalesDepartment(inventory);
            WarehouseManager warehouseManager = new
                    WarehouseManager(inventory);
            // 模拟库存变化
            inventory.setStock(500);
            inventory.setStock(300);
            inventory.setStock(100);
        }
    }
        在这个例子中,我们创建了一个Inventory 实例并向其注册了 PurchaseDepartment、 SalesDepartment WarehouseManager 观察者。当 Inventory的库存发生变化时,所有观察者都会收到通知并更新自己的显示。
        这个例子展示了观察者模式在ERP 系统中的一个应用场景,实现了库存信息与相关部 门(采购部门、销售部门、仓库管理员)之间的交互。这种模式可以帮助企业在库存 发生变化时快速作出相应的决策。        

        3、发布订阅

        发布-订阅模式 和 观察者模式都是用于实现对象间的解耦合的设计模式,尽管它们具有相似之处,但他们在实现方式和适用场景上存在一些关键区别。他们在概念上有一定的观赏性,都是用于实现对象间解耦合通信,可以将 发布-订阅模式看作是观察者模式的一种变体或扩展。

        3.1 观察者模式

        观察者模式定义了一种一对多的依赖关系,当一个对象(被观察者)的状态发生变化时,所有依赖于它的对象(观察者)都会得到通知并自动更新。在这个模式中,被观察者和观察者之间存在直接的关联关系。观察者模式主要包括两类对象:被观察者(Subject)和观察者(Observer)。

        3.2 发布-订阅模式

        发布-订阅模式(生产者和消费者)于观察者模式类似,但它们之间有一个关键区别:发布订阅模式引入了一个第三方组件(通常为消息代理或事件总线),该组件负责维护发布者和订阅者之间的关系。这意味着发布者和订阅者彼此不直接通信,而是通过消息代理进行通信。这种简洁通信允许发布者和订阅者在运行时动态的添加或删除,从而提高系统的灵活性和可扩展性。
        java中的发布-订阅模式示例:

interface Subscriber {
    void onEvent(String event);
}
class ConcreteSubscriber implements Subscriber {
    @Override
    public void onEvent(String event) {
        System.out.println("收到事件: " + event);
    }
}
// 创建消息总线
class EventBus {
    // 使用一个map维护,消息类型和该消息的订阅者
    private Map<String, List<Subscriber>> subscribers = new HashMap<>();
    // 订阅一个消息
    public void subscribe(String eventType, Subscriber subscriber) {
        subscribers.computeIfAbsent(eventType, k -> new ArrayList<>
                ()).add(subscriber);
    }
    // 接触订阅
    public void unsubscribe(String eventType, Subscriber subscriber) {
        List<Subscriber> subs = subscribers.get(eventType);
        if (subs != null) {
            subs.remove(subscriber);
        }
    }
    // 发布事件
    public void publish(String eventType, String event) {
        List<Subscriber> subs = subscribers.get(eventType);
        if (subs != null) {
            for (Subscriber subscriber : subs) {
                subscriber.onEvent(event);
            }
        }
    }
}


// 使用示例:
public class Main {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        Subscriber subscriber1 = new ConcreteSubscriber();
        Subscriber subscriber2 = new ConcreteSubscriber();
        // 订阅事件
        eventBus.subscribe("eventA", subscriber1);
        eventBus.subscribe("eventA", subscriber2);
        // 发布事件
        eventBus.publish("eventA", "这是事件A的内容");
        // 取消订阅
        eventBus.unsubscribe("eventA", subscriber1);
        // 再次发布事件
        eventBus.publish("eventA", "这是事件A的新内容");
    }
}

        以股票交易系统为例,股票价格的变化会作为事件发布,投资者可以订阅这些股票加个变化事件。当股票价格发生变化时,所有订阅了该股票的投资者都会收到通知。

        首先,创建一个Subscriber 接口,用于表示订阅者(投资者):

public interface Subscriber {
    void onStockPriceChanged(String stockSymbol, double newPrice);
}

       然后创建一个EventBus类,用于管理发布者和订阅者之间的通信:

public class EventBus {
    private Map<String, List<Subscriber>> subscribers = new HashMap<>();
    // 订阅股票价格变化事件
    public void subscribe(String stockSymbol, Subscriber subscriber) {
        subscribers.computeIfAbsent(stockSymbol, k -> new ArrayList<>
                ()).add(subscriber);
    }
    // 取消订阅股票价格变化事件
    public void unsubscribe(String stockSymbol, Subscriber subscriber) {
        List<Subscriber> subs = subscribers.get(stockSymbol);
        if (subs != null) {
            subs.remove(subscriber);
        }
    }
    // 发布股票价格变化事件
    public void publish(String stockSymbol, double newPrice) {
        List<Subscriber> subs = subscribers.get(stockSymbol);
        if (subs != null) {
            for (Subscriber subscriber : subs) {
                subscriber.onStockPriceChanged(stockSymbol, newPrice);
            }
        }
    }
}

        然后创建一个具体的订阅者实现,例如Investor类:

public class Investor implements Subscriber {
    private String name;
    public Investor(String name) {
        this.name = name;
    }
    // 股票代号,新价格
    @Override
    public void onStockPriceChanged(String stockSymbol, double newPrice) {
        System.out.println(name + " 收到股票 " + stockSymbol + " 价格变化通知,新
                价格:" + newPrice);
    }
}

        最后定义一个股票类:

// 股票
public class Stock{
    private double stockPrice;
    public void stockPriceChanged(double newPrice) {
        this.stockPrice = newPrice;
        eventBus.publish("AAPL", 210.0);
    }
}

·        使用示例:

public class Main {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        Investor investor1 = new Investor("投资者A");
        Investor investor2 = new Investor("投资者B");
        // 订阅股票价格变化事件
        eventBus.subscribe("AAPL", investor1);
        eventBus.subscribe("AAPL", investor2);
        Stock stock = new Stock();
        stock.stockPriceChanged(21.5);
        // 取消订阅股票价格变化事件
        eventBus.unsubscribe("AAPL", investor1);
        // 再次发布股票价格变化事件
        stock.stockPriceChanged(21.5);
    }
}

        这个示例中,Investor类实现了Subscriber接口,代表投资者。EventBus类是负责管理发布者和订阅者之间通信的中间组件。投资者可以通过EventBus订阅股票价格。

        总结一下两者的区别:
        1. 通信方式:观察者模式中,观察者与被观察者之间存在直接的关联关系,而发 布- 订阅模式中,发布者和订阅者通过一个第三方组件(消息代理或事件总线) 进行通信,彼此之间不存在直接关联关系。
        2. 系统复杂性:发布 - 订阅模式引入了一个额外的组件(消息代理或事件总线), 增加了系统的复杂性,但同时也提高了系统的灵活性和可扩展性。
        3. 使用场景:观察者模式适用于需要将状态变化通知给其他对象的情况,而发布 - 订阅模式适用于事件驱动的系统,尤其是那些需要跨越多个模块或组件进行通信 的场景。
        发布- 订阅模式和传统的观察者模式相比,在某些方面具有优势。以下是发布 - 订阅模 式相对于观察者模式的一些优点:
        1. 解耦:在发布 - 订阅模式中,发布者和订阅者之间没有直接关联,它们通过一个中间组件(消息代理或事件总线)进行通信。这种间接通信可以使发布者和订阅 者在运行时动态地添加或删除,从而进一步降低了它们之间的耦合度。
        2. 可扩展性:发布 - 订阅模式允许您更容易地向系统中添加新的发布者和订阅者, 而无需修改现有的代码。这使得系统在不同组件之间通信时具有更好的可扩展 性。
        3. 模块化:由于发布者和订阅者之间的通信通过中间组件进行,您可以将系统划分 为更小、更独立的模块。这有助于提高代码的可维护性和可读性。
        4. 异步通信:发布 - 订阅模式通常支持异步消息传递,这意味着发布者和订阅者可以在不同的线程或进程中运行。这有助于提高系统的并发性能和响应能力。
        5. 消息过滤:在发布 - 订阅模式中,可以利用中间组件对消息进行过滤,使得订阅 者只接收到感兴趣的消息。这可以提高系统的性能,减少不必要的通信开销。
        然而,发布- 订阅模式也有一些缺点,例如增加了系统的复杂性,因为引入了额外的 中间组件。根据具体的应用场景和需求来选择合适的设计模式是很重要的。在某些情 况下,观察者模式可能更适合,而在其他情况下,发布- 订阅模式可能是更好的选 择。

        4、源码使用

        4.1 jdk中的观察者

        java.util.Observable类实现了主题Subject的功能。而java.util.Observer接口则定义了观察者Observer的方法。
        通过调用Observable对象的notifyObservers()方法,可以通知所有注册的Observer对象,让他们更新自己的状态。
        以下是一个使用案例:假设有一个银行账户类,它的余额是可变的,当余额发生变化时,需要通知所有的观察者(比如银行客户),以便他们更新自己的显示信息。

// 银行账户类
public class BankAccount extends Observable {
    private double balance;
    // 构造函数
    public BankAccount(double balance) {
        this.balance = balance;
    }
    // 存款操作
    public void deposit(double amount) {
        balance += amount;
        setChanged(); // 表示状态已经改变
        notifyObservers(); // 通知所有观察者
    }
    // 取款操作
    public void withdraw(double amount) {
        balance -= amount;
        setChanged(); // 表示状态已经改变
        notifyObservers(); // 通知所有观察者
    }
    // 获取当前余额
    public double getBalance() {
        return balance;
    }
    // 主函数
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000.0);
        // 创建观察者
        Observer observer1 = new Observer() {
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("客户1: 余额已更新为 " +
                        ((BankAccount)o).getBalance());
            }
        };
        Observer observer2 = new Observer() {
            @Override
            public void update(Observable o, Object arg) {
                System.out.println("客户2: 余额已更新为 " +
                        ((BankAccount)o).getBalance());
            }
        };
        // 注册观察者
        account.addObserver(observer1);
        account.addObserver(observer2);
        // 存款操作,触发观察者更新
        account.deposit(100.0);
        // 取款操作,触发观察者更新
        account.withdraw(50.0);
    }
}

        这个案例中,BankAccount类继承了java.util.Observable类,表示它是一个主题 (Subject)。在存款或取款操作时,它会调用setChanged()方法表示状态已经改 变,并调用notifyObservers()方法通知所有观察者(Observer)。

        在主函数中,我们创建了两个观察者(observer1 observer2 ),它们分别实现了 Observer接口的 update() 方法。当观察者收到更新通知时,它们会执行自己的业务 逻辑,比如更新显示信息。
        这个案例演示了观察者模式在银行系统中的应用,通过观察者模式可以实现银行客户 对自己账户余额的实时监控。
        3.2 Guava中的消息总线
        Guava 库中的 EventBus 类提供了一个简单的消息总线实现,可以帮助您在 Java应用程序中实现发布- 订阅模式。以下是一个简单的示例,演示了如何使用 Guava 的 EventBus 来实现一个简单的消息发布和订阅功能。
        首先,确保您已将 Guava 添加到项目的依赖项中。如果您使用 Maven ,请在pom.xml 文件中添加以下依赖项:
<dependency>
        <groupId> com.google.guava </groupId>
        <artifactId> guava </artifactId>
        <version> 30.1-jre </version>
</dependency>
        接下来,定义一个事件类,例如 MessageEvent
public class MessageEvent {
    private String message;
    public MessageEvent(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}
        现在,创建一个订阅者类,例如 MessageSubscriber 。在订阅者类中,定义一个 方法并使用 @Subscribe 注解标记该方法,以便 EventBus 能够识别该方法作为事件处理器:
public class MessageSubscriber {
    @Subscribe
    public void handleMessageEvent(MessageEvent event) {
        System.out.println("收到消息: " + event.getMessage());
    }
}

        最后,我们来看一个使用案例:

public class Main {
    public static void main(String[] args) {
        // 创建 EventBus 实例
        EventBus eventBus = new EventBus();
        // 创建并注册订阅者
        MessageSubscriber subscriber = new MessageSubscriber();
        eventBus.register(subscriber);
        // 发布事件
        eventBus.post(new MessageEvent("Hello, EventBus!"));
        // 取消注册订阅者
        eventBus.unregister(subscriber);
        // 再次发布事件(此时订阅者已取消注册,将不会收到消息)
        eventBus.post(new MessageEvent("Another message"));
    }
}
        在这个示例中,我们创建了一个 EventBus 实例,然后创建并注册了一个MessageSubscriber 类型的订阅者。当我们使用 eventBus.post() 方法发布一 个 MessageEvent 事件时,订阅者的 handleMessageEvent 方法将被调用,并输出收到的消息。
        注意,如果订阅者处理事件的方法抛出异常, EventBus 默认情况下不会对异常进行处理。如果需要处理异常,可以在创建 EventBus 实例时传入一个自定义的SubscriberExceptionHandler 。
        5、进阶
        之前讲的实现方式,是一种同步阻塞的实现方式。观察者和被观察者代码在同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完之后,才执行后续的代码。如果某个接口是一个调用频繁的接口,对性能非常敏感,希望接口的响应时间尽可能短,那我们可以将同步阻塞的实现方式改为异步非阻塞的实现方式,以此来减少响应时间。
        5.1 异步非阻塞模型
        首先创建一个通用的观察者接口Observer和一个被观察者接口Observable。 
        
public interface Observer {
    void update(String message);
}
public interface Observable {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

        实现一个具体的被观察者类Subject 和 一个具体的观察者类ConcreteObserver

public class Subject implements Observable {
    private List<Observer> observers;
    private ExecutorService executorService;
    public Subject() {
        observers = new ArrayList<>();
        executorService = Executors.newCachedThreadPool();
    }
    @Override
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            executorService.submit(() -> observer.update(message));
        }
    }
    public void setMessage(String message) {
        notifyObservers(message);
    }
}
public class ConcreteObserver implements Observer {
    private String name;
    public ConcreteObserver(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + " received message: " + message);
    }
}

        最后创建一个简单的示例来测试实现的异步非阻塞观察者模式:

public class Main {
    public static void main(String[] args) {
        Subject subject = new Subject();
        ConcreteObserver observer1 = new ConcreteObserver("Observer 1");
        ConcreteObserver observer2 = new ConcreteObserver("Observer 2");
        ConcreteObserver observer3 = new ConcreteObserver("Observer 3");
        subject.addObserver(observer1);
        subject.addObserver(observer2);
        subject.addObserver(observer3);
        subject.setMessage("Hello, observers!");
        // 等待异步任务完成
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

        4.2 跨进程通信
        刚刚讲到的两个场景,不管是同步阻塞实现方式还是异步非阻塞实现方式,都是进程 内的实现方式。 如果用户注册成功之后,我们需要 发送用户信息给大数据征信系统 ,而大数据征信系统是一个独立的系统,跟它之间的交互是跨不同进程的,那如何实现一个跨进程的观察者模式呢?
        如果大数据征信系统提供了发送用户注册信息的 RPC 接口,我们仍然可以沿用之前的实现思路,在 notifyObservers() 函数中调用 RPC 接口来发送数据。但是,我们还 有更加优雅、更加常用的一种实现方式,那就是基于消息队列(Message Queue ,比如 ActiveMQ )来实现。
        当然,这种实现方式也有弊端,那就是需要引入一个新的系统(消息队列),增加了维护成本。不过,它的好处也非常明显。在原来的实现方式中,观察者需要注册到被 观察者中 ,被观察者需要依次遍历观察者来发送消息。而 基于消息队列的实现方式, 被观察者和观察者解耦更加彻底,两部分的耦合更小 。被观察者完全不感知观察者, 同理,观察者也完全不感知被观察者。被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。

 

二、模板模式

        模板模式主要是用来解决复用和扩展两个问题

        1、原理与实现

        模板模式,全称是模板方法设计模式,英文是Template Method Design Pattern.在GoF的 设计模式一书中,它是这么定义的:

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

        翻译成中文就是:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤

        这里的 算法 ,可以理解为广义上的 业务逻辑,并不特指数据结构和算法中的 算法。这里的算法骨架的方法就是模板方法,这也是模板方法模式名字的由来。

        代码示例:
        1、首先创建一个抽象类,定义算法的骨架:

public abstract class AbstractTemplate {
    // 模板方法,定义算法的骨架
    public final void templateMethod() {
        step1();
        step2();
        step3();
    }
    // 基本方法,定义算法中不会变化的步骤
    private void step1() {
        System.out.println("Step 1: Prepare the ingredients.");
    }
    // 抽象方法,定义算法中需要子类实现的步骤
    protected abstract void step2();
    // 基本方法,定义算法中不会变化的步骤
    private void step3() {
        System.out.println("Step 3: Serve the dish.");
    }
}

        2、然后创建具体的子类,实现抽象类中定义的抽象方法:

public class ConcreteTemplateA extends AbstractTemplate {
    @Override
    protected void step2() {
        System.out.println("Step 2 (A): Cook the dish using method A.");
    }
}
public class ConcreteTemplateB extends AbstractTemplate {
    @Override
    protected void step2() {
        System.out.println("Step 2 (B): Cook the dish using method B.");
    }
}
        3、最后在客户端中使用模板方法:
public class Main {
    public static void main(String[] args) {
        AbstractTemplate templateA = new ConcreteTemplateA();
        AbstractTemplate templateB = new ConcreteTemplateB();
        System.out.println("Using Template A:");
        templateA.templateMethod();
        System.out.println("\nUsing Template B:");
        templateB.templateMethod();
    }
}
        输出如下:
vbnetCopy code
Using Template A:
Step 1: Prepare the ingredients.
Step 2 (A): Cook the dish using method A.
Step 3: Serve the dish.
Using Template B:
Step 1: Prepare the ingredients.
Step 2 (B): Cook the dish using method B.
Step 3: Serve the dish.

         这个例子中, AbstractTemplate 是一个抽象类,它定义了一个名为 templateMethod 的模板方法。该方法包含三个步骤: step1 step2 和 step3 。其中, step1 step3 是基本方法,它们的实现在抽象类中定义且不会改变。 step2 是一个抽象方法,需要子类(如 ConcreteTemplateA 和 ConcreteTemplateB )根据具体需求实现。客户端代码通过创建子类的实例并调用 templateMethod 方法来执行算法。

        2、源码中的作用

        2.1 复用

        模板模式把一个算法中不变的流程抽象到父类的模板方法  templateMethod() 中,将可变的部分step2() 留给子类来实现。所有子类都可以复用父类中的模板方法定义的流程代码。

        1)java InputStream

        java IO 类库中,有很多类的设计用到模板模式,比如InputStream、OutputStream、Reader、Writer。我们拿 InputStream 来举例说明一下。

        InputStream 部分相关代码贴在了下面。在代码中, read() 函数是一个模板方法,定义了读取数据的整个流程,并且暴露了一个可以由子类来定制的抽象方法。不过这个方法也被命名为了 read() ,只是参数跟模板方法不同。
public abstract class InputStream implements Closeable {
    //...省略其他代码...
    public int read(byte b[], int off, int len) throws IOException {
        Objects.checkFromIndexSize(off, len, b.length);
        if (len == 0) {
            return 0;
        }
        int c = read();
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;
        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
    public abstract int read() throws IOException;
}
// 这里有一个具体的实现类。用于从一个字节缓冲区中读取一个字节。方法的签名和
功能如下:
public class ByteArrayInputStream extends InputStream {
    //...省略其他代码...
    @Override
    public synchronized int read() {
        return (pos < count) ? (buf[pos++] & 0xff) : -1;
    }
}

        2.2 扩展
        模板模式的第二大作用的是扩展 。这里所说的扩展,并不是指代码的扩展性,而是指 框架的扩展性 ,基于这个作用,模板模式常用在 框架的开发中 ,让框架用户可以在不 修改框架源码的情况下,定制化框架的功能。我们通过 Junit TestCase Java Servlet 两个例子来解释一下。
        1)Java Servlet
        对于 Java Web 项目开发来说,常用的开发框架是 SpringMVC 。利用它,我们只需 要关注业务代码的编写,底层的原理几乎不会涉及。但是,如果我们抛开这些高级框 架来开发 Web 项目,必然会用到 Servlet 。实际上,使用比较底层的 Servlet 来开发 Web 项目也不难。我们只需要定义一个继承 HttpServlet 的类,并且重写其中的 doGet() 或 doPost() 方法,来分别处理 get post 请求。具体的代码示例如下所示:
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        this.doPost(req, resp);
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.getWriter().write("Hello World.");
    }
}
        除此之外,我们还需要在配置文件 web.xml 中做如下配置。 Tomcat Jetty 等 Servlet 容器在启动的时候,会自动加载这个配置文件中的 URL Servlet 之间的映 射关系。

 <servlet>

        <servlet-name> HelloServlet </servlet-name>
        <servlet-class> com.xzg.cd.HelloServlet </servlet-class>
</servlet>
<servlet-mapping>
        <servlet-name> HelloServlet </servlet-name>
        <url-pattern> /hello </url-pattern>
</servlet-mapping>
        当我们在浏览器中输入网址(比如, http://127.0.0.1:8080/hello )的时候, Servlet容器会接收到相应的请求,并且根据 URL Servlet 之间的映射关系,找到相应的 Servlet( HelloServlet ),然后执行它的 service() 方法。 service() 方法定义在父类 HttpServlet 中,它会调用 doGet() doPost() 方法,然后输出数据( “Hello world”)到网页。
        我们现在来看,HttpServlet service() 函数长什么样子。
    public void service(ServletRequest req, ServletResponse res)
            throws ServletException, IOException
    {
        HttpServletRequest request;
        HttpServletResponse response;
        if (!(req instanceof HttpServletRequest &&
                res instanceof HttpServletResponse)) {
            throw new ServletException("non-HTTP request or response");
        }
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
        service(request, response);
    }
    protected void service(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException
    {
        String method = req.getMethod();
        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
        // servlet doesn't support if-modified-since, no reason
        // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < lastModified) {
        // If the servlet mod time is later, call doGet()
        // Round down to the nearest second for a proper compare
        // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
        // 子类实现的扩展点
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }
        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
        // 子类实现的扩展点
            doPost(req, resp);
        } else if (method.equals(METHOD_PUT)) {
        // 子类实现的扩展点
            doPut(req, resp);
        } else if (method.equals(METHOD_DELETE)) {
        // 子类实现的扩展点
            doDelete(req, resp);
        } else if (method.equals(METHOD_OPTIONS)) {
        // 子类实现的扩展点
            doOptions(req,resp);
        } else if (method.equals(METHOD_TRACE)) {
        // 子类实现的扩展点
            doTrace(req,resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }
        从上面的代码中我们可以看出,HttpServlet service() 方法就是一个模板方法,它 实现了整个 HTTP 请求的执行流程, doGet() doPost() 是模板中可以由子类来定制 的部分。实际上,这就相当于 Servlet 框架提供了一个扩展点( doGet() doPost()方法),让框架用户在不用修改 Servlet 框架源码的情况下,将业务代码通过扩展点镶嵌到框架中执行。
        3、应用场景
        3.1 电商系统
        将通用逻辑和特定逻辑分离,提高代码复用性和可维护性。以下是一些典型的应用场景:
        1. 支付流程 :电商系统通常需要支持多种支付方式(如信用卡支付、支付宝支付、 微信支付等)。虽然不同的支付方式在实现细节上有所不同,但它们的整体流程 是相似的。可以使用模板方法设计模式创建一个支付流程抽象类,定义通用的支 付流程骨架,然后通过子类实现各种具体支付方式的逻辑。
        2. 订单处理 :电商系统的订单处理流程通常包括一系列步骤,如验证库存、计算价格、生成运单等。这些步骤中,有些是通用的,而有些可能因订单类型、商品类型等因素而异。可以使用模板方法设计模式创建一个订单处理抽象类,定义通用 的订单处理流程骨架,然后通过子类实现特定订单类型或商品类型的逻辑。
        3. 促销策略 :电商系统中的促销活动通常具有多种策略(如满减、打折、赠品等)。尽管不同策略的具体实现不同,但它们都需要进行一些通用操作(如获取用户信息、验证促销条件等)。可以使用模板方法设计模式创建一个促销策略抽象类,定义通用的促销操作骨架,然后通过子类实现各种具体促销策略的逻辑。
        4. 报表生成 :电商系统需要生成各种报表(如销售报表、库存报表、财务报表等)。这些报表在数据查询和报表样式上可能有所不同,但它们的生成流程是类似的(如查询数据、生成报表、导出文件等)。可以使用模板方法设计模式创建一个报表生成抽象类,定义通用的报表生成流程骨架,然后通过子类实现具体报表类型的逻辑。
        这些应用场景展示了如何在电商系统中利用模板方法设计模式来简化代码结构和提高可维护性。在实际开发过程中,可以根据业务需求和系统架构选择合适的设计模式。
        我们以支付流程为例:
        在电商系统中,支付流程是一个典型的应用场景。我们可以使用模板方法设计模式创建一个抽象类 PaymentProcessor 来定义通用的支付流程骨架,然后通过子类实现各种具体支付方式的逻辑。以下是一个简化的代码示例及其中文注释:
        首先,我们创建一个抽象类 PaymentProcessor 来定义支付流程:
public abstract class PaymentProcessor {
    // 模板方法,定义支付流程骨架
    public final void processPayment(Order order) {
    // 获取支付方式(如信用卡、支付宝、微信等)
        String paymentMethod = getPaymentMethod();
    // 验证订单信息(如订单金额、收货地址等)
        validateOrder(order);
    // 验证支付信息(如支付账号、支付密码等)
        validatePaymentInfo(paymentMethod);
    // 执行支付
        executePayment(paymentMethod, order);
    // 发送支付通知
        sendPaymentNotification(order);
    }
    // 获取支付方式,具体实现由子类提供
    protected abstract String getPaymentMethod();
    // 验证订单信息,通用逻辑
    private void validateOrder(Order order) {
// 验证订单信息的实现
    }
    // 验证支付信息,具体实现由子类提供
    protected abstract void validatePaymentInfo(String paymentMethod);
    // 执行支付,具体实现由子类提供
    protected abstract void executePayment(String paymentMethod, Order
            order);
    // 发送支付通知,通用逻辑
    private void sendPaymentNotification(Order order) {
    // 发送支付通知的实现
    }
}
        接下来,我们创建一个具体的支付处理器类 AlipayProcessor 来实现支付宝支付方式:
public class AlipayProcessor extends PaymentProcessor {
    @Override
    protected String getPaymentMethod() {
        return "Alipay";
    }
    @Override
    protected void validatePaymentInfo(String paymentMethod) {
    // 验证支付宝支付信息的实现
    }
    @Override
    protected void executePayment(String paymentMethod, Order order) {
    // 执行支付宝支付的实现
    }
}
        同样,我们可以创建一个具体的支付处理器类 WechatPayProcessor 来实现微信支付方式:
public class WechatPayProcessor extends PaymentProcessor {
    @Override
    protected String getPaymentMethod() {
        return "WechatPay";
    }
    @Override
    protected void validatePaymentInfo(String paymentMethod) {
    // 验证微信支付信息的实现
    }
    @Override
    protected void executePayment(String paymentMethod, Order order) {
    // 执行微信支付的实现
    }
}
        在这个例子中, PaymentProcessor 定义了支付流程的通用骨架,如验证订单信息、发送支付通知等。具体的支付方式(如支付宝、微信支付等)由子类 AlipayProcessor 和 WechatPayProcessor 实现。这样,我们可以轻松地添加新 的支付方式,而不需要修改现有的支付流程代码,从而提高代码的可维护性和可扩展 性。
        现在,我们可以使用 AlipayProcessor WechatPayProcessor 来处理不同的支付方式。例如,当用户选择支付宝支付时,我们可以创建一个 AlipayProcessor 实例来处理支付流程:
public class PaymentService {
    public void processPayment(Order order, String paymentType) {
        PaymentProcessor paymentProcessor;
        if ("Alipay".equalsIgnoreCase(paymentType)) {
            paymentProcessor = new AlipayProcessor();
        } else if ("WechatPay".equalsIgnoreCase(paymentType)) {
            paymentProcessor = new WechatPayProcessor();
        } else {
            throw new IllegalArgumentException("Unsupported payment type: " +
                    paymentType);
        }
        paymentProcessor.processPayment(order);
    }
}
        在 PaymentService 类中,我们根据用户选择的支付方式创建相应的支付处理器实例,然后调用 processPayment 方法来处理支付流程。这种方式使得支付流程的处理逻辑更加清晰,易于维护和扩展。
        需要注意的是,为了更好地支持新的支付方式,我们可以考虑使用工厂模式或策略模式来创建支付处理器实例,进一步提高代码的可维护性和可扩展性。
        这个例子展示了如何在电商系统的支付流程中应用模板方法设计模式。通过将通用逻辑和特定逻辑分离,我们可以更轻松地添加新的支付方式,同时保持代码的清晰和易 于维护。在实际开发过程中,可以根据业务需求和系统架构灵活地运用模板方法设计 模式。
        4、Callback 回调
        模板 跟回调的区别:
        1)设计范式:模板方法设计模式通常用于面向对象编程(OOP),它依赖于继承和多态来实现代码复用。回调函数则通常用于函数式编程,它通过将函数作为参数传递给其他函数来实现代码复用。
        2)实现方式:模板方法设计依赖于抽象类和子类之间的继承关系。在抽象类中定义一个算法的骨架,并将某些步骤延迟到子类中实现。而回调函数通过将一个函数作为参数传递给另一个函数,让调用者可以自定义特定的行为。
        联系:
        1)目的 :模板方法设计模式和回调函数都旨在将变化的部分于不变的部分分离,提高代码的复用性和可维护性。
        2)实现相互关系:在某些情况下,模板方法设计模式可以通过回调函数来实现。例如,在java中,可以使用匿名内部类或者lambda表达式作为回调函数,实现模板方法设计的目标,类似的在,在面向对象的语言中,回调函数也可以通过模板方法设计模式来实现。
        总之,模板方法设计模式和回调函数都是解决代码复用和灵活性问题的有效手段。在不同的编程范式和上下文中,可以根据需要选择适当的方法。在实际开发中,我们甚至可以将这两种方法结合使用,以获得更好的灵活性和扩展性。
        4.1 回调的原理解析
               
        相对于普通的函数调用来说,回调是一种双向调用关系。A类事先注册某个函数F到B类,A类在调用B类的P函数的时候,B类反过来调用A类注册给它的F函数。这里的F函数就是回调函数,A调用B,B反过来有调用A,这种调用机制就叫做 回调,
        代码如下所示:
public interface ICallback {
    void methodToCallback();
}
public class BClass {
    public void process(ICallback callback) {
//...
        callback.methodToCallback();
//...
    }
}
public class AClass {
    public static void main(String[] args) {
        BClass b = new BClass();
        b.process(new ICallback() { //回调对象
            @Override
            public void methodToCallback() {
                System.out.println("Call back me.");
            }
        });
    }
}
        上面就是 Java 语言中回调的典型代码实现。从代码实现中,我们可以看出,回调跟模板模式一样,也具有复用和扩展的功能。除了回调函数之外,BClass 类的process() 函数中的逻辑都可以复用。如果 ICallback BClass 类是框架代码,AClass 是使用框架的客户端代码,我们可以通过 ICallback 定制 process() 函数,也就是说,框架因此具有了扩展的能力。
        实际上,回调不仅可以应用在代码设计上,在更高层次的架构设计上也比较常用。比如,通过三方支付系统来实现支付功能,用户在发起支付请求之后,一般不会一直阻塞到支付结果返回,而是注册回调接口(类似回调函数,一般是一个回调用的URL)给三方支付系统,等三方支付系统执行完成之后,将结果通过回调接口返回给用户。
        
        回调可以分为同步回调和异步回调(或者延迟回调)。同步回调指在函数返回之前执行回调函数;异步回调指的是在函数返回之后执行回调函数。上面的代码实际上是同步回调的实现方式,在 process() 函数返回之前,执行完回调函数methodToCallback()。而上面支付的例子是异步回调的实现方式,发起支付之后不需要等待回调接口被调用就直接返回。从应用场景上来看,同步回调看起来更像模板模式,异步回调看起来更像观察者模式。

三、策略模式

        1、原理和实现
        策略模式,英文全称是 Strategy Design Pattern该模式是这样定义的:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it。

        翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换,策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

        策略模式主要包含一下角色:
        1)策略接口(Strategy):定义所有支持的算法的公共接口,客户端使用这个接口与具体策略进行交互。
        2)具体策略(Concrete Strategy):实现策略接口的具体策略类,这些类封装了实际的算法逻辑。
        3)上下文(Context):持有一个策略对象,用于与客户端进行交互,上下文可以定义一些接口,让客户端不直接与策略接口交互,从而实现策略的封装。

        例子:假设我们要实现一个计算器,支持加法、减法和乘法运算。我们可以使用策略模式将各种运算独立为不同的策略,并让客户端根据需要选择和使用不同的策略。
        首先我们定义一个策略接口:

    public interface Operation {
        double execute(double num1, double num2);
    }

        接下来,我们创建具体的策略类来实现加法、减法和乘法运算:


    public class Addition implements Operation {
        @Override
        public double execute(double num1, double num2) {
            return num1 + num2;
        }
    }
    public class Subtraction implements Operation {
        @Override
        public double execute(double num1, double num2) {
            return num1 - num2;
        }
    }
    public class Multiplication implements Operation {
        @Override
        public double execute(double num1, double num2) {
            return num1 * num2;
        }
    }
        然后,我们创建一个上下文类 Calculator ,让客户端可以使用这个类来执行不同的运算:        
    public class Calculator {
        private Operation operation;
        public void setOperation(Operation operation) {
            this.operation = operation;
        }
        public double executeOperation(double num1, double num2) {
            return operation.execute(num1, num2);
        }
    }

         现在,客户端可以使用 Calculator 类来执行不同的运算,例如:

    public class Client {
        public static void main(String[] args) {
            Calculator calculator = new Calculator();
            calculator.setOperation(new Addition());
            System.out.println("10 + 5 = " + calculator.executeOperation(10, 5));
            calculator.setOperation(new Subtraction());
            System.out.println("10 - 5 = " + calculator.executeOperation(10, 5));
            calculator.setOperation(new Multiplication());
            System.out.println("10 * 5 = " + calculator.executeOperation(10,5));
        }
    }

        在这个例子中,我们使用策略模式将加法、减法和乘法运算独立为不同的策略。客户端可以根据需要选择和使用不同的策略。 Calculator 上下文类持有一个Operation 策略对象,并通过 setOperation 方法允许客户端设置所需的策略。这种方式使得算法的选择和执行更加灵活,易于扩展和维护

        策略模式的优点包括:
        1. 提高代码的可维护性和可扩展性。当需要添加新的算法时,我们只需要实现一个新的具体策略类,而无需修改客户端代码。
        2. 符合开闭原则。策略模式允许我们在不修改现有代码的情况下引入新的策略。
        3. 避免使用多重条件判断。使用策略模式可以消除一些复杂的条件判断语句,使代码更加清晰和易于理解。
        
        策略模式的缺点包括:
        1. 客户端需要了解所有的策略。为了选择合适的策略,客户端需要了解不同策略之间的区别。
        2. 增加了类的数量。策略模式会导致程序中具体策略类的数量增加,这可能会导致代码的复杂性增加。
        
        在实际开发中,我们可以根据业务需求和系统架构灵活地运用策略模式。例如,在电商系统中,我们可以使用策略模式处理不同的促销策略;在游戏系统中,我们可以使用策略模式处理不同的角色行为等。

        1.1 策略的定义

        策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。因为所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活的替换不同的策略,示例:

public interface Strategy {
    void algorithmInterface();
}
public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {
//具体的算法...
    }
}
public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithmInterface() {
//具体的算法...
    }
}

        1.2 策略的创建

        因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。
        事实上我们可以做一定的优化,可以根据type创建策略的逻辑抽离出来,放到工厂类中,示例代码:

public class StrategyFactory {
    private static final Map<String, Strategy> strategies = new HashMap<>();
    static {
        strategies.put("A", new ConcreteStrategyA());
        strategies.put("B", new ConcreteStrategyB());
    }
    public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
        }
        return strategies.get(type);
    }
}

        一般来讲,如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 getStrategy() 的时候,都创建一个新的策略对象。针对这种情况,我们可以使用上面这种工厂类的实现方式,事先创建好每个策略对象,缓存到工厂类中,用的时候直接返回。

        相反,如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中,获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那我们就需要按照如下方式来实现策略工厂类。
public class StrategyFactory {
    public static Strategy getStrategy(String type) {
        if (type == null || type.isEmpty()) {
            throw new IllegalArgumentException("type should not be empty.");
        }
        if (type.equals("A")) {
            return new ConcreteStrategyA();
        } else if (type.equals("B")) {
            return new ConcreteStrategyB();
        }
        return null;
    }
}

        1.3 策略的使用

         最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。 这里的 运行时动态 指的是,我们事先并不知道会使用哪个策略,而是在程序运行期 间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。接 下来,我们通过一个例子来解释一下。
// 策略接口:EvictionStrategy
// 策略类:LruEvictionStrategy、FifoEvictionStrategy、
LfuEvictionStrategy...
// 策略工厂:EvictionStrategyFactory
public class UserCache {
    private Map<String, User> cacheData = new HashMap<>();
    private EvictionStrategy eviction;
    public UserCache(EvictionStrategy eviction) {
        this.eviction = eviction;
    }
//...
}
// 运行时动态确定,根据配置文件的配置决定使用哪种策略
public class Application {
    public static void main(String[] args) throws Exception {
        EvictionStrategy evictionStrategy = null;
        Properties props = new Properties();
        props.load(new FileInputStream("./config.properties"));
        String type = props.getProperty("eviction_type");
        evictionStrategy = EvictionStrategyFactory.getEvictionStrategy(type);
        UserCache userCache = new UserCache(evictionStrategy);
        //...
    }
}
// 非运行时动态确定,在代码中指定使用哪种策略
public class Application {
    public static void main(String[] args) {
        //...
        EvictionStrategy evictionStrategy = new LruEvictionStrategy();
        UserCache userCache = new UserCache(evictionStrategy);
        //...
    }
}
        从上面的代码中,我们也可以看出,“ 非运行时动态确定 ,也就是第二个Application 中的使用方式,并不能发挥策略模式的优势。在这种应用场景下,策略模式实际上退化成了“ 面向对象的多态特性 基于接口而非实现编程原则 ,这其实就是开篇时列举的例子的场景。
        
        2、优化if分支

        2.1 基础优化

        我们将以解析报文的例子为基础,展示如何使用策略模式优化具有if条件分支的代码。
        示例:

public class MessageParser {
    public void parseMessage(Message message) {
        String messageType = message.getType();
        if ("XML".equalsIgnoreCase(messageType)) {
            // 解析 XML 报文
            System.out.println("解析 XML 报文: " + message.getContent());
        } else if ("JSON".equalsIgnoreCase(messageType)) {
            // 解析 JSON 报文
            System.out.println("解析 JSON 报文: " + message.getContent());
        } else if ("CSV".equalsIgnoreCase(messageType)) {
            // 解析 CSV 报文
            System.out.println("解析 CSV 报文: " + message.getContent());
        } else {
            throw new IllegalArgumentException("未知的报文类型: " + messageType);
        }
    }
}
        使用策略模式优化上述代码,首先定义一个报文解析接口   MessageParserStrategy
public interface MessageParserStrategy {
    // 解析报文内容的方法,输入一个 Message 对象,无返回值
    void parse(Message message);
}

        然后,实现xml、json、csv报文解析策略:

// XML 报文解析策略
public class XmlMessageParserStrategy implements MessageParserStrategy {
    @Override
    public void parse(Message message) {
        System.out.println("解析 XML 报文: " + message.getContent());
    }
}
// JSON 报文解析策略
public class JsonMessageParserStrategy implements MessageParserStrategy {
    @Override
    public void parse(Message message) {
        System.out.println("解析 JSON 报文: " + message.getContent());
    }
}
// CSV 报文解析策略
public class CsvMessageParserStrategy implements MessageParserStrategy {
    @Override
    public void parse(Message message) {
        System.out.println("解析 CSV 报文: " + message.getContent());
    }
}

        接下来创建一个MessageParserContext类,该类将根据传入的策略解析报文:

public class MessageParserContext {
    private MessageParserStrategy strategy;
    // 设置报文解析策略
    public void setStrategy(MessageParserStrategy strategy) {
        this.strategy = strategy;
    }
    // 根据策略解析报文
    public void parseMessage(Message message) {
        strategy.parse(message);
    }
}

        最后,使用策略模式进行报文解析,避免了分支判断:

public class Main {
    public static void main(String[] args) {
        MessageParserContext parserContext = new MessageParserContext();
        // 使用 XML 报文解析策略
        parserContext.setStrategy(new XmlMessageParserStrategy());
        parserContext.parseMessage(new Message("XML", "<xml>这是一个 XML 报文</xml>"));
        // 使用 JSON 报文解析策略
        parserContext.setStrategy(new JsonMessageParserStrategy());
        parserContext.parseMessage(new Message("JSON", "{\"message\": \"这是一个 JSON 报文\"}"));
        // 使用 CSV 报文解析策略
        parserContext.setStrategy(new CsvMessageParserStrategy());
        parserContext.parseMessage(new Message("CSV", "这是一个,CSV,报文"));
    }
}

        2.2 结合工厂模式:

        我们可以将策略模式与工厂模式结合,以便根据不同的消息类型自动匹配不同的解析策略。下面是如何实现这个优化的:   

        首先,我们创建一个 MessageParserStrategyFactory 类,用于根据报文类型创建相应的解析策略:

public class MessageParserStrategyFactory {
    private static final Map<String, MessageParserStrategy> strategies = new
            HashMap<>();
    static {
        strategies.put("XML", new XmlMessageParserStrategy());
        strategies.put("JSON", new JsonMessageParserStrategy());
        strategies.put("CSV", new CsvMessageParserStrategy());
    }
    public static MessageParserStrategy getStrategy(String messageType) {
        MessageParserStrategy strategy =
                strategies.get(messageType.toUpperCase());
        if (strategy == null) {
            throw new IllegalArgumentException("未知的报文类型: " + messageType);
        }
        return strategy;
    }
}

        接下来,我们修改 MessageParserContext 类,使其根据报文类型自动选择解析策略:

public class MessageParserContext {
    public void parseMessage(Message message) {
        MessageParserStrategy strategy =
                MessageParserStrategyFactory.getStrategy(message.getType());
        strategy.parse(message);
    }
}
        现在,我们的代码可以根据不同的消息类型自动匹配不同的解析策略,而无需手动设置策略。以下是使用此优化的示例:
public class Main {
    public static void main(String[] args) {
        MessageParserContext parserContext = new MessageParserContext();
        // 自动使用 XML 报文解析策略
        parserContext.parseMessage(new Message("XML", "<xml>这是一个 XML 报文</xml>"));
        // 自动使用 JSON 报文解析策略
        parserContext.parseMessage(new Message("JSON", "{\"message\": \"这是一个 JSON 报文\"}"));
        // 自动使用 CSV 报文解析策略
        parserContext.parseMessage(new Message("CSV", "这是一个,CSV,报文"));
    }
}
        通过将策略模式与工厂模式结合,我们优化了报文解析过程。这样的代码更容易扩展和维护,因为我们可以通过在工厂类中添加新的解析策略来轻松地支持新的报文类型。
        3、使用场景
        以一个简单的支付系统为例:
        
        下面是一个使用策略模式的支付系统的简化示例。我们将定义一个支付策略接口PaymentStrategy ,并为不同的支付方式(如信用卡支付和支付宝支付)提供具体的实现。客户端可以根据用户选择的支付方式来调用相应的支付策略。
        首先,我们定义一个支付策略接口 PaymentStrategy
public interface PaymentStrategy {
    // 支付操作
    void pay(double amount);
}

        接着,我们为信用卡支付方式提供一个具体实现CreditCardPaymentStrategy

public class CreditCardPaymentStrategy implements PaymentStrategy {
    private String name;
    private String cardNumber;
    private String cvv;
    private String expirationDate;
    public CreditCardPaymentStrategy(String name, String cardNumber, String
            cvv, String expirationDate) {
        this.name = name;
        this.cardNumber = cardNumber;
        this.cvv = cvv;
        this.expirationDate = expirationDate;
    }
    @Override
    public void pay(double amount) {
        System.out.println("使用信用卡支付:" + amount + "元");
    }
}
        接下来,我们为支付宝支付方式提供一个具体实现 AlipayPaymentStrategy
public class AlipayPaymentStrategy implements PaymentStrategy {
    private String email;
    private String password;
    public AlipayPaymentStrategy(String email, String password) {
        this.email = email;
        this.password = password;
    }
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付:" + amount + "元");
    }
}

        现在,我们可以在客户端代码中根据用户选择的支付方式来调用相应的支付策略:

public class PaymentClient {
    public static void main(String[] args) {
        // 创建信用卡支付策略实例
        PaymentStrategy creditCardStrategy = new CreditCardPaymentStrategy("张三", "1234567890123456", "123", "12/23");
        // 创建支付宝支付策略实例
        PaymentStrategy alipayStrategy = new AlipayPaymentStrategy("zhangsan@example.com", "mypassword");
        // 根据用户选择的支付方式进行支付
        double paymentAmount = 100.0;
        System.out.println("用户选择信用卡支付:");
        creditCardStrategy.pay(paymentAmount);
        System.out.println("用户选择支付宝支付:");
        alipayStrategy.pay(paymentAmount);
    }
}
        在这个示例中,我们定义了一个支付策略接口 PaymentStrategy ,并为不同的支付方式提供了具体实现。客户端代码可以根据用户选择的支付方式来调用相应的支付策略。这样,当需要添加新的支付方式时,我们只需提供新的支付策略实现,而无需修改客户端代码,从而提高了系统的可扩展性。

四、职责链模式

        1、原理和实现

        职责链模式的英文翻译是 Chain Of Responsibility Design Pattern,在 GoF 的 设计模式中,他是这么定义的:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

        翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上某个接收对象能够处理它,实际上,我们的责任链并不是和概念中的完全一样。 

        原始概念中,是直到链上的某个接收对象能够处理它为止。
        实际使用中,链上的所有对象都可以对请求进行特殊处理。

        2、实现方式

        2.1 使用链表实现

        第一种实现方式如下所示。其中,Handler 是所有处理器类的抽象父类,handle() 是抽象方法。每个具体的处理器类(HandlerAHandlerB)的 handle() 函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 successor.handle())。HandlerChain 是处理器链,从数据结构的角度来看,它就是一个记录了链头、链尾的链表。其中,记录链尾是为了方便添加处理器。

public abstract class Handler {
    protected Handler successor = null;
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    public abstract void handle();
}
public class HandlerA extends Handler {
    @Override
    public boolean handle() {
        boolean handled = false;
//...
        if (!handled && successor != null) {
            successor.handle();
        }
    }
}
public class HandlerB extends Handler {
    @Override
    public void handle() {
        boolean handled = false;
//...
        if (!handled && successor != null) {
            successor.handle();
        }
    }
}
public class HandlerChain {
    private Handler head = null;
    private Handler tail = null;
    public void addHandler(Handler handler) {
        handler.setSuccessor(null);
        if (head == null) {
            head = handler;
            tail = handler;
            return;
        }
        tail.setSuccessor(handler);
        tail = handler;
    }
    public void handle() {
        if (head != null) {
            head.handle();
        }
    }
}
// 使用举例
public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

         实际上,上面的代码实现不够优雅。处理器类的 handle() 函数,不仅包含自己的业 务逻辑,还包含对下一个处理器的调用,也就是代码中的 successor.handle()。一个不熟悉这种代码结构的程序员,在添加新的处理器类的时候,很有可能忘记在handle() 函数中调用 successor.handle(),这就会导致代码出现 bug

       针对这个问题,我们对代码进行重构,利用模板模式,将调用 successor.handle() 的逻辑从具体的处理器类中剥离出来,放到抽象父类中。这样具体的处理器类只需要实现自己的业务逻辑就可以了。重构之后的代码如下所示:

public abstract class Handler {
    protected Handler successor = null;
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    public final void handle() {
        boolean handled = doHandle();
        if (successor != null && !handled) {
            successor.handle();
        }
    }
    protected abstract boolean doHandle();
}
public class HandlerA extends Handler {
    @Override
    protected boolean doHandle() {
        boolean handled = false;
//...
        return handled;
    }
}
public class HandlerB extends Handler {
    @Override
    protected boolean doHandle() {
        boolean handled = false;
//...
        return handled;
    }
}
// HandlerChain和Application代码不变

                

        2.2 使用数组实现

        HandlerChain 类用数组而非链表来保存所有的处理器,并且需要在 HandlerChain的 handle() 函数中,依次调用每个处理器的 handle() 函数。

public interface IHandler {
    boolean handle();
}
public class HandlerA implements IHandler {
    @Override
    public boolean handle() {
        boolean handled = false;
//...
        return handled;
    }
}
public class HandlerB implements IHandler {
    @Override
    public boolean handle() {
        boolean handled = false;
//...
        return handled;
    }
}
public class HandlerChain {
    private List<IHandler> handlers = new ArrayList<>();
    public void addHandler(IHandler handler) {
        this.handlers.add(handler);
    }
    public void handle() {
        for (IHandler handler : handlers) {
            boolean handled = handler.handle();
            if (handled) {
                break;
            }
        }
    }
}
// 使用举例
public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

        2.3 扩展

        在 GoF 给出的定义中,如果处理器链上的某个处理器能够处理这个请求,那就不会继续往下传递请求。实际上,职责链模式还有一种变体,那就是请求会被所有的处理器都处理一遍,不存在中途终止的情况。这种变体也有两种实现方式:用链表存储处理器和用数组存储处理器,跟上面的两种实现方式类似,只需要稍微修改即可。
        我这里只给出其中一种实现方式,如下所示。另外一种实现方式你对照着上面的实现自行修改。

public abstract class Handler {
    protected Handler successor = null;
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    public final void handle() {
        doHandle();
        if (successor != null) {
            successor.handle();
        }
    }
    protected abstract void doHandle();
}
public class HandlerA extends Handler {
    @Override
    protected void doHandle() {
//...
    }
}
public class HandlerB extends Handler {
    @Override
    protected void doHandle() {
//...
    }
}
public class HandlerChain {
    private Handler head = null;
    private Handler tail = null;
    public void addHandler(Handler handler) {
        handler.setSuccessor(null);
        if (head == null) {
            head = handler;
            tail = handler;
            return;
        }
        tail.setSuccessor(handler);
        tail = handler;
    }
    public void handle() {
        if (head != null) {
            head.handle();
        }
    }
}
// 使用举例
public class Application {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new HandlerA());
        chain.addHandler(new HandlerB());
        chain.handle();
    }
}

        3、源码实现

        Servlet Filter、Spring Interceptor、mybtias的插件 源码中都包含有责任连模式,可以自行查阅一下相关资料。

        

        4、应用场景

        责任链模式在工作中的应用广泛,以下列举了一些常见的使用场景,大家可以自行学习:

        1. 日志记录器:在应用程序中,我们可能需要将日志记录到不同的位置,如控制台、文件、数据库等。我们可以创建一个日志记录器链,每个记录器处理特定级别的日志,然后将请求传递给下一个记录器。这样,可以根据日志级别灵活地记录日志信息。

        2. Web 应用中的过滤器和拦截器:在 Web 应用程序中,我们经常需要对请求进行预处理和后处理,如身份验证、授权、编码转换、请求日志记录等。过滤器和拦截器就是典型的使用责任链模式的场景,请求和响应在过滤器或拦截器链中依次传递,每个过滤器或拦截器执行特定的任务。

        3. 工作流引擎:在一个工作流引擎中,一个请求可能需要经过多个处理步骤,这些步骤可以看作是一个责任链。每个处理器处理请求的一个部分,然后将请求传递给下一个处理器,直到请求被完全处理。

        4. 软件审批流程:在企业软件开发过程中,代码审查、需求审批、文档审查等流程可能需要多个审批者按顺序审批。这种场景下,责任链模式能够确保每个审批者只关注自己的审批职责,并将审批请求传递给下一个审批者。

        5. 电子邮件处理:在一个电子邮件处理系统中,可能需要对不同类型的邮件进行不同的处理,如垃圾邮件过滤、自动回复、邮件归类等。在这种情况下,可以使用 责任链模式来创建一个邮件处理链,每个处理器负责处理特定类型的邮件,然后将邮件传递给下一个处理器。

        6. 事件处理系统:在一个事件驱动的系统中,可能需要对不同类型的事件进行不同的处理。责任链模式可以用来创建一个事件处理器链,每个处理器负责处理特定类型的事件,并将事件传递给下一个处理器。这样可以确保系统的可扩展性和灵活性。

        7. 规则引擎:在某些业务场景下,可能需要按照一定的规则对数据进行处理。规则引擎是典型的使用责任链模式的场景。每个规则可以看作是一个处理器,对数据进行特定的处理,然后将数据传递给下一个规则,直至所有规则都被执行。

        8. 任务调度系统:在任务调度系统中,根据任务的优先级、类型和资源需求,可能需要将任务分配给不同的执行器。责任链模式可以确保每个执行器只关注自己可以处理的任务,并将其他任务传递给下一个执行器。

        除了以上的使用场景,我们再给大家列举一个使用场景,如下:

        对于支持 UGC(User Generated Content,用户生成内容)的应用(比如论坛)来说,用户生成的内容(比如,在论坛中发表的帖子)可能会包含一些敏感词(比如涉黄、广告、反动等词汇)。针对这个应用场景,我们就可以利用职责链模式来过滤这些敏感词。

        在这个应用场景中,我们可以创建一个过滤器链来过滤用户生成的内容。每个过滤器负责处理一种类型的敏感词,然后将内容传递给下一个过滤器。以下是一个简单的实现示例:

        首先,我们定义一个过滤器接口

public interface ContentFilter {
    String filter(String content);
}

        然后,我们实现不同类型的过滤器,例如涉黄过滤器、广告过滤器和反动过滤器:

// 涉黄过滤器
public class PornographyFilter implements ContentFilter {
    @Override
    public String filter(String content) {
// 这里用简单的字符串替换来表示过滤操作,实际应用中需要更复杂的过滤逻辑
        return content.replaceAll("涉黄词汇", "***");
    }
}
// 广告过滤器
public class AdvertisementFilter implements ContentFilter {
    @Override
    public String filter(String content) {
        return content.replaceAll("广告词汇", "***");
    }
}
// 反动过滤器
public class ReactionaryFilter implements ContentFilter {
    @Override
    public String filter(String content) {
        return content.replaceAll("反动词汇", "***");
    }
}

        接下来,我们创建一个过滤器链:

public class FilterChain {
    private List<ContentFilter> filters = new ArrayList<>();

    public FilterChain addFilter(ContentFilter filter) {
        filters.add(filter);
        return this;
    }

    public String doFilter(String content) {
        for (ContentFilter filter : filters) {
            content = filter.filter(content);
        }
        return content;
    }
}
        最后,我们可以在应用中使用过滤器链来处理用户生成的内容:
public class Main {
    public static void main(String[] args) {
        // 创建一个过滤器链
        FilterChain filterChain = new FilterChain();
        filterChain.addFilter(new PornographyFilter())
                .addFilter(new AdvertisementFilter())
                .addFilter(new ReactionaryFilter());
        // 用户生成的内容
        String userContent = "这里有一些涉黄词汇,这里有一些广告词汇,这里有一些反动词汇。";
        // 使用过滤器链处理内容
        String filteredContent = filterChain.doFilter(userContent);
        System.out.println(filteredContent);
    }
}
        代码中的注释已经用中文解释了每个部分的作用。通过使用责任链模式,我们可以灵活地添加、删除或修改过滤器,使得过滤用户生成内容的逻辑更加易于维护和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值