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

2023年 7月 26日 43.1k 0

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

1. 策略模式(Strategy)

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

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

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

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

public interface Strategy {  
  
    int calc(int num1, int num2);  
  
}

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

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

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

public enum CalcEnum {  
  
    ADD(AddStrategy.class),  
    SUBTRACT(SubtractStrategy.class);  

    private Class getClazz() {  
        return clazz;  
    }  
  
}
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);  
    }  
  
}

运行后输出结果:

1 + 2 = 3
5 - 3 = 2

策略模式的优点:1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
策略模式的缺点:1、策略类会增多。 2、所有策略类都需要对外暴露。

2. 模版模式(Template)

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

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

模版模式涉及的角色:

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

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

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();  
  
}

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

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("宫爆鸡丁 做菜后...");  
    }  
  
}
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("红烧排骨 做菜后...");  
    }  
  
}

运行后输出结果:

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

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

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

模板模式的优点:

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

  • 每个不同的实现都需要定义一个子类,会导致类的个数增加,系统更加庞大。
  • 3. 观察者模式(Observer)

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

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

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

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

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

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

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

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

    public class WechatServer implements Subject {  
      
        private List list;  
        private String message;  
    
        public WechatServer() {  
            list = new ArrayList();  
        }  
    
        @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();  
        }  
      
    }
    

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

    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);  
        }  
      
    }
    

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

    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 是世界上最好用的语言!");  
    
        }  
      
    }
    
    微信服务发送消息: 微信小程序获取手机号即将收费!
    Han Mei 收到推送消息: 微信小程序获取手机号即将收费!
    Li Lei 收到推送消息: 微信小程序获取手机号即将收费!
    ----------------------------------------------
    微信服务发送消息: JAVA 是世界上最好用的语言!
    Han Mei 收到推送消息: JAVA 是世界上最好用的语言!
    

    观察者模式的优点:

  • 建立一套触发机制,使观察者和被观察者是抽象关联的。
  • 观察者模式的缺点:

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

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

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

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

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

    public interface Iterator {  
      
        boolean hasNext();  
    
        Object next();  
      
    }
    

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

    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;  
        }  
      
    }
    
    public class MenuIterator implements Iterator {  
      
        private int position;  
        private final ArrayList menuItems;  
    
        public MenuIterator(ArrayList 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;  
        }  
      
    }
    
    public class ShopMenu {  
      
        private final ArrayList 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 getMenuItems() {  
            return menuItems;  
        }  
      
    }
    

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

    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());  
            }  
        }  
      
    }
    
    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
    

    迭代器模式的优点:

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

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

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

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

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

    public interface Filter {  
    
        void doFilter(FilterChain chain);  
      
    }
    
    public abstract class AbsFilter implements Filter {  
      
        @Override  
        public void doFilter(FilterChain chain) {  
            chain.doFilter();  
        }  
      
    }
    
    public interface FilterChain {  
      
        void doFilter();  
      
    }
    

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

    public class FilterA extends AbsFilter {  
      
        @Override  
        public void doFilter(FilterChain chain) {  
            System.out.println("A过滤器开始工作");  
            super.doFilter(chain);  
        }  
      
    }
    
    public class FilterB extends AbsFilter {  
      
        @Override  
        public void doFilter(FilterChain chain) {  
            System.out.println("B过滤器开始工作");  
            super.doFilter(chain);  
        }  
      
    }
    
    public class FilterC extends AbsFilter {  
      
        @Override  
        public void doFilter(FilterChain chain) {  
            System.out.println("C过滤器开始工作");  
            super.doFilter(chain);  
        }  
    
    }
    
    public class FilterChainImpl implements FilterChain {  
      
        private List 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);  
            }  
        }  
      
    }
    

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

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

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

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

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

    A过滤器开始工作
    B过滤器停止工作
    

    责任链模式的优点:

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

    责任链模式的缺点:

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

    6. 命令模式(Command)

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

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

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

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

    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");  
        }  
      
    }
    
    public interface Command {  
      
        public void exec();  
    
        public void undo();  
      
    }
    

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

    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();  
        }  
      
    }
    
    public class Controller {  
      
        public void execCommand(Command command) {  
            command.exec();  
        }  
    
        public void undoCommand(Command command) {  
            command.undo();  
        }  
      
    }
    

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

    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();  
        }  
      
    }
    
    energy-saving light On
    energy-saving light Off
    

    7. 状态模式(State)

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

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

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

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

    public interface State {  
      
        void stateA2B(StateContext context);  
    
        void stateB2C(StateContext context);  
    
        void stateC2B(StateContext context);  
      
    }
    
    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() + " 状态不匹配,无法转变! ");  
        }  
      
    }
    
    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() + " 状态不匹配,无法转变! ");  
        }  
      
    }
    
    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);  
        }  
      
    }
    
    public enum StateEnum {  

    A ("已付款", StateA.class),
    B ("已发货", StateB.class),
    C ("已退货", StateC.class);

    private String desc;
    private Class

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论