行为型模式

279 阅读34分钟

行为型模式

行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。 通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤 立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。

行为型模式分为类行为型模式和对象行为型模式两种:

• 类行为型模式:类的行为型模式使用继承关系在几个类之间 分配行为,类行为型模式主要通过多态等方式来分配父类与 子类的职责。

• 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方 式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式

职责链模式

职责链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求。

链上的每一个对象都是请求处理者,职责链模式可以将请求的处理者组织成一条链,并使请求沿着链传递,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,将请求的发送者和请求的处理者解耦。这就是职责链模式的模式动机。

QCtxhj.md.png

目的是为了解耦合,将职责功能分开,确保单一职责原则的同时根据条件不同使用不同的请求处理对象,请求通过职责链一步一步往下传递,即请求往下传递直到传递到一个能解决的对象进行解决。即将多个能处理请求的对象连接成一个链,传递到能解决的对象上就进行处理(handle)

QCNzVO.md.png

模式结构 :

• Handler: 抽象处理者,定义一个处理请求的接口

• ConcreteHandler: 具体处理者,实现具体请求,维护对下家的引用

• Client: 客户类

使用职责链必须知道上下级的关系才能以链式方式处理请求,同时下级必须知道对应的上级是谁,就像链表一样传递,但是前提是必须得知道是谁,同时可以指定分配职责,这样就更加灵活,但相应的粒度级划分的更小,会产生更多对象。因此使用职责链必须要先组装链,正如同构建链表一样,抽象类必须维护一个successor,该successor就是上级对象,提供setter(),下级通过setter()注册上级,处理不了时直接调用successor进行传递。

public abstract class Leader {
    private Leader successor;//内部维护一个上级处理对象

    //通过该方法注入上级
    public void setSuccessor(Leader successor) {
        this.successor = successor;
    }

    public Leader getSuccessor() {
        return successor;
    }

    public abstract void handleRequest(String request);//必须处理请求
}
public class HandlerA extends Leader {
    @Override
    public void handleRequest(String request){
        if(request.equals("/test/1")){
            System.out.println("A处理"+request);//可以处理请求,进行处理
        }else{
            //无法处理,获取上级,向上传递
            if(getSuccessor()==null)System.out.println("链到底");
            else getSuccessor().handleRequest(request);//获取上级处理
        }
    }
}
public class HandlerB extends Leader {
    @Override
    public void handleRequest(String request){
        if(request.equals("/test/2")){
            System.out.println("B处理"+request);//可以处理请求,进行处理
        }else{
            //无法处理,获取上级,向上传递
            if(getSuccessor()==null)System.out.println("链到底");
            else getSuccessor().handleRequest(request);
        }
    }
}
public class HandlerC extends Leader{
    @Override
    public void handleRequest(String request){
        if(request.equals("/test/3")){
            System.out.println("C处理"+request);//可以处理请求,进行处理
        }else{
            //无法处理,获取上级,向上传递
            if(getSuccessor()==null)System.out.println("链到底,无法处理");
            else getSuccessor().handleRequest(request);
        }
    }
}
public class ChainOfResponsibilityPatternTest {
    public static void main(String[] args){
        //必须先组装链,正如同构建链表一样
        HandlerA handlerA=new HandlerA();
        HandlerB handlerB=new HandlerB();
        HandlerC handlerC=new HandlerC();
        //构建链
        handlerA.setSuccessor(handlerB);
        handlerB.setSuccessor(handlerC);

        handlerA.handleRequest("/test/2");
        handlerA.handleRequest("/test/3");
        handlerA.handleRequest("error");
    }
}
/*
B处理/test/2
C处理/test/3
链到底,无法处理
*/

这样处理没能屏蔽构造链的细节,可以进一步细化,创建一个专门的类专门生产链(就跟生产链表一样)

public class Handler extends HandlerA{
    public Handler(){
        HandlerB handlerB=new HandlerB();
        HandlerC handlerC=new HandlerC();
        //构建链
        super.setSuccessor(handlerB);
        handlerB.setSuccessor(handlerC);
    }
}
main中:
        Handler handler=new Handler();
        handler.handleRequest("/test/3");//C处理/test/3

