设计模式---行为型模式

1.责任链模式(Chain of Responsibility)
2.命令模式(Command)
3.解释器模式(Interpreter)
4.迭代器模式(Iterator)
5.中介者模式(Mediator)
6.备忘录模式(Memento)
7.观察者模式(Observer)
8.状态模式(State)
9.策略模式(Strategy)
10.模板方法模式(Template Method)
11.访问者模式(Visitor)

行为型关注点:关注对象之间的交互通信(方法级别)


模板方法模式(Template Method)

定义:

定义一个操作中的算法骨架,而将一些步骤延迟到子类中。

通俗来说

在父类中设计主流程,将主流程步骤的实现方法交给子类来实现。这样就实现了方法的延迟。

角色
  • 抽象模板类(AbstractClass):,就是一个抽象模板,定义算法的骨架。
  • 具体实现类(ConcreteClass):实现算法其中某些过程方法,每个具体实现类给出算法的不同实现,从而使得抽象模板中骨架方法的实现各不相同。
模板方法类图

抽象模板类:

public abstract class AbstractClass {

    public abstract void primitiveOperation1();

    public abstract void primitiveOperation2();

    // 模板方法,给出了逻辑的骨架
    // 而逻辑的组成是一些相应的抽象操作,他们都推迟到子类实现
    public void templateMethod() {

        primitiveOperation1();
        primitiveOperation2();
        System.out.println("");
    }
}

具体实现类A:

public class ConcreteClassA extends AbstractClass {

    @Override
    public void primitiveOperation1() {

        System.out.println("具体类A方法1实现");
    }

    @Override
    public void primitiveOperation2() {

        System.out.println("具体类A方法2实现");
    }
}

具体实现类B:

public class ConcreteClassA extends AbstractClass {

    @Override
    public void primitiveOperation1() {

        System.out.println("具体类B方法1实现");
    }

    @Override
    public void primitiveOperation2() {

        System.out.println("具体类B方法2实现");
    }
}

客户端调用类

public class Client {

    public static void main(String[] args) {

        AbstractClass c;

        c = new ConcreteClassA();
        c.templateMethod();

        c = new ConcreteClassB();
        c.templateMethod();
    }
}
优点:
  • 1.模板方法模式通过把不变的行为搬移到父类,去除了子类中重复的代码。
  • 2.通过子类扩展增加新的行为,符合开闭原则。
应用场景:

多个子类有共有的方法,并且逻辑基本相同。
重构时,模板方法是一个经常使用的方法。把相同代码抽取到父类中,然后通过构造函数约束其行为。


策略模式(Strategy)

定义

策略模式定义了一系列算法,并将每个算法封装起来,让它们之间可以互相替换,让算法的变化独立于使用算法的客户。

通俗来说

通过注入不同的类,改变业务方法的业务行为通过选择策略类,来执行不同算法分支。。此模式就是springIOC的思想。核心是通过注入对象,改变行为。

通过改变注入的对象,从而改变它的行为。

通过实例化不同的class对象,改变spring业务方法的业务行为。IOC思想的雏形。

角色:
  • 上下文角色(Context):也叫Context封装角色,起承上启下的作用,屏蔽高层模块对策略算法的直接访问,封装可能存在的变化。
  • 抽象策略角色(AbstractStrategy):是对策略算法家族的抽象,通常为借口,定义每个策略算法必须具有的方法和属性。
  • 具体策略角色(ConcreteStrategy):用于实现抽象策略中的操作,即实现的具体算法。


    image.png

context上下文:

public class Context {
    Strategy strategy;
    
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
    
    //上下文接口
    public void contextInterface() {
        strategy.algorithmInterface();
    }

}

抽象策略角色:

public interface Strategy {
    //算法方法
    void algorithmInterface();

}

具体策略角色:

public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("算法A实现");
    }

}

调用代码:

public class Client {
    
    public static void main(String[] args) {
        Context context;
        
        context = new Context(new ConcreteStrategyA());
        context.contextInterface();
        
        context = new Context(new ConcreteStrategyB());
        context.contextInterface();
        
        context = new Context(new ConcreteStrategyC());
        context.contextInterface();
    }

}
优点:
  • 算法可以自由切换。
  • 避免使用多重条件转移语句。
  • 扩展性好。
缺点:
  • 用户需要知道所有策略类,并自行决定使用哪一种策略。
  • 每个算法都会产生一个策略类,造成策略类过多。
