• Java学习--设计模式之行为型模式(一)


    一、简介

      行为型模式:这些设计模式特别关注对象之间的通信。包括:责任链模式(Chain of Responsibility Pattern)、命令模式(Command Pattern)、解释器模式(Interpreter Pattern)、迭代器模式(Iterator Pattern)、中介者模式(Mediator Pattern)、备忘录模式(Memento Pattern)、观察者模式(Observer Pattern)、状态模式(State Pattern)、空对象模式(Null Object Pattern)、策略模式(Strategy Pattern)、模板模式(Template Pattern)、访问者模式(Visitor Pattern);

    二、责任链模式(Chain of Responsibility Pattern)

      1、概念

       顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。

      2、简介

       意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

       主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

       何时使用:在处理消息的时候以过滤很多道。

       如何解决:拦截的类都实现统一接口。

       关键代码:Handler 里面聚合它自己,在 HanleRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。

       应用实例: 

        (1)、JS 中的事件冒泡。

        (2)、JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。

       优点: 

        (1)、降低耦合度。它将请求的发送者和接收者解耦。

        (2)、简化了对象。使得对象不需要知道链的结构。

        (3)、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。

        (4)、增加新的请求处理类很方便。

       缺点: 

        (1)、不能保证请求一定被接收。

        (2)、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。

        (3)、可能不容易观察运行时的特征,有碍于除错。

       使用场景: 

        (1)、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

        (2)、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

        (3)、可动态指定一组对象处理请求。

       注意事项:在 JAVA WEB 中遇到很多应用。

      3、实例

       我们创建抽象类 AbstractLogger,带有详细的日志记录级别。然后我们创建三种类型的记录器,都扩展了 AbstractLogger。每个记录器消息的级别是否属于自己的级别,如果是则相应地打印出来,否则将不打印并把消息传给下一个记录器。

        

        (1)、创建抽象的记录器类

    public abstract class AbstractLogger {
       public static int INFO = 1;
       public static int DEBUG = 2;
       public static int ERROR = 3;
    
       protected int level;
    
       //责任链中的下一个元素
       protected AbstractLogger nextLogger;
    
       public void setNextLogger(AbstractLogger nextLogger){
          this.nextLogger = nextLogger;
       }
    
       public void logMessage(int level, String message){
          if(this.level <= level){
             write(message);
          }
          if(nextLogger !=null){
             nextLogger.logMessage(level, message);
          }
       }
    
       abstract protected void write(String message);
        
    }

        (2)、创建扩展了该记录类的实体类

    public class ConsoleLogger extends AbstractLogger {
    
       public ConsoleLogger(int level){
          this.level = level;
       }
    
       @Override
       protected void write(String message) {        
          System.out.println("Standard Console::Logger: " + message);
       }
    }
    public class ErrorLogger extends AbstractLogger {
    
       public ErrorLogger(int level){
          this.level = level;
       }
    
       @Override
       protected void write(String message) {        
          System.out.println("Error Console::Logger: " + message);
       }
    }
    public class FileLogger extends AbstractLogger {
    
       public FileLogger(int level){
          this.level = level;
       }
    
       @Override
       protected void write(String message) {        
          System.out.println("File::Logger: " + message);
       }
    }

        (3)、创建不同类型的记录器。赋予它们不同的错误级别,并在每个记录器中设置下一个记录器。每个记录器中的下一个记录器代表的是链的一部分。

    public class ChainPatternDemo {
        
       private static AbstractLogger getChainOfLoggers(){
    
          AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
          AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
          AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
    
          errorLogger.setNextLogger(fileLogger);
          fileLogger.setNextLogger(consoleLogger);
    
          return errorLogger;    
       }
    
       public static void main(String[] args) {
          AbstractLogger loggerChain = getChainOfLoggers();
    
          loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
    
          loggerChain.logMessage(AbstractLogger.DEBUG,  "This is an debug level information.");
    
          loggerChain.logMessage(AbstractLogger.ERROR, "This is an error information.");
       }
    }

        (4)、验证输出

    1 Standard Console::Logger: This is an information.
    2 File::Logger: This is an debug level information.
    3 Standard Console::Logger: This is an debug level information.
    4 Error Console::Logger: This is an error information.
    5 File::Logger: This is an error information.
    6 Standard Console::Logger: This is an error information.

    三、命令模式(Command Pattern)

      1、概念

       命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。

      2、简介

       意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

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

       如何解决:通过调用者调用接受者执行命令,顺序:调用者→接受者→命令。

       优点: 

        (1)、降低了系统耦合度。

        (2)、新的命令可以很容易添加到系统中去。

       缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

       使用场景:认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。

       注意事项:系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。

      3、实例

       我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。演示类 CommandPatternDemo,我们的演示类使用 Broker 类来演示命令模式。

        

        (1)、创建一个命令接口

    public interface Order {
       void execute();
    }

        (2)、创建一个请求类

    public class Stock {
        
       private String name = "ABC";
       private int quantity = 10;
    
       public void buy(){
          System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] bought");
       }
       public void sell(){
          System.out.println("Stock [ Name: "+name+", Quantity: " + quantity +" ] sold");
       }
    }

        (3)、创建实现了 Order 接口的实体类

    public class BuyStock implements Order {
       private Stock abcStock;
    
       public BuyStock(Stock abcStock){
          this.abcStock = abcStock;
       }
    
       public void execute() {
          abcStock.buy();
       }
    }
    public class SellStock implements Order {
       private Stock abcStock;
    
       public SellStock(Stock abcStock){
          this.abcStock = abcStock;
       }
    
       public void execute() {
          abcStock.sell();
       }
    }

        (4)、创建命令调用类

    import java.util.ArrayList;
    import java.util.List;
    
       public class Broker {
       private List<Order> orderList = new ArrayList<Order>(); 
    
       public void takeOrder(Order order){
          orderList.add(order);        
       }
    
       public void placeOrders(){
          for (Order order : orderList) {
             order.execute();
          }
          orderList.clear();
       }
    }

        (5)、使用 Borker 类来接受并执行命令

    public class CommandPatternDemo {
       public static void main(String[] args) {
          Stock abcStock = new Stock();
    
          BuyStock buyStockOrder = new BuyStock(abcStock);
          SellStock sellStockOrder = new SellStock(abcStock);
    
          Broker broker = new Broker();
          broker.takeOrder(buyStockOrder);
          broker.takeOrder(sellStockOrder);
    
          broker.placeOrders();
       }
    }

        (6)、演示结果

    1 Stock [ Name: ABC, Quantity: 10 ] bought
    2 Stock [ Name: ABC, Quantity: 10 ] sold

    四、解释器模式(Interpreter Pattern)

      1、概念

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

      2、简介

       意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。

       主要解决:对于一些固定文法构建一个解释句子的解释器。

       何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

       如何解决:构建语法树,定义终结符与非终结符。

       关键代码:构件环境类,包含解释器之外的一些全局信息,一般是 HashMap。

       应用实例:编译器、运算表达式计算。

       优点: 

        (1)、可扩展性比较好,灵活。

        (2)、增加了新的解释表达式的方式。

        (3)、易于实现简单文法。

       缺点: 

        (1)、可利用场景比较少。

        (2)、对于复杂的文法比较难维护。

        (3)、解释器模式会引起类膨胀。

        (4)、解释器模式采用递归调用方法。

       使用场景: 

        (1)、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。

        (2)、一些重复出现的问题可以用一种简单的语言来进行表达。

        (3)、一个简单语法需要解释的场景。

       注意事项:可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。

      3、实例

       我们将创建一个接口 Expression 和实现了 Expression 接口的实体类。定义作为上下文中主要解释器的 TerminalExpression 类。其他的类 OrExpression、AndExpression 用于创建组合式表达式。演示类 InterpreterPatternDemo,我们的演示类使用 Expression 类创建规则和演示表达式的解析。

        

        (1)、创建一个表达式接口

    public interface Expression {
       public boolean interpret(String context);
    }

        (2)、创建实现了 Expression 接口的实体类

    public class TerminalExpression implements Expression {
        
       private String data;
    
       public TerminalExpression(String data){
          this.data = data; 
       }
    
       @Override
       public boolean interpret(String context) {
          if(context.contains(data)){
             return true;
          }
          return false;
       }
    }
    public class OrExpression implements Expression {
         
       private Expression expr1 = null;
       private Expression expr2 = null;
    
       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);
       }
    }
    public class AndExpression implements Expression {
         
       private Expression expr1 = null;
       private Expression expr2 = null;
    
       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);
       }
    }

        (3)、InterpreterPatternDemo 使用 Expression 类来创建规则,并解析它们。

    public class InterpreterPatternDemo {
    
       //规则: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 is male? " + isMale.interpret("John"));
          System.out.println("Julie is a married women? " + isMarriedWoman.interpret("Married Julie"));
       }
    }

        (4)、演示结果

    1 John is male? true
    2 Julie is a married women? true

    五、迭代器模式(Iterator Pattern)

      1、概念

       迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式属于行为型模式。

      2、简介

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

       主要解决:不同的方式来遍历整个整合对象。

       何时使用:遍历一个聚合对象。

       如何解决:把在元素之间游走的责任交给迭代器,而不是聚合对象。

       应用实例:JAVA 中的 iterator。

       优点: 

        (1)、它支持以不同的方式遍历一个聚合对象。

        (2)、迭代器简化了聚合类。

        (3)、在同一个聚合上可以有多个遍历。

        (4)、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。

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

       使用场景: 

        (1)、访问一个聚合对象的内容而无须暴露它的内部表示。

        (2)、需要为聚合对象提供多种遍历方式。

        (3)、为遍历不同的聚合结构提供一个统一的接口。

       注意事项:迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

      3、实例

       我们将创建一个叙述导航方法的 Iterator 接口和一个返回迭代器的 Container 接口。实现了 Container 接口的实体类将负责实现 Iterator 接口。演示类 IteratorPatternDemo,我们的演示类使用实体类 NamesRepository 来打印 NamesRepository 中存储为集合的 Names。

        

        (1)、创建接口

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

        (2)、创建实现了 Container 接口的实体类。该类有实现了 Iterator 接口的内部类 NameIterator。

    public class NameRepository implements Container {
       public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};
    
       @Override
       public Iterator getIterator() {
          return new NameIterator();
       }
    
       private class NameIterator implements Iterator {
    
          int index;
    
          @Override
          public boolean hasNext() {
             if(index < names.length){
                return true;
             }
             return false;
          }
    
          @Override
          public Object next() {
             if(this.hasNext()){
                return names[index++];
             }
             return null;
          }        
       }
    }

        (3)、使用 NameRepository 来获取迭代器,并打印名字。

    public class IteratorPatternDemo {
        
       public static void main(String[] args) {
          NameRepository namesRepository = new NameRepository();
    
          for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
             String name = (String)iter.next();
             System.out.println("Name : " + name);
          }     
       }
    }

        (4)、演示结果

    1 Name : Robert
    2 Name : John
    3 Name : Julie
    4 Name : Lora

    PS:因作者能力有限,如有误还请谅解;

  • 相关阅读:
    G. Yash And Trees 线段树 + dfs序 + bitset
    网络流最小割 H
    P2764 最小路径覆盖问题 网络流重温
    P4016 负载平衡问题 网络流重温
    D. Yet Another Subarray Problem 思维 难 dp更好理解
    J
    20190709 暑训 区间种类数 莫队的学习
    E
    线段树 离散化 E. Infinite Inversions E. Physical Education Lessons
    CbsPersist
  • 原文地址:https://www.cnblogs.com/WHL5/p/9198858.html
Copyright © 2020-2023  润新知