在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理 这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织 链和分配责任。

职责链模式的主要优点在于可以降低系统的耦合度,简化对象的相互连接,同时增强给对象指派职责的灵活性,增加新的请求处理类也很方便;其主要缺点在于不能保证请求一定被接收,且对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。

职责链模式适用情况包括:有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定;在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;可动态指定一组对象处理请求。

命令模式

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

模式结构:

**Command类:**是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个 execute 方法用来执行命令。

**ConcreteCommand类:**Command类的实现类,对抽象类中声明的方法进行实现。内部维护Receiver,实际请求由Receiver执行

**Receiver类:**接收者,负责接收命令并且执行命令

**Invoker类:**调用者,负责调用命令。内部维护Command,通过注入不同的Command来达到执行不同命令的目的,请求接受者Receiver(也就是实际的处理者)有具体的Command来实现

仍然通过层级调用的方法通过增加层级类来达到解耦的目的,只需构造具体的Command,传递给invoker,交给invoker执行,invoker调用具体Command的execute,Command调用内部维护的Receiver执行

public interface Command {
    void execute(String request);
}
public class ConcreteCommand implements Command {
    //内部维护实际接受者,解耦合,提供构造器与setter,可交给接受者去处理
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver){
        this.receiver=receiver;
    }
    public void setReceiver(Receiver receiver) {
        this.receiver = receiver;
    }
    @Override
    public void execute(String request){
        System.out.println("请求到达命令者,但实际调用接受者Receiver的方法去执行");
        receiver.handle(request);
    }
}
//实际处理请求的接受者
public class Receiver {
    public void handle(String request){
        System.out.println("实际接受者,处理请求:"+request);
    }
}
//调度器,调度命令,解耦合,可根据不同的请求调度不同的命令
//提供构造器与setter进行具体的命令注入
public class Invoker implements Command{
    private Command command;
    public Invoker(Command command){
        this.command=command;
    }

    public void setCommand(Command command) {
        this.command = command;
    }
    @Override
    public void execute(String request){
        System.out.println("请求到达调度器,交给Command执行");
        command.execute(request);
    }
}
public class CommandPatternTest {
    public static void main(String[] args){
        //手动构造命令,注入Command
        Receiver receiver=new Receiver();
        Command command=new ConcreteCommand(receiver);
        command.execute("客户端请求");

        System.out.println("----------------");
        //通过调度器,注入Command
        Invoker invoker=new Invoker(command);
        invoker.execute("调度器的客户端请求");
    }
}
/*
请求到达命令着,但实际调用接受者Receiver的方法去执行
实际接受者,处理请求:客户端请求
----------------
请求到达调度器,交给Command执行
请求到达命令着,但实际调用接受者Receiver的方法去执行
实际接受者,处理请求:调度器的客户端请求
*/

把一条命令的执行分三步,引入invoker将命令的调度者与执行者分开,客户只需要知道具体命令Command,传入invoker然后调用方法执行即可,根本不用管命令如何实现,原理还是通过层级调用,引入层次依赖解决当前的高度耦合

命令模式的主要优点在于降低系统的耦合度,增加新的命令很方便,而且可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复;其主要缺点在于可能会导致某些系统 有过多的具体命令类。

命令模式适用情况包括:需要将请求调用者和请求接收者解耦, 使得调用者和接收者不直接交互;需要在不同的时间指定请求、 将请求排队和执行请求;需要支持命令的撤销操作和恢复操作。