应用场景:

线程池的拒绝策略 默认四种就是策略模式
java AWT中的layoutManager 布局管理器。

策略模式和装饰模式的区别

策略模式侧重于算法的自由替换,IOC的一种实现思想,关注的是方法级别的。
装饰模式侧重于对类的增强,需要了解每一个装饰器做什么功能,是关注类级别组合。


责任链模式(Chain of Responsibility)

定义

请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

通俗来说

将请求传给一个接收者链,由链将请求流转给目标对象。
当某一个业务请求,多个对象需要对这个请求做处理,就可以使用责任链模式。把所有对象串成一个链式结构,让请求在链中流动。

责任链和组合模式有点相似,组合模式是适合树形结构的业务逻辑,而链表呢是一种特殊的树,只有一个后继节点。责任链就是这种特殊的组合模式。

角色
  • 抽象处理者角色(handler):定义出一个处理请求的接口,如果需要,接口也可以定义出一个模板方法以设定固定的流转流程。这个角色通常是一个抽象类或者java接口。角色内包含下个节点的引用。

  • 具体处理者角色(ConcreteHandler):具体处理者,接到请求后,可以选择将请求处理,或流转到下个实例。

责任链模式类图

抽象处理者:

public abstract class Handler {
    
    /**
     * 持有后继的责任对象
     */
    protected Handler successor;

    public Handler (Handler handler) {
        this.successor = handler;
    }
    /**
     * 示意处理请求的方法,虽然这个示意方法是没有传入参数的
     * 但实际是可以传入参数的,根据具体需要来选择是否传递参数
     */
    public abstract void handleRequest();
    /**
     * 取值方法
     */
    public Handler getSuccessor() {
        return successor;
    }
    /**
     * 赋值方法,设置后继的责任对象
     */
    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }
    
}

具体处理者:

public class ConcreteHandler extends Handler {
    /**
     * 处理方法,调用此方法处理请求
     */
    @Override
    public void handleRequest() {
        /**
         * 判断是否有后继的责任对象
         * 如果有,就转发请求给后继的责任对象
         * 如果没有,则处理请求
         */
        if(getSuccessor() != null)
        {            
            System.out.println("放过请求");
            getSuccessor().handleRequest();            
        }else
        {            
            System.out.println("处理请求");
        }
    }
 
}

调用方:

public class Client {
 
    public static void main(String[] args) {
        //组装责任链
        Handler handler = new ConcreteHandler(null);
        handler = new ConcreteHandler(handler);
        
        //提交请求
        handler1.handleRequest();
    }

可以将流转下一个流程的代码抽象到父类中,由一个模板方法模式进行封装,所有子类流转的代码都是相同的。

  if(null != getSuccessor()){
    getSuccessor().handleRequest();
  }
优点:

简化了调用,使用者不需要知道链的结构
责任分离处理,让各个节点各司其职。
动态地增加减少责任链

应用场景:

spring的拦截器 就是一个责任链模式
servlet的filter 就是一个责任链模式
springsecurity 的核心逻辑也是一个过滤器链。
做审批处理的时候。


命令模式(Command)

定义

将请求、命令封装成对象,这样可以让项目使用这些对象来参数化其他对象。是的命令的请求者和执行者解耦。

通俗来说

可以使发送者接收者解耦,发送者和接收者之间并没有直接的引用关系。发送者只知道如何发送请求,不需要知道如何完成请求。

角色:
  • 调用者(Invoker) :就是具体的调用者。
  • 接收者(receiver) :如何实施和执行一个请求相关的一个操作。
  • 抽象命令角色(command):需要执行的所有命令都在这里,可以使接口或抽象类
  • 具体命令角色(ConcreteCommand):将一个接收者对象与一个命令绑定,调用接收者响应的操作。
命令模式类图

抽象命令

/**
 * 命令抽象父类
 */
public abstract class Commond {

    private Barbecue barbecue;

    public Commond(Barbecue barbecue) {
        super();
        this.barbecue = barbecue;
    }

    public abstract void excuteCommond();

    public Barbecue getBarbecue() {
        return barbecue;
    }

    public void setBarbecue(Barbecue barbecue) {
        this.barbecue = barbecue;
    }
}

具体命令1

/**
 * 烤羊腿命令
 */
public class MuttonCommod extends Commond{

    public MuttonCommod(Barbecue barbecue) {
        super(barbecue);
    }
    @Override
    public void excuteCommond() {
        super.getBarbecue().makeMutton();
    }

}

具体命令2

/**
 * 烤鸡翅命令
 */
public class ChickenCommond extends Commond{

    public ChickenCommond(Barbecue barbecue) {
        super(barbecue);
    }
    @Override
    public void excuteCommond() {
        super.getBarbecue().makeChicken();
    }

}

接收者(Receiver)

/**
 * 烧烤师傅
 */
public class Barbecue {

    public void makeMutton() {
        System.out.println("烤羊肉串");
    }

    public void makeChicken() {
        System.out.println("考鸡肉串");
    }
}

调用者(Invoker)

/**
 * 服务员
 */
public class Waiter {

    private List commonds = new ArrayList<>();

    public void addCommond(Commond commond) {
        // TODO 可以做很多事情  记日志等等
        commonds.add(commond);
    }

    public void removeCommond(Commond commond) {
        // TODO 可以做很多事情  记日志等等
        commonds.remove(commond);
    }

    public void Notify() {
        for (Commond commond : commonds) {
            commond.excuteCommond();
        }
    }
}
优点:

1.降低耦合。
2.容易扩展新命令或者一组命令。

缺点:

命令的无限扩展会增加类的数量,提高了系统的复杂度。

命令模式和策略模式的区别:
  • 命令模式和策略模式很相似,只是命令模式多了一个接收者的角色。
  • 策略模式关注的是算法的替换问题,或者提供多种算法由调用者选择使用。算法的自由替换是它的要点。
  • 命令模式则是对调用方和接收方的解耦,要求把请求的内容封装成一个一个的命令,由接收者执行。由于封装成了命令就可以对命令进行多种处理,撤销 排队等等。
  • 策略模式适用于算法要求变换的场景。命令模式适合两个紧耦合关系的对象解耦 或者多命令多撤销的场景。

举两个具体的例子:

1.如果要实现压缩 我们选择zip算法 和gzip算法 那么策略模式就会设计成两个策略:zip 一个策略 里面包含了 压缩和解压缩的方法,gzip一个策略 也包含一个压缩和一个解压缩的算法。
而命令模式就会设计成4个命令 zip压缩命令、zip解压缩命令、gzip压缩命令、gzip解压缩命令。
由此看出命令模式关注的是通过封装成命令来解耦。

2.另外一种情况说明命令模式可以像适配器模式一样,作为一种补救办法。假设有A、B、C三个类,三个类的功能大体相似。但没有实现同一接口,这个时候我们可以用命令模式解除他们与调用者之间的紧耦合。封装三个命令实现类,来聚合A、B、C三个类,通过命令的方式让调用者不直接操作A、B、C三个类。而此时如果是想用策略模式去实现,需要修改大量的代码 让A、B、C去实现相同的接口,并不适用。

命令模式和外观模式的区别:
  • 1.外观模式是结构型模型,命令模式是行为型模型。
  • 2.外观模式关注类如何组合聚合来解耦,命令模式关注方法间的通信来解耦。
  • 3.外观模式主要为了简化操作。封装内部,不符合开闭原则,新增需要修改外观类。命令模式主要为了发送方接收方解耦,符合开闭原则
  • 4.控制粒度不一样。还是用遥控器来举例,命令模式像一个多按钮的遥控器,每个按钮是一个命令,调用方使用某些按钮进行开关操作,并且还支持等待执行的行为。命令模式能细粒度的控制每个按钮的执行顺序,执行是否撤回。而外观模式则是更像遥控器上只有一个按钮,而这一个按钮执行多个设定好顺序的开关操作。并且中间不能撤回。外观模式无法穿透外观类(或接口)去细粒度的控制内部的执行顺序或某些特殊的逻辑。
    命令模式控制内部细节操作,外观模式控制整体操作。
应用场景:
  • 1.请求的调用者和接收者,需要解耦。使得调用者和接收者不直接交互。
  • 2.需要抽象出等待执行的行为。
  • 3.命令模式适合做可撤销类的应用。
    1. 对没有使用面向接口编程的时候,就可以从中间加入一个command命令抽象层,用命令模式来改造调用方式。来解耦。

解释器模式(Interpreter)

定义

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

通俗来说

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

角色:
  • 抽象表达式(Expression):抽象表达式,定义解释器的接口,约定解释器的解释操作,主要包含一个interpret()方法。
  • 终结表达式(TerminalExpression):是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
  • 非终结表达式(NonTerminalExpression):也是抽象表达式的子类,用来实现文法中非终结符的相关操作,文法中每条规则都对应一个非终结符表达式。
  • 上下文容器(Context):context是环境角色,包含解释器之外的一些全局信息