解释器模式

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。 即对两个不同的表达方式进行转换,正如同现实世界中各种语言的翻译解释一样,给定规定的语言文法表示,翻译成相应的可执行的语句。除了使用文法规则来定义一个语言,在解释器模式中还可以通过一种称之为抽象语法树 (Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例,通过分析抽象语法树进行解释执行。

一些ORM框架就通过特定的方法限定语法规则,完成对sql语句的拼接,即只写java代码而不用写sql语句,这也算是解释器模式的一个实例。

模式结构

• AbstractExpression: 抽象表达式,内部定义抽象方法interpect()表示可以有多种不同的具体解释

• TerminalExpression: 终结符表达式

• NonterminalExpression: 非终结符表达式,抽象表达式的具体实现,定义具体的语法解析

• Context: 定义语法的上下文

来自:zhuanlan.zhihu.com/p/62426301 的例子:

/**
 * 定义上下文语法规则,拼接SQL时需要的 表名、字段参数、条件参数
 */
public class Context {
    private String tableName;
    private Map<String, Object> params = new HashMap<>();
    private Map<String, Object> wheres = new HashMap<>();

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public Map<String, Object> getParams() {
        return params;
    }

    public void setParams(Map<String, Object> params) {
        this.params = params;
    }

    public Map<String, Object> getWheres() {
        return wheres;
    }

    public void setWheres(Map<String, Object> wheres) {
        this.wheres = wheres;
    }
}
/**
 * 定义抽象解释器,可以有多种不同的具体实现去解释文法
 */
public abstract class SQLExpression {
    public abstract String interpret(Context context);
}
//具体的解释器去实现具体的文法解析工作
public class SelectSQLExpression extends SQLExpression{

    @Override
    public String interpret(Context context) {
        StringBuilder select = new StringBuilder();
        select.append("select * from ")
                .append(context.getTableName())
                .append(" where ");
        for (String key : context.getWheres().keySet()) {
            select.append(key)
                    .append(" = '")
                    .append(context.getWheres().get(key))
                    .append("'")
                    .append(" and ");
        }

        return select.substring(0,select.length()-4);
    }
}
public class InterpreterPatternTest {
    public static void main(String[] args) {
        Context context = new Context();
        context.setTableName("user");
        context.setParams(null);

        Map<String, Object> wheres = new HashMap<>();
        wheres.put("name", "小明");
        context.setWheres(wheres);

        SQLExpression sqlExpression = new SelectSQLExpression();
        System.out.println(sqlExpression.interpret(context));
    }
}
/*
select * from user where name = '小明' 
*/

Context定义文法信息,解释器Interpreter就是要根据文法去进行解释工作,需要解释器工作的地方面临着全局的抽象转换需求。

解释器模式的主要优点包括易于改变和扩展文法,易于实现文法并可以按需增加新的解释表达式的方式

其主要缺点是对于复杂文法难以维护,执行效率较低且应用场景很有限,实际中较少用到解释器

迭代器模式

一个聚合对象,如一个列表(List)或者一个集合(Set),应该提供一种方法来让别人可以访问它的元素,而又不需要暴露它的内部结构。针对不同的需要,可能还要以不同的方式遍历整个聚合对象, 但是我们并不希望在聚合对象的抽象层接口中充斥着各种不同遍历的操作。 遍历一个聚合对象,不需要了解聚合对象的内部结构, 还能够提供多种不同的遍历方式,这就是迭代器模式要解决的问题。

在迭代器模式中,提供一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有。

模式结构:

• Iterator: 抽象迭代器 ,定义迭代一个对象所需要的功能

• ConcreteIterator: 具体迭代器,迭代器接口的对象

• Aggregate: 抽象聚合类,内部维护数据对象的数据结构

• ConcreteAggregate: 具体聚合类

很多语言已经内置了迭代器与forEach迭代语法,更具体的,在java中,使用Iterable接口表示可迭代,内部有iterator()方法返回一个迭代器,Iterator()接口表示真正的迭代器,通过该接口进行操作,具体的集合类实现Iterable接口,返回iterator对象,自己实现自己的迭代操作,外部不需要知道集合如何遍历元素,只管使用返回来的迭代器即可,这个设计很“解耦”

/**
 * 声明迭代器,实现该接口就代表该类可以进行内部元素迭代,返回一个迭代器
 */
public interface Iterable<E> {
    Iterator<E> iterator();
}
/**
 * 迭代器声明,不需要管实现,遵循迭代规则即可
 */
public interface Iterator<E> {
    E first();
    boolean hasNext();
    E next();
    E currentItem();
}
//具体使用
public class Aggregate<E> implements Iterable{
    private List<E> list=new ArrayList<>();
    private int currentCursor=0;
    public void add(E element){
        list.add(element);
    }

    /**
     * 返回迭代器即可,外部不需要管内部实现
     */
    @Override
    public Iterator iterator(){
        return new Iterator() {
            @Override
            public Object first() {
                if(list.size()==0)throw new ArrayIndexOutOfBoundsException();
                return list.get(0);
            }

            @Override
            public boolean hasNext() {
                return currentCursor<list.size();
            }

            @Override
            public Object next() {
                if(currentCursor>=list.size())throw new ArrayIndexOutOfBoundsException();
                return list.get(currentCursor++);
            }

            @Override
            public Object currentItem() {
                return list.get(currentCursor);
            }
        };
    }
}

public class IteratorPattern {
    public static void main(String[] args){
        Aggregate<Integer> aggregate=new Aggregate<Integer>();
        aggregate.add(3);
        aggregate.add(2);
        aggregate.add(1);

        Iterator iterator=aggregate.iterator();
        while (iterator.hasNext()){
            System.out.println(iterator.currentItem());
            iterator.next();//迭代
        }
    }
}
/*
3
2
1
*/

迭代器模式的主要优点在于它支持以不同的方式遍历一个聚合对象,还简化了聚合类,而且在同一个聚合上可以有多个遍历;其缺点在于增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。 迭代器模式适用情况包括:访问一个聚合对象的内容而无须暴露它的内部表示;需要为聚合对象提供多种遍历方式;为遍历不同的聚合结构提供一个统一的接口。

中介者模式

用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式。

中介者模式可以使对象之间的关系数量急剧减少:

QYrniQ.md.png

模式结构:

(1)Mediator:抽象中介者,它定义了对象之间交流的方法。就好比国与国之间的沟通方式。

(2)ConcreteMediator:具体中介者,联合国的正式组成运营。

(3)Colleague:抽象同事类,就好比是一个抽象的国家模型。里面定义了一些功能。

(4) ConcreteColleague: 具体同事类。就好比是一个个具体的国家,英国、美国。

还是为了解耦合,如果有多个Colleague要互相通信,通知者必须内部维护被通知者,对象一多,会产生大量的耦合代码。抽象中介者Mediator内部定义对象之间的交互方式,具体中介者ConcreteMediator内部维护一个Colleage的列表,进行Colleage通信调度。Colleague抽象出通信功能,具体子类内部维护Mediator,实现功能时调用Mediator的方法进行交互,传递必要信息,交给Mediator调度,内部不再维护其它Colleague,实现解耦合。

/**
 * 抽象中介者,定义中介的功能
 */
public interface Mediator {
    //定义交互功能
    void communicate(String msg,Colleague from,String to);
}
public class ConcreteMediator implements Mediator {
    //中介者维护所有要交互的Colleague的信息,进行统一的交互管理
    public List<Colleague> colleagues=new ArrayList<>();

    //具体的交互管理
    @Override
    public void communicate(String msg, Colleague from,String to) {
        //消息传递
        for(Colleague colleague:colleagues){
            if (colleague.getName().equals(to)){
                colleague.receiveMsg(msg);
            }
        }
    }
}
/**
 * 抽象交互类Client,定义与其他Colleague交互功能
 */
public abstract class Colleague {
    protected String name;
    protected Mediator mediator;//定义中介者
    Colleague(String name,Mediator mediator){
        this.name=name;
        this.mediator=mediator;
    }
    public String getName() {
        return name;
    }

    //定义交互功能
    public abstract void sendMsg(String msg,String to);
    public abstract void receiveMsg(String msg);
}
/**
 * 具体的Colleague,内部的交互方法教给中介者mediator而不是自己内部维护Colleague对象交互,解耦合
 */
public class AColleague extends Colleague {
    AColleague(String name,Mediator mediator){
        super(name,mediator);
    }
    //调用中介者的方法进行通知交互而不是维护另一个Colleague对象,进行解耦合
    @Override
    public void sendMsg(String msg,String to) {
        mediator.communicate(msg,this,to);//交给中介者调度
    }

    @Override
    public void receiveMsg(String msg) {
        System.out.println("A获得信息:" + msg);
    }

}
public class BColleague extends Colleague {
    BColleague(String name,Mediator mediator){
        super(name,mediator);
    }
    //调用中介者的方法进行通知交互而不是维护另一个Colleague对象,进行解耦合
    @Override
    public void sendMsg(String msg,String to) {
        mediator.communicate(msg,this,to);
    }

    @Override
    public void receiveMsg(String msg) {
        System.out.println("B获得信息:" + msg);
    }
}
public class MediatorPatternTest {
    public static void main(String[] args){
        ConcreteMediator mediator=new ConcreteMediator();
        AColleague aColleague=new AColleague("a",mediator);
        BColleague bColleague=new BColleague("b",mediator);
        mediator.colleagues.add(aColleague);
        mediator.colleagues.add(bColleague);

        //使用中介者
        aColleague.sendMsg("lsl","b");
        bColleague.sendMsg("sls","a");
    }
}
/*
B获得信息:lsl
A获得信息:sls
*/

通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构,中介者承担了中转作用和协调作用。中介者类是中介者模式的核心,它对整个系统进行控制和协调,简化了对象之间的交互, 还可以对对象间的交互进行进一步的控制。

中介者模式的主要优点在于简化了对象之间的交互,将各同事解耦, 还可以减少子类生成,对于复杂的对象之间的交互,通过引入中介者, 可以简化各同事类的设计和实现;中介者模式主要缺点在于具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

中介者模式适用情况包括:系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解;一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象;想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。

备忘录模式

在应用软件的开发过程中,很多时候我们都需要记录一个对象的内部状态。 在具体实现过程中,为了允许用户取消不确定的操作或 从错误中恢复过来,需要实现备份点和撤销机制,而要实现这些机制,必须事先将状态信息保存在某处,这样才能将对象恢复到它们原先的状态。 备忘录模式是一种给我们的软件提供后悔药的机制,通过它可以使系统恢复到某一特定的历史状态。

备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。其别名为Token。

QwOEt0.md.png

模式结构:

Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复状态。Originator可根据需求决定Memento存储Originator的哪些内部状态。

Memento(备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。

Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。

//发起人,需要保存状态的对象
public class Originator {

    //需要保存的属性
    private String state;

    //创建备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
    public Memento createMemento() {
        return new Memento(state);
    }

    //恢复备忘录,将Memento导入并将相关数据恢复为传递进来的Memento
    public void setMemento(Memento memento) {
        this.state = memento.getState();
    }

    //显示数据
    public void show() {
        System.out.println("state = " + state);
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
//备忘录,保存实际状态
public class Memento {

    private String state;

    //构造方法,将相关数据导入
    public Memento(String state) {
        this.state = state;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
//管理者,内部维护备忘录对象
public class Caretaker {

    private Memento memento;

    public Memento getMemento() {
        return memento;
    }

    public void setMemento(Memento memento) {
        this.memento = memento;
    }
}
public class Client {
    public static void main(String[] args) {
        Originator originator = new Originator();
        originator.setState("On");
        originator.show();

        Caretaker caretaker = new Caretaker();
        caretaker.setMemento(originator.createMemento());//创建备忘录导出状态交给Caretaker保存

        originator.setState("Off");
        originator.show();

        originator.setMemento(caretaker.getMemento());//从Caretaker中拿出保存的状态
        originator.show();
    }
}
/*
state = On
state = Off
state = On
*/

Originator是需要进行备忘的类,内部维护需要进行备忘的对象,通过返回一个Memento导出备忘对象,备忘录Memento内部维护需备忘的对象,由Originator导出保存以前的值,Caretaker用于管理Memento,内部维护Memento列表,当Originator需要恢复到以前的状态时通过调用Caretaker获取以前Originator导出的备忘录。

备忘录模式的主要优点在于它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,还简化了原发器对象,备 忘录只保存原发器的状态,采用堆栈来存储备忘录对象可以实现多次撤消操作,可以通过在负责人中定义集合对象来存储多个备忘录;

备忘录模式的主要缺点在于资源消耗过大,因为每一个历史状态的保存 都需要一个备忘录对象。

备忘录模式适用情况包括:保存一个对象在某一个时刻的状态或部分状态,这样以后需要时它能够恢复到先前的状态;如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

观察者模式

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。 在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

即定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时, 其相关依赖对象皆得到通知并被自动更新。

模式结构:

• Subject: 目标,定义增删观察者、向观察者发送通知的方法

• ConcreteSubject: 具体目标 ,内部维护观察者列表,当状态改变时通知观察者

• Observer: 观察者 ,定义接收通知后将调用的更新方法

• ConcreteObserver: 具体观察者

QrIKKJ.md.png

/**
 * 观察者,定义被通知之后将要执行的功能
 */
public interface Observer {
    void update();
}
public class AObserver implements Observer {
    @Override
    public void update(){
        System.out.println("A已被通知,执行操作");
    }
}
public class BObserver implements Observer {
    @Override
    public void update(){
        System.out.println("B已被通知");
    }
}
/**
 * 被观察者Observable,也是订阅者,或者主题,定义增删观察者,执行通知的功能
 */
public interface Submit {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers();
}
public class ConcreteSubject implements Submit{
    private List<Observer> observerList=new ArrayList<>();

    @Override
    public void attach(Observer observer){
        observerList.add(observer);
    }

    @Override
    public void detach(Observer observer){
        observerList.remove(observer);
    }

    @Override
    public void notifyObservers(){
        for(Observer observer:observerList){
            observer.update();//调用观察者,执行观察者方法
        }
    }

    public static void main(String[] args){
        Submit submit=new ConcreteSubject();
        submit.attach(new AObserver());
        submit.attach(new BObserver());

        submit.notifyObservers();
    }
}
/*
A已被通知,执行操作
B已被通知
*/

观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变, 所有的观察者都将得到通知。 作为对这个通知的响应,每个观察者都将即时更新自己的状态, 以与目标状态同步,这种交互也称为发布-订阅(publishsubscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通知。

观察者模式的主要优点在于可以实现表示层和数据逻辑层的分离, 在观察目标和观察者之间建立一个抽象的耦合,支持广播通信;其主要缺点在于如果一个观察目标对象有很多直接和间接的观察者的话, 将所有的观察者都通知到会花费很多时间,而且如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用, 可能导致系统崩溃。

观察者模式适用情况包括:一个抽象模型有两个方面,其中一个方面依赖于另一个方面;一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变;一个对象必须通知其他对象,而并不知道这些对象是谁;需要在系统中创建一个触发链。 在JDK的java.util包中,提供了Observable类以及Observer接口,它们构成了Java语言对观察者模式的支持。

状态模式

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

模式结构 :

• Context: 环境类,内部维护抽象状态State,根据条件设置具体的State,不同的状态下可以执行不同的操作

• State: 抽象状态类 ,定义所有状态下可能执行的不同操作

• ConcreteState: 具体状态类 ,定义某一具体状态下允许执行的操作

状态模式描述了对象状态的变化以及对象如何在每一 种状态下表现出不同的行为。状态模式的关键是引入了一个抽象类来专门表示对象的状态,这个类我们叫做抽象状态类,而对象的每一种具体状态类都继承了该类,并在不同具体状态类中实现了不同状态的行为,包括各种状态之间的转换。

状态模式通常用于描述现实状态下个体不同状态时所能执行的操作(如打游戏时,用户每升一级就是一个不同的状态)

QrbsgK.md.png

不使用状态模式时,每一次进行状态判断都需要判断多个条件,状态模式封装了具体状态下的可执行操作,按照条件设置状态即可。使用时内部维护抽象状态State,根据条件设置具体的State,不同的状态下可以执行不同的操作

/**
 * 抽象状态对象
 */
public abstract class LevelState {
    public void A(){System.out.println("技能A不可释放");}
    public void B(){System.out.println("技能B不可释放");}
}
public class OneLevelState extends LevelState {
    @Override
    public void A(){
        System.out.println("技能A可以释放");
    }
}
public class TwoLevelState extends LevelState {
    @Override
    public void A(){
        System.out.println("技能A可以释放");
    }
    @Override
    public void B(){
        System.out.println("技能B可以释放");
    }
}
public class Player {
    private LevelState state;

    public LevelState getState() {
        return state;
    }

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

    public static void main(String[] args){
        Player player=new Player();
        //(根据条件)设置状态
        player.setState(new OneLevelState());
        player.getState().A();
        player.getState().B();

        player.setState(new TwoLevelState());
        player.getState().A();
        player.getState().B();
    }
}
/*
技能A可以释放
技能B不可释放
技能A可以释放
技能B可以释放
*/

状态模式的主要优点在于封装了转换规则,并枚举可能的状态,它将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为,还可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数;

其缺点在于使用状态模式会增加系统类和对象的个数,且状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,对于可以切 换状态的状态模式不满足“开闭原则”的要求。

状态模式适用情况包括:对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为;代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。

策略模式

在软件系统中,有许多算法可以实现某一功能,如查找、排序等, 一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。

为了解决这些问题,可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。

即定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。

模式结构:

• Context: 环境类,要调用策略执行功能的类

• Strategy: 抽象策略类,定义策略要实现的功能

• ConcreteStrategy: 具体策略类 ,功能的具体实现

Q6KGY4.md.png

//策略类,定义抽象算法功能
public abstract class Strategy {
    public abstract void algorithm();
}
public class AStrategy extends Strategy {
    @Override
    public void algorithm(){
        System.out.println("执行A算法");
    }
}
public class BStratery extends Strategy{
    @Override
    public void algorithm(){
        System.out.println("执行B算法");
    }
}
public class Context {
    private Strategy strategy;
    public Context(Strategy strategy){
        this.strategy=strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void algorithm(){
        strategy.algorithm();
    }

    public static void main(String[] args){
        Context context=new Context(new AStrategy());
        context.algorithm();
        context.setStrategy(new BStratery());
        context.algorithm();
    }
}
/*
执行A算法
执行B算法
*/

策略模式的思想就是将原来庞大的算法集合按类别分开来,抽象出公共的算法问题定义Strategy,交给子类进行不同的实现,同时在来一个类Context内部维护一个Strategy,客户端根据需要注入不同的具体Strategy,实现一定程度上的解耦。

策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法, 它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,同时在一定 程度上增加了系统中类的个数,可能会存在很多策略类。

策略模式适用情况包括:在一个系统里面有许多类,它们之间的区别仅在于它们的行为,使用策略模式可以动态地让一个对象在许多行为中选择一种行为;一个系统需要动态地在几种算法中选择一种;避免使用难以维护的多重条件选择语句;希望在具体策略类中封装算法和与相关的数据结构。

模板方法模式

模板方法模式是一种类的行为型模式,在它的结构图中只有类之间的继承关系,没有对象关联关系。

在模板方法模式的使用过程中,要求开发抽象类和开发具体子类的设计师之间进行协作。一个设计师负责给出一个 算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。实现这些具体逻辑步骤的方法称为基本方 法(Primitive Method),而将这些基本法方法汇总起来的方法称为模板方法(Template Method),模板方法模式的名字从此而来。 模板方法模式 :一个模板方法是定义在抽象类中的、把基本操作方法组合在一起形成一个总算法或一个总行为的方法。

基本方法:基本方法是实现算法各个步骤的方法,是模板方法的组成部分。

• 抽象方法(Abstract Method)

• 具体方法(Concrete Method)

• 钩子方法(Hook Method):“挂钩”方法和空方法 ,不是抽象方法但没有实现,子类按需实现

QfHWd0.md.png

模板方法模式将一整套业务流程抽象出来,任何用户使用时都需要根据该流程进行,因此抽象类定义整个业务流程所需要的方法,提供模板方法一次调用所需要的方法完成业务流程,不同的子类有自己的业务条件实现,关键在于模板方法的实现,该方法定义了基本方法执行的顺序。

/**
 * 抽象类,定义业务流程方法及执行逻辑
 */
public abstract class DBOperator {
    public abstract void connDB(String address);
    public void openDB(String address){
        System.out.println("打开"+address);
    }
    public void useDB(String sql){
        System.out.println("执行SQL"+sql);
    }
    public void closeDB(String address){
        System.out.println("关闭"+address);
    }
    public void process(String address,String sql){
        connDB(address);
        openDB(address);
        useDB(sql);
        closeDB(address);
    }

}
/**
 * 不同的业务流程实现子类
 */
public class SQLServerDB extends DBOperator{
    public void connDB(String address){
        System.out.println("连接SQLServer"+address);
    }
}
public class OracleDB extends DBOperator {
    public void connDB(String address){
        System.out.println("连接Oracle"+address);
    }

    public static void main(String[] args){
        DBOperator dbOperator=new SQLServerDB();
        dbOperator.process("127.0.0.1:8080","test1");

        dbOperator=new OracleDB();
        dbOperator.process("127.0.0.1:8080","test2");
    }
}

模板方法模式的优点在于在子类定义详细的处理算法时不会改变算法的结构,实现了代码的复用,通过对子类的扩展可以增加新的行为,符合“开闭原则”;其缺点在于需要为每个不同的实现都定义一个子类,这会导致类的个数 增加,系统更加庞大,设计也更加抽象

模板方法模式适用情况包括:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现;各子类中公共 的行为应被提取出来并集中到一个公共父类中以避免代码重复;对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法,而一些可以改变的细节由其子类来实现;通过模板方法模式还可以控制子类的扩展。

访问者模式

对于系统中的某些对象,它们存储在同一个集合中,且具有不同的类型,而且对于该集合中的对象,可以接受一类称为访问者的对象来访问,而且不同的访问者其访问方式有所不同,访问者模式为解决这类问题而诞生。

即表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

Qfq4v4.md.png

问题的关键在于可以以不同的方式访问元素集合内不同的元素,由客户端决定以什么方式访问什么元素

模式结构:

• Vistor: 抽象访问者,定义要以何种方式访问何种元素

• ConcreteVisitor: 具体访问者

• Element: 抽象元素,某个集合内要存放的元素

• ConcreteElement: 具体元素

• ObjectStructure: 对象结构,存放抽象元素的集合

/**
* 抽象元素类型,定义访问者访问方法
*/
public interface Element {
   void accept(Visitor visitor);
}
/**
* 具体元素实现,访问元素时根据Visitor调用访问
*/
public class ConcentrateElementA implements Element {
   @Override
   public void accept(Visitor visitor){
       visitor.visitConcentrateElementA(this);
   }
}
public class ConcentrateElementB implements Element {
   @Override
   public void accept(Visitor visitor){
       visitor.visitConcentrateElementB(this);
   }
}
/**
* 具体访问者提供对元素的具体访问方法
*/
public class ConcentrateVisitorA implements Visitor {
   @Override
   public void visitConcentrateElementA(ConcentrateElementA concentrateElementA){
       System.out.println("第一种方式访问元素A");
   }
   @Override
   public void visitConcentrateElementB(ConcentrateElementB concentrateElementB){
       System.out.println("第一种方式访问元素B");
   }
}
public class ConcentrateVisitorB implements Visitor{
   @Override
   public void visitConcentrateElementA(ConcentrateElementA concentrateElementA){
       System.out.println("第二种方式访问元素A");
   }
   @Override
   public void visitConcentrateElementB(ConcentrateElementB concentrateElementB){
       System.out.println("第二种方式访问元素B");
   }
}
public class Test {
   public static void main(String[] args){
       List<Element> list=new ArrayList<>();//抽象元素集合
       list.add(new ConcentrateElementA());
       list.add(new ConcentrateElementB());
   	//由客户端决定以什么方式访问什么元素
       Visitor visitor=new ConcentrateVisitorA();
       for(Element element:list){
           element.accept(visitor);
       }
       visitor=new ConcentrateVisitorB();
       for(Element element:list){
           element.accept(visitor);
       }
  }
}
/*
第一种方式访问元素A
第一种方式访问元素B
第二种方式访问元素A
第二种方式访问元素B
*/

访问者模式的主要优点在于使得增加新的访问操作变得很容易,将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中,还可以跨过类的等级结构访问属于不同的等级结构的元素类,让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的操作;其主要缺点在于增加新的元素类很困难,而且在一定程度上破坏系统的封装性。

访问者模式适用情况包括:一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作;需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作 “污染”这些对象的类,也不希望在增加新操作时修改这些类;对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。