    解释器模式类图

通过解释器实现四则运算,如计算加减法表达式,UML如图:


加减法表达式类图

上下文容器context

public class Context{
    
    //定义表达式
    private Expression expression;
    
    //构造函数传参,并解析
    public Context(String expStr) {
        //安排运算先后顺序
        Stack stack = new Stack<>();
        //表达式拆分为字符数组
        char[] charArray = expStr.toCharArray();
        
        Expression left = null;
        Expression right = null;
        for(int i=0; i var) {
        return this.expression.interpreter(var);
    }

}

抽象表达式

public abstract class Expression {
    //解析公式和数值,key是公式中的参数,value是具体的数值
    public abstract int interpreter(HashMap varMap)
}

非终结解释器(变量解释器)

public class VarExpression extends Expression {
    
    private String key;
    
    public VarExpression(String key) {
        this.key = key;
    }

    @Override
    public int interpreter(HashMap var) {
        return var.get(this.key);
    }

}

终结解释器(抽象运算符解释器)

public class SymbolExpression extends Expression {
    
    protected Expression left;
    protected Expression right;

    public SymbolExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpreter(HashMap var) {
        // TODO Auto-generated method stub
        return 0;
    }
    
}

终结解释器实现1 (加法解释器)

public class AddExpression extends SymbolExpression {

    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }
    
    public int interpreter(HashMap var) {
        return super.left.interpreter(var) + super.right.interpreter(var);
    }

}

终结解释器实现2(减法解释器)

public class SubExpression extends SymbolExpression {
    
    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }
    
    public int interpreter(HashMap var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }

}

调用方

public class Client {
    
    public static void main(String[] args) throws IOException {
        String expStr = getExpStr();
        HashMap var = getValue(expStr);
        Context context = new Context (expStr);
        System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
    }
    
    //获得表达式
    public static String getExpStr() throws IOException {
        System.out.print("请输入表达式:");
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    }
    
    //获得值映射
    public static HashMap getValue(String expStr) throws IOException {
        HashMap map = new HashMap<>();
        
        for(char ch : expStr.toCharArray()) {
            if(ch != '+' && ch != '-' ) {
                if(! map.containsKey(String.valueOf(ch))) {
                    System.out.print("请输入" + String.valueOf(ch) + "的值:");
                    String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }
        
        return map;
    }
    
}
优点:

解决特定重复出现的场景。可扩展性好

缺点:

1.解释器模式会引起类膨胀
2.解释器模式采用递归调用方法,导致调试复杂。
3.使用大量循环递归,效率是一个不容忽视的问题。
尽量不要在重要的模块中使用解释器模式,否则维护是一个很大的问题。

应用场景:

1.正则表达式
2.Spring el表达式
3.corn表达式


迭代器模式(Iterator)

定义

提供一种遍历集合元素的统一接口,用一致的方法遍历集合元素,不需要知道集合对象的底层表示,即不暴露其内部结构。

通俗来说

用户通过特定的接口访问容器的数据,不需要了解容器内部的数据结构。

角色:
  • 抽象迭代器(Iterator):迭代器接口,提供hasNext、next、remove方法。
  • 具体迭代器(ConcreteItorator):具体的迭代器类,管理迭代
  • 聚合接口(aggregate):一个统一的聚合接口,将调用方和具体的聚合解耦。包含一个createIterator方法。
  • 具体聚合(ConcreteAggregate):持有对象的集合,并提供createIterator的具体方法。
  • element(元素)


    迭代器类图

聚合接口

public interface Aggregate{
    public abstract Iterator createIterator();
}

迭代器接口

public interface Iterator{
     boolean hasNext();
     T next();
    void remove();
}

元素

public class Book{
    private String name ;
    public Book(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
}

具体聚合

public class BookShelf implements Aggregate {

    private List books;


    public BookShelf() {
        this.books = new ArrayList();
    }

    public Book getBookAt(int index) {
        return books.get(index);
    }

    public void appendBook(Book book) {
        books.add(book);
    }

    public int getLength() {
        return books.size();
    }

    public Iterator iterator() {
        return new BookShelfIterator(this);
    }
}

具体迭代器

public class BookShelfIterator implements Iterator {

    private BookShelf bookShelf;
    private int index;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }

    public boolean hasNext() {
        if (index < bookShelf.getLength()) {
            return true;
        } else {
            return false;
        }
    }


    public Object next() {
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
    }
}
优点:

1.面向接口编程
2.将元素的遍历和实现分离开来

为什么不让容器直接继承Iterator接口,Iterable接口会不会有一种多此一举的感觉?

个人认为是为了让数据处理逻辑和容器内部数据管理进行分离,提供容器数据的安全性。

应用场景:

需要遍历的场景,提供多种遍历。而无需暴露其内部结构。
JDK中 Collection包下基本都实现了iterator

  • fail-fast:直接在容器上进行遍历,在遍历过程中一点发现容器被修改,会抛出ConcurrentModificationException异常导致遍历失败,常见的hashMap arrayList
  • fail-safe:遍历时复制一份容器,对容器修改不影响遍历。ConcurrentHaspMap和CopyOnWrite容器

中介者模式(Mediator)

定义

定义一个中介者对象, 封装一系列对象的交互关系, 使得各对象不必显示的相互引用, 从而使其耦合松散, 而且可以独立的改变它们的交互

通俗来说

多个类相互耦合,会形成网状结构,使用中介者模式将网站结构分离为星状结构,由中介者来控制这些类之间消息的相互流转,从而使其耦合松散。

角色
  • 1.中介者(Mediator):抽象中介者,用于定义统一接口,用于各同事之间的交互。

  • 2.具体中介者(ConcreteMediator):具体中介者持有各同事对象的引用,负责协调各同事对象的行为以完成协作,因此具体中介者必须依赖具体同事对象。(不符合依赖倒置原则)

  • 3.同事角色(Colleague):每个同事角色都知道中介者对象,而且与其他同事角色通信的时候,都通过中介者协作完成。每个同事角色都有两种行为:

    • 1.自发行为:同事本身的行为,如修改自身的状态,与其他同事或者中介者没有任何联系。
    • 2.依赖方法:必须依赖中介者才能完成的行为。
  • 4.具体同事角色(ConcreteColleague): 实现抽象同事角色。

中介者模式类图

中介者

public interface Mediator {
    /**
     * 注册同事方法 可以使用构造器 而不用此方法
     */
    void register(Member member);

    /**
     * 发出命令方法
     */
    void notifyOthers(String info,Member member);

具体中介者

public class ConcreteMediator implements Mediator  {
   private HashMap  memberMap = new HashMap();

  public void register( Member member) {
        memberMap.put(member.getClass().getSimpleName(), member);
    }

    public void notifyOthers(String info, Member member){
        String name = member.getClass().getSimpleName();
        switch (name){
            case "Member01": {
                memberMap.get("Member02").recv(info);
                memberMap.get("Member03").recv(info);
                break;
            }
            case "Member02": {
                memberMap.get("Member01").recv(info);
                memberMap.get("Member03").recv(info);
                break;
            }
            case "Member03": {
                memberMap.get("Member01").recv(info);
                memberMap.get("Member02").recv(info);
                break;
            }
        }
    }
}

同事角色

public abstract class Member {
    protected Mediator mediator;
    public Mediator getMediator() {
        return mediator;
    }

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }
    //依赖方法 将调用行为传递给中介者,让中介者具体进行下一步操作。  也可以将此方法延迟到子类实现
    public void send(String info){
        mediator.notifyOthers(info,this);
    }
    //自发行为---中介者调用  
    public abstract  void recv(String info);  
}

具体同事角色1

public class Member01 extends Member{
    public void recv(String info){
       System.out.println(this.getClass().getName() + " 收到消息: " + info +"进行处理1");
    }
}

具体同事角色2

public class Member02 extends Member{
    public void recv(String info){
       System.out.println(this.getClass().getName() + " 收到消息: " + info +"进行处理2");
    }
}

具体同事角色3

public class Member03 extends Member{
    public void recv(String info){
       System.out.println(this.getClass().getName() + " 收到消息: " + info +"进行处理3");
    }
}

调用方

public class Client {
    public static void main(String[] args) {

        Member member01 = new Member01();
        Member member02 = new Member02();
        Member member03 = new Member03();

        Mediator mediator = new Mediator();
        mediator.register(member01);
        mediator.register(member01);
        mediator.register(member01);

        member01.setMediator(mediator);
        member02.setMediator(mediator);
        member03.setMediator(mediator);

        member01.notifyOthers("消息1");
        member02.notifyOthers("消息2");
        member03.notifyOthers("消息3");
    }
}

优点:

1.多个类相互耦合,会形成网状结构,使用中介者模式将网站结构分离为星状结构,进行解耦。
2.减少类间的依赖,降低耦合,符合迪米特法则。

缺点:

中介者是将各个类中的耦合,转移到自己的内部,中介者承担了更多的责任,一个中介者出现问题,整个系统就会收到影响。
如果设计不当,中介者对象本身会过于复杂。 本身并不符合开闭原则。
应用场景:
将网状依赖关系转化为星状依赖关系。

中介者模式、代理模式、外观模式区别

外观模式(Facade Pattern):定义一个外观类,隐藏系统的复杂性,为客户端提供简化的方法和对现有系统类方法的委托调用。
代理模式(Proxy Pattren):用一个代理类代表另一个类的功能,但是不改变被代理类的功能。目的是控制对被代理类的访问。
中介者模式(Mediator Pattern):各个类之间互相交互,形成网状结构,中介者使各对象之间不直接相互引用。从而耦合松散。从而形成网状结构。例如MVC中的C 就是view和model的中介者。让view与model 不网状交互。


观察者模式(Observer)

定义

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

通俗来说

又被称为发布-订阅模式 一个对象状态改变时,通知其他对象。为的是尽量弱化对象间的依赖。

角色

抽象主题(Subject):也叫抽象目标类,提供了一个用于保存观察者对象的集合,和增加删除,以及通知观察者的方法。
具体主题(ConcreteSubject):实现通知方法的具体类。
抽象观察者(Observer):是一个抽象类或接口,包含一个update方法。当接到主题更改通知时被调用。
具体观察者(ConcreteOberser):抽象观察者的实现。

观察者模式类图

抽象主题(Subject)

public class Subject {

    //观察者数组
    private Vector oVector = new Vector<>();
    
    //增加一个观察者
    public void addObserver(Observer observer) {
        this.oVector.add(observer);
    }
    
    //删除一个观察者
    public void deleteObserver(Observer observer) {
        this.oVector.remove(observer);
    }
    
    //通知所有观察者
    public void notifyObserver() {
        for(Observer observer : this.oVector) {
            observer.update();
        }
    }
    
}

具体主题(ConcreteSubject)

public class ConcreteSubject extend Subject {
   public void doSomething() {
      //...
      super.notifyObserver();
   }

}

抽象观察者(Observer)

public interface Observer {
    void update();
}

具体观察者(ConcreteObserver)

public class ConcreteObserver implements Observer {

    @Override
    public void update() {
        System.out.println("收到消息,进行处理");
    }

}

调用方

public class Client {
    
    public static void main(String[] args) {
        //创建一个主题
        ConcreteSubject subject = new ConcreteSubject();
        //定义一个观察者
        Observer observer = new ConcreteObserver();
        //观察
        subject.addObserver(observer);
        //开始活动
        subject.doSomething();
    }
    
}
优点:

1.实现了触发机制。
2.观察者和被观察者是弱耦合。

观察者模式和消息中间件这种发布订阅模式区别

发布订阅是 通知到broker的topic中 broker再通知订阅方。中间是有数据传输。没有耦合。而观察者模式是有耦合的。
发布订阅更像是观察者模式+命令模式,观察者负责事件触发,有消息了观察者处理触发,命令模式负责解耦发送方接收方,传输的数据其实就是命令。发送方并不知道具体会由哪个接收方处理。

应用场景:

事件的触发场景
发布订阅这种,跨系统消息变换场景

JDK对观察者模式的支持
Observer接口 观察者接口 观察者需要实现的接口
Observable类 是观察的目标发布方


备忘录模式(Memento)

定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。

通俗来说

给用户提供一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史状态。

角色
  • 原发器(originator):是一个普通的类,创建备忘录的原始角色,备忘录存储它的当前内部状态,也可以使用备忘录来恢复其内部状态。
  • 备忘录(Memento):存储原发器的内部状态,根据原发器来决定保存哪些内部状态备忘录一般可以参考原发器设计,根据实际需要确定备忘录中的属性。需要注意的是,除了原发器和守护者类之外,备忘录对象不能直接供其他类使用。
  • 守护者(CareTaker):守护者又称(管理者,负责人)。他负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在守护者勒种,可以存储多个备忘录对象。但是他只负责存储,而不能修改对象,也无需知道对象的实现细节。
备忘录模式类图

原发器

public class Originator {
   private String state;
 
   public void setState(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }
 
   public Memento saveStateToMemento(){
      return new Memento(state);
   }
 
   public void getStateFromMemento(Memento Memento){
      state = Memento.getState();
   }
}

备忘录

public class Memento {
   private String state;
 
   public Memento(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }  
}

守护者

public class CareTaker {
   private List mementoList = new ArrayList();
 
   public void add(Memento state){
      mementoList.add(state);
   }
 
   public Memento get(int index){
      return mementoList.get(index);
   }
}

调用方

public class MementoPatternDemo {
   public static void main(String[] args) {
      Originator originator = new Originator();
      CareTaker careTaker = new CareTaker();
      originator.setState("State #1");
      originator.setState("State #2");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #3");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #4");
 
      System.out.println("Current State: " + originator.getState());    
      originator.getStateFromMemento(careTaker.get(0));
      System.out.println("First saved State: " + originator.getState());
      originator.getStateFromMemento(careTaker.get(1));
      System.out.println("Second saved State: " + originator.getState());
   }
}
优点:

1.提供了状态恢复机制。
2.提供了封装,用户不需要关心状态的保存细节。
缺点:
如果成员变量过多,势必会占用比较大的资源,而且每次保存都会消耗一定的内存。

应用场景:
  • 1.浏览器中的后退
  • 2.springSecurity 登录后跳转到之前访问的页
  • 3.打游戏时的存档
  • 4.windows的ctrl+z
  • 5.数据库里的事务管理。

一般备忘录模式和原型模式配合使用。


状态模式(State)

定义

当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

通俗来说

状态模式很像策略模式,但是关注于状态的转换而改变内在行为,而且状态之间相互有依赖。
状态模式也能减少代码中的if else 判断不同的状态

角色
  • 抽象状态(State):定义一个接口,来封装使用Context的一个特定状态相关的行为。
  • 具体状态(ConcreteState):抽象状态的实现。
  • 上下文环境(Context):定义了客户程序需要的接口,并且维护一个状态角色的实例,将与状态相关的操作委托给状态角色实例来处理。


    状态模式类图

抽象状态

public abstract class AbstractState {

    /** 封装了上下文 */
    protected Context context;

    public void setContext(Context  context){
        this.context = context;
    }

    /** 执行方法 */
    abstract void run();

    /** 切换至下一个状态 */
    abstract void next();
}

具体状态1

public class AState extends AbstractState {

    /** 执行方法 */
    @Override
    public void run() {
        System.out.println("执行 AState 的 run() 方法");
    }

    /** 切换至下一个状态 */
    @Override
    void next() {
        System.out.println("执行 AState 的 next() 方法");
        // 定义了下一个状态是 BState
        context.setState(new BState());
    }
}

具体状态2

public class BState extends AbstractState {

    /** 执行方法 */
    @Override
    public void run() {
        System.out.println("执行 BState 的 run() 方法");
    }

    /** 切换至下一个状态 */
    @Override
    void next() {
        System.out.println("执行 BState 的 next() 方法");
        // 定义了下一个状态是 CState
        context.setState(new CState());
    }
}

具体状态3

public class CState extends AbstractState {

    /** 执行方法 */
    @Override
    public void run() {
        System.out.println("执行 CState 的 run() 方法");
    }

    /** 切换至下一个状态 */
    @Override
    void next() {
        System.out.println("执行 CState 的 next() 方法");
        System.out.println("已经是终态了!");
    }
}

上下文环境

public class Context {

    private AbstractState state;

    public AbstractState getState() {
        return state;
    }
    /** 设置当前状态 */
    public void setState(AbstractState state) {
        this.state = state;
        // 记得 setContext,不然会空指针
        this.state.setContext(this);
    }
    /** 执行方法 */
    public void run(){
        this.state.run();
    }
    /** 下一个状态 */
    public void next(){
        this.state.next();
    }
}

调用方

public class TestMe {

    public static void main(String[] args){
        Context context = new Context();
        context.setState(new AState());

        context.run();
        System.out.println("当前状态:" + context.getState().toString());
        context.next();
        System.out.println("当前状态:" + context.getState().toString());

        context.run();
        System.out.println("当前状态:" + context.getState().toString());
        context.next();
        System.out.println("当前状态:" + context.getState().toString());

        context.run();
        System.out.println("当前状态:" + context.getState().toString());
        context.next();
        System.out.println("当前状态:" + context.getState().toString());

    }
}
优点

1.将状态相关的行为封装在一个状态中,符合单一职责。

缺点

状态较多的情况下,导致类膨胀。
结构和实现都比较复杂,使用不当造成代码结构混乱。
并不符合开闭原则,新增状态,变化到这个状态,需要修改与之关联的状态

应用场景

1.当一个对象的行为取决于它的状态,并且在运行时需要根据状态做出不同行为,可以使用状态模式。
2.一个操作中含有大量的if else 并且判断条件是状态的时候。

状态模式和策略模式的区别
  • 策略模式的客户端必须对所有策略类了解,权衡场景策略选择。对客户暴露。算法之间没有依赖关系。策略模式不依赖上下文。
  • 状态模式依赖于状态变化时,其内部行为变化。将动作委托到代表当前状态的对象,对外表现为类发生了变化。状态之间有依赖关系。状态模式需要依赖上下文容器。

访问者模式(Visitor)

设计模式中比较复杂的。

什么是双重分派?
https://www.jianshu.com/p/a42928e3f9d3
1.首先在客户端程序中,将 具体的Visit访问者为参数传递到了Element中(第一次分派)
2.然后element实现类 调用作为参数的visit中的具体方法,将自己(this)作为参数传入,完成第二次分派。

定义

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

通俗来说

利用双重分派机制,Element接口需要accept方法来接受一个访问者,而accept方法中又调用visit 中的具体方法,传入element自己(this) 一共这两次分派,来实现将数据元素和数据操作分离。适合对象结构不经常变化的情况。

角色
  • 抽象访问者(Visitor):是抽象访问者,为每一个ConcreteElement的每一个类声明一个visit操作。
  • 具体访问者(ConcreteVisitor):是一个具体的访问者,实现每个有Visitor声明的操作。是每个操作的实现部分。
  • 抽象元素(Element):定义一个accept方法,接收一个访问者对象。
  • 具体元素(ConcreteElement): 实现了accept方法。
  • 对象结构(ObjectStructure):
访问者模式类图

抽象访问者

interface Visitor {
   void visit(Games games);
   void visit(Photos photos);
}


抽象元素

interface Computer {
   void accept(Visitor visitor);
}

具体访问者

class ZhangSan implements Visitor {
   @Override
   public void visit(Games games) {
       games.play();
   }

   @Override
   public void visit(Photos photos) {
       photos.watch();
   }
}

class LiSi implements Visitor {
   @Override
   public void visit(Games games) {
       games.play();
   }
   @Override
   public void visit(Photos photos) {
       photos.watch();
   }
}

具体元素

class Games implements Computer {
   @Override
   public void accept(Visitor visitor) {
       visitor.visit(this);
   }

   public void play() {
       System.out.println("play lol");
   }
}

class Photos implements Computer {
   @Override
   public void accept(Visitor visitor) {
       visitor.visit(this);
   }
   
   public void watch() {
       System.out.println("watch scenery photo");
   }
}

对象结构

class ObjectStructure {

    private List computers = new ArrayList();

    public void action(Visitor visitor) {
        computers.forEach(c -> {
            c.accept(visitor);
        });
    }
    public void add(Computer computer) {
        computers.add(computer);
    }
}

调用方

public static void main(String[] args) {
       // 创建一个结构对象
       ObjectStructure os = new ObjectStructure();
       // 给结构增加一个节点
       os.add(new Games());
       // 给结构增加一个节点
       os.add(new Photos());
       // 创建一个访问者
       Visitor visitor = new ZhangSan();
       os.action(visitor);

}
优点

1.数据元素和数据操作解耦,使得操作集合可以独立变化。扩展性好。
2.符合单一职责

缺点

1.不适合对象结构经常变化的情况元素扩展比较困难,需要修改visitor以及visitor所有的实现类。
2.元素对访问者公布细节,违反了迪米特法则。
3.依赖具体类,而没有依赖接口,违反了依赖倒置原则。

应用场景

数据结构稳定,作用于数据结构的操作经常变化。
让静态分派转为动态分派的情况。

在遍历集合的过程中对元素进行固定的处理是常有的需求。Visitor模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是Vistor模式。

你可能感兴趣的:(设计模式---行为型模式)