学习笔记-Java设计模式-行为型模式2

Java设计原则&&模式学习笔记

说明

近期扫地生决定整合一下年初学习的设计模式,一来用于复习巩固,二来也希望可以把自己的整合与有需要的同学共勉。

扫地生在学习的过程中主要参考的是公众号“一角钱技术”的相关推文,同时也参考了下列几篇文章。对这些作者扫地生表示衷心的感谢。

参考文章1-Java设计原则

参考文章2-Java设计模式总结

参考文章3-23种设计模式速记

5、行为型模式

5.4 行为型模式4——状态模式(State)

速记关键词:状态变成类

简介

定义:允许一个对象在其对象内部状态改变时改变它的行为

在很多情况下我们对象的行为依赖于它的一个或者多个变化的属性,这些可变的属性我们称之为状态,也就是说行为依赖状态,即当该对象因为在外部的互动而导致他的状态发生变化,从而它的行为也会做出相应的变化。对于这种情况,我们是不能用行为来控制状态的变化,而应该站在状态的角度来思考行为,即是什么状态就要做出什么样的行为。这个就是状态模式。

所以状态模式就是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

在状态模式中可以减少大块的if…else语句,它是允许态转换逻辑与状态对象合成一体,但是减少if…else语句的代价就是会换来大量的类,所以状态模式势必会增加系统中类或者对象的个数。

同时状态模式是将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。但是这样就会导致系统的结构和实现都会比较复杂,如果使用不当就会导致程序的结构和代码混乱,不利于维护。

image-20211108211248646

模式实现

package top.saodisheng.designpattern.state.v1;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class StatePattern {

    public static void main(String[] args) {
        //创建环境
        Context context = new Context();
        //处理请求
        context.Handle();
        context.Handle();
        context.Handle();
        context.Handle();
    }
}

/**
 * 抽象状态类
 */
abstract class State {
    public abstract void Handle(Context context);
}

/**
 * 具体状态A类
 */
class ConcreteStateA extends State {
    @Override
    public void Handle(Context context) {
        System.out.println("当前状态是 A.");
        context.setState(new ConcreteStateB());
    }
}

/**
 * 具体状态B类
 */
class ConcreteStateB extends State {
    @Override
    public void Handle(Context context) {
        System.out.println("当前状态是 B.");
        context.setState(new ConcreteStateA());
    }
}

/**
 * 环境类
 */
class Context {
    private State state;

    /**
     * 定义环境类的初始状态
     */
    public Context() {
        this.state = new ConcreteStateA();
    }

    /**
     * 设置新状态
     * @param state
     */
    public void setState(State state) {
        this.state = state;
    }

    /**
     * 读取状态
     * @return
     */
    public State getState() {
        return (state);
    }

    /**
     * 对请求做处理
     */
    public void Handle() {
        state.Handle(this);
    }
}

image-20211108212702801

解决的问题

对象的行为依赖与它的状态(属性),并且可以根据它的状态改变而改变的相关行为。

模式组成

组成(角色) 作用
环境 (Context)角色 也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
抽象状态 (State)角色 定义一个接口,用以封装环境对象中的特定状态所对应的行为。
具体状态 (Concrete State)角色 实现抽象状态所对应的行为。

实例说明

用“状态模式”设计一个多线程的状态转换程序。

分析:多线程存在5种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如下:

image-20211108213709444

现在先定义一个抽象类(ThreadState),然后为上图的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable)、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转换状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相互触发的方法,下图是线程转换程序的结构图:

图片

使用步骤

package top.saodisheng.designpattern.state.v2;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class StatePattern {
    public static void main(String[] args) {
        ThreadContext threadContext = new ThreadContext();
        threadContext.start();
        threadContext.getCPU();
        threadContext.suspend();
        threadContext.resume();
        threadContext.getCPU();
        threadContext.stop();
    }
}

/**
 * 1 定义抽象状态类:线程状态
 */
abstract class ThreadState {
    /** 状态名 **/
    protected String stateName;
}

/**
 * 2 定义具体的状态类
 */
class New extends ThreadState {
    public New() {
        stateName = "新建状态";
        System.out.println("当前线程处于:新建状态!!!");
    }

    public void start(ThreadContext hj) {
        System.out.print("调用start()方法 -->");
        if ("新建状态".equals(stateName)) {
            hj.setState(new Runnable());
        } else {
            System.out.println("当前线程不是新建状态,不能调用start()方法!!!");
        }
    }
}

class Runnable extends ThreadState {
    public Runnable() {
        stateName = "就绪状态";
        System.out.println("当前线程处于:就绪状态.");
    }

    public void getCPU(ThreadContext hj) {
        System.out.print("获得CPU时间-->");
        if (stateName.equals("就绪状态")) {
            hj.setState(new Running());
        } else {
            System.out.println("当前线程不是就绪状态,不能获取CPU.");
        }
    }
}

class Running extends ThreadState {
    public Running() {
        stateName = "运行状态";
        System.out.println("当前线程处于:运行状态.");
    }

    public void suspend(ThreadContext hj) {
        System.out.print("调用suspend()方法 --> ");
        if ("运行状态".equals(stateName)) {
            hj.setState(new Blocked());
        } else {
            System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
        }
    }

    public void stop(ThreadContext hj) {
        System.out.print("调用stop()方法 -->");
        if ("运行状态".equals(stateName)) {
            hj.setState(new Dead());
        } else {
            System.out.println("当前线程不是运行状态,不能调用stop()方法.");
        }
    }
}

class Blocked extends ThreadState {
    public Blocked() {
        stateName = "阻塞状态";
        System.out.println("当前线程处于:阻塞状态.");
    }

    public void resume(ThreadContext hj) {
        System.out.print("调用resume()方法-->");
        if ("阻塞状态".equals(stateName)) {
            hj.setState(new Runnable());
        } else {
            System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
        }
    }
}

class Dead extends ThreadState {
    public Dead() {
        stateName = "死亡状态";
        System.out.println("当前线程处于:死亡状态.");
    }
}

/**
 * 3 定义环境类
 */
class ThreadContext {
    private ThreadState state;

    ThreadContext() {
        state = new New();
    }

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

    public ThreadState getState() {
        return state;
    }

    public void start() {
        ((New) state).start(this);
    }

    public void getCPU() {
        ((Runnable) state).getCPU(this);
    }

    public void suspend() {
        ((Running) state).suspend(this);
    }

    public void stop() {
        ((Running) state).stop(this);
    }

    public void resume() {
        ((Blocked) state).resume(this);
    }
}

image-20211108220029076

优缺点

优点:

  • 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足一定的“单一职责原则”。
  • 减少对象间的相互依赖。将不同的状态引入独立的对象中使得状态转换变得更加明确,且减少对象间的相互依赖。
  • 有利于程序的扩展。通过定义新的子类很容易增加新的状态和转换。

缺点:

  • 状态模式的使用必然会增加系统的类与对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。

应用场景

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,可以采用该模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定对象的状态时。

状态模式的扩展

在有些情况下,可能有多个环境需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,其结构如下:

图片

分析:共享模式的不同之处是在环境类中添加了一个HashMap来保存相关状态,当需要某种状态时可以从中获取,其程序代码如下:

package top.saodisheng.designpattern.state.v3;

import java.util.HashMap;

/**
 * description:
 * 共享状态模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class FlyweightStatePattern {
    public static void main(String[] args) {
        // 创建环境类
        ShareContext context = new ShareContext();
        // 处理请求
        context.handle();
        context.handle();
        context.handle();
        context.handle();
    }
}

/**
 * 1 抽象环境类
 */
abstract class ShareState {
    abstract void handle(ShareContext context);
}

/**
 * 2 创建具体的状态类
 */
class ConcreteState1 extends ShareState {

    @Override
    void handle(ShareContext context) {
        System.out.println("当前状态是:状态1");
        context.setState(context.getState("2"));
    }
}
class ConcreteState2 extends ShareState {

    @Override
    void handle(ShareContext context) {
        System.out.println("当前状态是:状态2");
        context.setState(context.getState("1"));
    }
}

/**
 * 3 创建环境类
 */
class ShareContext {
    private ShareState state;
    private HashMap<String, ShareState> stateSet = new HashMap<>();

    public ShareContext() {
        stateSet.put("1", new ConcreteState1());
        stateSet.put("2", new ConcreteState2());
        state = getState("1");
    }

    public ShareState getState(String key) {
        return stateSet.get(key);
    }

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

    /**
     * 对请求做处理
     */
    public void handle() {
        state.handle(this);
    }
}

image-20211108221748975

源码中的应用

#JDK中的状态模式:
java.util.Iterator
# 通过FacesServlet控制, 行为取决于当前JSF生命周期的阶段(状态
javax.faces.lifecycle.LifeCycle#execute()

5.5 行为型模式5——观察者模式 (Observer)

速记关键词:联动

简介

定义:定义了对象之间的一对多依赖,让多个观察者对象同时监听某一个主题对象,当主题对象发生变化时,它的所有依赖者都会收到通知并进行更新。

在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。所以观察者提供了一种对象设计,让主题和观察者之间以松耦合的方式结合。

img

观察者模式是对象之间的一种模式,被依赖的对象是Subject,依赖的对象是Observer,Subject通知Observer变化,Subject为1,Observer为多

解决的问题

一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度协作。

模式组成

组成(角色) 作用
抽象主题角色(Subject) 把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者角色。一般用一个抽象类和接口来实现。
抽象观察者角色(Observer) 为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
具体主题角色(Subject1) 在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
具体观察者角色(Observer1) 该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。

实例说明

某天下午班主任通知某班学生和老师将要听一节课,以此来对老师的授课质量进行评分。

  • 老师和学生收到后开始安排相关的课程;

  • 上课期间老师和班主任通过观察学生的神情来预判课程的讲的好坏

    • 老师观察到学生皱眉头可以适当调节课程气氛
    • 班主任观察到学生课堂氛围好转,给与高分
  • 课程结束后,班主任和老师回到办公室,一起回顾这节开心的课程。

使用步骤

package top.saodisheng.designpattern.observer;

import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * description:
 * 观察者模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class ObserverPattern {
    public static void main(String[] args) {
        // 创建学生主体
        StudentSubject studentSubject = new StudentSubject();

        // 创建观察者老师
        TeacherObserver teacherObversable = new TeacherObserver("王老师");

        // 创建观察者班主任
        HeadTeacherObserver headTeacherObserver = new HeadTeacherObserver("班主任");

        // 学生反映上课状态
        studentSubject.setState("讲的不错,很好!");
        studentSubject.addObservable(teacherObversable);
        studentSubject.addObservable(headTeacherObserver);

        // 开始上课
        studentSubject.doNotify();

        // 上课结束
        studentSubject.removeObservable(headTeacherObserver);
        studentSubject.removeObservable(teacherObversable);
    }
}

/**
 * 1 构建一个课程实体类
 */

class Course {

    /** 上课时间:time **/
    private Date time;
    /** 上课地点:place **/
    private String place;
    /** 上课内容:content **/
    private String content;

    public Course() {
    }

    public Course(Date time, String place, String content) {
        this.time = time;
        this.place = place;
        this.content = content;
    }

    public Date getTime() {
        return time;
    }

    public void setTime(Date time) {
        this.time = time;
    }

    public String getPlace() {
        return place;
    }

    public void setPlace(String place) {
        this.place = place;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

/**
 * 2 构建一个发现者的抽象类以及相关的实现类,老师和班主任都分别继承了该接口并重写相关方法
 */
abstract class Observer {
    abstract void update(Object args);

    public Observer(String identity) {
        this.identity = identity;
    }

    private String identity;

    public String getIdentity() {
        return identity;
    }

    public void setIdentity(String identity) {
        this.identity = identity;
    }
}

/**
 * 3 创建一个具体的观察者角色,老师拿着教材开始来上课
 */
class TeacherObserver extends Observer {

    private Course course;

    @Override
    public void update(Object args) {
        DateFormat df = DateFormat.getTimeInstance(DateFormat.LONG, Locale.CHINA);
        System.out.println("我是王老师,正在讲课中...");

        course = new Course(new Date(), "A栋教学楼", "高等数学");
        System.out.println("今天上课时间:" + df.format(course.getTime()) + " 地点:" + course.getPlace() + " 上课内容:" + course.getContent());
    }

    public TeacherObserver(String identity) {
        super(identity);
    }
}

/**
 * 4 创建一个具体的观察者角色,班主任来听课
 */
class HeadTeacherObserver extends Observer {
    @Override
    public void update(Object args) {
        System.out.println("我是班主任来听课了,正在检查课程质量...");
        System.out.println("学生反馈课程质量为:" + args);
    }

    public HeadTeacherObserver(String identity) {
        super(identity);
    }
}

/**
 * 5 创建被观察者抽象主题角色
 */
abstract class Subject {
    /**
     * 修改通知
     */
    abstract void doNotify();

    /**
     * 添加被观察者
     */
    abstract void addObservable(Observer o);

    /**
     * 移除被观察者
     */
    abstract void removeObservable(Observer o);
}

/**
 * 6 创建具体的观察者主体角色,学生主体为被观察对象
 */
class StudentSubject extends Subject {
    /**
     * 上课状态
     */
    private String state;

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
    private List<Observer> observableList = new ArrayList<>();

    @Override
    public void doNotify() {
        for (Observer observer : observableList) {
            observer.update(state);
        }
    }

    @Override
    public void addObservable(Observer observable) {
        observableList.add(observable);
    }

    @Override
    public void removeObservable(Observer observable) {
        try {
            if (observable == null) {
                throw new Exception("要移除的被观察者不能为空");
            } else {
                if (observableList.contains(observable) ) {
                    System.out.println("下课了,"+observable.getIdentity()+" 已回到办公室");
                    observableList.remove(observable);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

image-20211108231858282

优缺点

优点:

  • 符合开闭原则
  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系
  • 目标与观察者之间建立了一套触发机制

缺点:

  • 目标与观察者之间的依赖关系没有完全解除,而且可能出现循环引用
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率

应用场景

当更改一个对象的状态可能需要更改其他对象,并且实际的对象集事先未知或动态更改时,使用该模式。

源码中的应用

#JDK:
java.util.Observable

#Spring:
org.springframework.context.ApplicationListener

5.6 行为型模式6——备忘录模式(Memento)

速记关键词:游戏存档

简介

定义:在不破坏封装的前提下,保持对象内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它实现了对信息的封装,使得客户不需要关心状态保存的细节。保存就要消耗资源,所以备忘录模式的缺点就在于消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

img

模板的实现

package top.saodisheng.designpattern.memento.v1;

/**
 * description:
 * 备忘录模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-06
 */
public class MementoPattern {
    public static void main(String[] args) {
        Originator or = new Originator();
        Caretaker cr = new Caretaker();
        or.setState("S0");
        System.out.println("初始状态:" + or.getState());
        //保存状态
        cr.setMemento(or.createMemento());
        or.setState("S1");
        System.out.println("新的状态:" + or.getState());
        //恢复状态
        or.restoreMemento(cr.getMemento());
        System.out.println("恢复状态:" + or.getState());
    }
}

/**
 * 备忘录
 */
class Memento {
    private String state;

    public Memento(String state) {
        this.state = state;
    }

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

    public String getState() {
        return state;
    }
}

/**
 * 发起人
 */
class Originator {
    private String state;

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

    public String getState() {
        return state;
    }

    public Memento createMemento() {
        return new Memento(state);
    }

    public void restoreMemento(Memento m) {
        this.setState(m.getState());
    }
}

/**
 * 管理者
 */
class Caretaker {
    private Memento memento;

    public void setMemento(Memento m) {
        memento = m;
    }

    public Memento getMemento() {
        return memento;
    }
}

image-20211109095602900

解决的问题

备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。

模式组成

备忘录模式的核心是设计备忘录以及用于管理备忘录的管理者类。

组成(角色) 作用
发起人(Originator)角色 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
备忘录(Memento)角色 负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
管理者(Caretaker)角色 对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

实例说明

以游戏存档为例,看一下如何用备忘录模式实现

图片

使用步骤

package top.saodisheng.designpattern.memento.v2;

/**
 * description:
 * 备忘录模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-06
 */
public class MementoPattern {
    public static void main(String[] args) {
        // 逻辑大概为打boss前存档,打boss后失败了恢复
        // 打boss前
        GameRole gameRole = new GameRole();
        gameRole.getInitState();
        gameRole.stateDisplay();

        //保存进度
        RoleStateCaretaker caretaker = new RoleStateCaretaker();
        caretaker.setMemento(gameRole.saveState());

        //打boss失败
        gameRole.fight();
        gameRole.stateDisplay();

        //恢复状态
        gameRole.recoveryState(caretaker.getMemento());
        gameRole.stateDisplay();
    }
}

/**
 * 1 定义备忘录角色,用于存储角色状态
 */
class RoleStateMemento {
    /** 生命力 **/
    private int vit;
    /** 攻击力 **/
    private int atk;
    /** 防御力 **/
    private int def;

    public RoleStateMemento(int vit, int atk, int def) {
        this.vit = vit;
        this.atk = atk;
        this.def = def;
    }

    public int getVit() {
        return vit;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

    public void setDef(int def) {
        this.def = def;
    }
}

/**
 * 2 定义发起人角色(当前游戏角色),记录当前游戏角色的生命力、攻击力、防御力。
 * 通过saveState()方法来保存当前状态,通过recoveryState()方法来恢复角色状态
 */
class GameRole {
    /** 生命力 **/
    private int vit;
    /** 攻击力 **/
    private int atk;
    /** 防御力 **/
    private int def;

    public int getVit() {
        return vit;
    }

    public void setVit(int vit) {
        this.vit = vit;
    }

    public int getAtk() {
        return atk;
    }

    public void setAtk(int atk) {
        this.atk = atk;
    }

    public int getDef() {
        return def;
    }

    public void setDef(int def) {
        this.def = def;
    }

    /**
     * 状态显示
     */
    public void stateDisplay() {
        System.out.println("角色当前状态:");
        System.out.println("体力:" + this.vit);
        System.out.println("攻击力:" + this.atk);
        System.out.println("防御力:" + this.def);
        System.out.println("--------------------------");
    }

    /**
     * 获得初始状态
     */
    public void getInitState() {
        this.vit = 100;
        this.atk = 100;
        this.def = 100;
    }

    /**
     * 战斗后
     */
    public void fight() {
        this.vit = 0;
        this.atk = 0;
        this.def = 0;
    }

    /**
     * 保存角色状态
     * @return
     */
    public RoleStateMemento saveState() {
        return (new RoleStateMemento(vit, atk, def));
    }

    /**
     * 恢复角色状态
     * @param memento
     */
    public void recoveryState(RoleStateMemento memento) {
        this.vit = memento.getVit();
        this.atk = memento.getAtk();
        this.def = memento.getDef();
    }
}

/**
 * 3 定义管理者角色,角色状态管理者
 */
class RoleStateCaretaker {
    private RoleStateMemento memento;

    public RoleStateMemento getMemento() {
        return memento;
    }

    public void setMemento(RoleStateMemento memento) {
        this.memento = memento;
    }
}

image-20211109102011114

优缺点

优点:

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史状态。
  • 实现内部状态的封装。除了创建它的发起人之外,其他对象都不能访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

缺点:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

注意事项

  • 为了符合迪米特法则,需要有一个备忘录的类
  • 不要在频繁建立备份的场景中使用备忘录模式。为了节约内存,可使用原型模式+备忘录模式

应用场景

  • 需要保存和恢复数据的相关场景
  • 提供一个可回滚的操作,如ctrl + z、浏览器回退键、Backspace键等
  • 需要监控的副本场景

模式的扩展

在备忘录模式中,有单状态的例子,也有多状态的例子。可以结合原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的clone()方法具有自备份功能,所以,如果让发起人实现Cloneable接口就有备份自己的功能,这时可以删除备忘录类,其结构如下:

图片

源码中的应用

#Spring
org.springframework.binding.message.StateManageableMessageContext

5.7 行为型模式7——中介者模式(Mediator)

速记关键词:不直接引用

简介

定义:用一个中介对象来封装一系列的对象交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

中介者模式就是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。在中介者模式中,中介对象用来封装对象之间的关系,各个对象可以不需要知道具体的信息通过中介者对象就可以实现相互通信。它减少了对象之间的互相关系,提供了系统可复用性,简化了系统的结构。

在中介者模式中,各个对象不需要互相知道了解,他们只需要知道中介者对象即可,但是中介者对象就必须要知道所有的对象和他们之间的关联关系,正是因为这样就导致了中介者对象的结构过于复杂,承担了过多的职责,同时它也是整个系统的核心所在,它有问题将会导致整个系统的问题。所以如果在系统的设计过程中如果出现“多对多”的复杂关系群时,千万别急着使用中介者模式,而是要仔细思考是不是您设计的系统存在问题。

img

模板实现

package top.saodisheng.designpattern.mediator.v1;

import java.util.ArrayList;
import java.util.List;

/**
 * description:
 * 中介者模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-06
 */
public class MediatorPattern {

    public static void main(String[] args) {
        Mediator md = new ConcreteMediator();
        Colleague c1, c2;
        c1 = new ConcreteColleague1();
        c2 = new ConcreteColleague2();
        md.register(c1);
        md.register(c2);
        c1.send();
        System.out.println("==============");
        c2.send();
    }
}

/**
 * 1. 抽象中介者
 */
abstract class Mediator {
    public abstract void register(Colleague colleague);

    /**
     * 转发
     * @param cl
     */
    public abstract void relay(Colleague cl);
}


/**
 * 2. 抽象同事类
 */
abstract class Colleague {
    protected Mediator mediator;

    public void setMedium(Mediator mediator) {
        this.mediator = mediator;
    }

    public abstract void receive();

    public abstract void send();
}

/**
 * 3. 具体中介者
 */
class ConcreteMediator extends Mediator {
    private List<Colleague> colleagues = new ArrayList<Colleague>();

    @Override
    public void register(Colleague colleague) {
        if (!colleagues.contains(colleague)) {
            colleagues.add(colleague);
            colleague.setMedium(this);
        }
    }

    @Override
    public void relay(Colleague cl) {
        for (Colleague ob : colleagues) {
            if (!ob.equals(cl)) {
                ((Colleague) ob).receive();
            }
        }
    }
}

/**
 * 4. 具体同事类
 */
class ConcreteColleague1 extends Colleague {
    @Override
    public void receive() {
        System.out.println("具体同事类1收到请求。");
    }

    @Override
    public void send() {
        System.out.println("具体同事类1发出请求。");
        // 请中介者转发
        mediator.relay(this); 
    }
}

class ConcreteColleague2 extends Colleague {
    @Override
    public void receive() {
        System.out.println("具体同事类2收到请求。");
    }

    @Override
    public void send() {
        System.out.println("具体同事类2发出请求。");
        // 请中介者转发
        mediator.relay(this); 
    }
}

image-20211109121443825

解决的问题

对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

模式组成

组成(角色) 作用
抽象中介者(Mediator)角色 它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(ConcreteMediator)角色 实现中介者接口,定义一个List来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色 定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(ConcreteColleague)角色 是抽象同事类的实现者,当需要与其它同事对象交互时,由中介者负责后续的交互。

实例说明

用中介者模式编写一个“房地产交流平台”程序。

分析:房地产交流平台是“房地产中介公司”提供给“卖方客户”与“买方客户”进行信息交流的平台,比较适合用中介者模式来实现。

图片

使用步骤

package top.saodisheng.designpattern.mediator.v2;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;

/**
 * description:
 * 中介者模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-06
 */
public class MediatorPattern {
    public static void main(String[] args) {
        Medium md = new EstateMedium();
        Customer c1, c2;
        c1 = new Seller("卖家");
        c2 = new Buyer("买家");
        md.register(c1);
        md.register(c2);
    }
}

/**
 * 1 定义一个中介公司(Medium)接口,它是抽象中介者,它包含了客户注册方法(register(Customer member)和信息转发方法relay(String from, String ad)
 */
interface Medium {
    /**
     * 客户注册
     */
    void register(Customer member);

    /**
     * 转发
     * @param from
     * @param ad
     */
    void relay(String from, String ad);
}

/**
 * 2:定义一个房地产中介(EstateMedium)公司,它是具体中介者类,它包含了保存客户信息的 List 对象,并实现了中介公司中的抽象方法。
 */
class EstateMedium implements Medium {
    private List<Customer> members = new ArrayList<Customer>();

    @Override
    public void register(Customer member) {
        if (!members.contains(member)) {
            members.add(member);
            member.setMedium(this);
        }
    }

    @Override
    public void relay(String from, String ad) {
        for (Customer ob : members) {
            String name = ob.getName();
            if (!name.equals(from)) {
                ((Customer) ob).receive(from, ad);
            }
        }
    }
}


/**
 * 3:定义一个客户(Qistomer)类,它是抽象同事类,其中包含了中介者的对象,
 * 和发送信息的 send(String ad) 方法与接收信息的 receive(String from,Stringad) 方法的接口,
 * 由于本程序是窗体程序,所以本类继承 JPmme 类,并实现动作事件的处理方法 actionPerformed(ActionEvent e)。
 */
abstract class Customer extends JFrame implements ActionListener {
    private static final long serialVersionUID = -7219939540794786080L;
    protected Medium medium;
    protected String name;
    JTextField SentText;
    JTextArea ReceiveArea;

    public Customer(String name) {
        super(name);
        this.name = name;
    }

    void ClientWindow(int x, int y) {
        Container cp;
        JScrollPane sp;
        JPanel p1, p2;
        cp = this.getContentPane();
        SentText = new JTextField(18);
        ReceiveArea = new JTextArea(10, 18);
        ReceiveArea.setEditable(false);
        p1 = new JPanel();
        p1.setBorder(BorderFactory.createTitledBorder("接收内容:"));
        p1.add(ReceiveArea);
        sp = new JScrollPane(p1);
        cp.add(sp, BorderLayout.NORTH);
        p2 = new JPanel();
        p2.setBorder(BorderFactory.createTitledBorder("发送内容:"));
        p2.add(SentText);
        cp.add(p2, BorderLayout.SOUTH);
        SentText.addActionListener(this);
        this.setLocation(x, y);
        this.setSize(250, 330);
        //窗口大小不可调整
        this.setResizable(false);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String tempInfo = SentText.getText().trim();
        SentText.setText("");
        this.send(tempInfo);
    }

    @Override
    public String getName() {
        return name;
    }

    public void setMedium(Medium medium) {
        this.medium = medium;
    }

    public abstract void send(String ad);

    public abstract void receive(String from, String ad);
}


class Seller extends Customer {
    private static final long serialVersionUID = -1443076716629516027L;

    public Seller(String name) {
        super(name);
        ClientWindow(50, 100);
    }

    @Override
    public void send(String ad) {
        ReceiveArea.append("我(卖方)说: " + ad + "\n");
        //使滚动条滚动到最底端
        ReceiveArea.setCaretPosition(ReceiveArea.getText().length());
        medium.relay(name, ad);
    }

    @Override
    public void receive(String from, String ad) {
        ReceiveArea.append(from + "说: " + ad + "\n");
        //使滚动条滚动到最底端
        ReceiveArea.setCaretPosition(ReceiveArea.getText().length());
    }
}

/**
 * 4 定义卖方(Seller)类和买方(Buyer)类,它们是具体同事类,是客户(Customer)类的子类,
 * 它们实现了父类中的抽象方法,通过中介者类进行信息交流。
 */
class Buyer extends Customer {
    private static final long serialVersionUID = -474879276076308825L;

    public Buyer(String name) {
        super(name);
        ClientWindow(350, 100);
    }

    @Override
    public void send(String ad) {
        ReceiveArea.append("我(买方)说: " + ad + "\n");
        //使滚动条滚动到最底端
        ReceiveArea.setCaretPosition(ReceiveArea.getText().length());
        medium.relay(name, ad);
    }

    @Override
    public void receive(String from, String ad) {
        ReceiveArea.append(from + "说: " + ad + "\n");
        //使滚动条滚动到最底端
        ReceiveArea.setCaretPosition(ReceiveArea.getText().length());
    }
}

image-20211109124404731

优缺点

优点:

  • 降低了对象之间的耦合性,使得对象易于独立地被复用
  • 将对象间的一对多关联转变为一对一关联,提高系统的灵活性,使得系统易于维护和扩展。

缺点:当同事类太对时,中介者的职责将很大,它会变得复杂而庞大,以至于难易维护。

应用场景

  • 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。

模式的扩展

在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。

  1. 不定义中介者接口,把具体中介者对象实现成为单例。
  2. 同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用。

图片

package top.saodisheng.designpattern.mediator.v3;

import java.util.ArrayList;
import java.util.List;

/**
 * description:
 * 简化中介者模式
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-06
 */
public class SimpleMediatorPattern {
    public static void main(String[] args) {
        SimpleColleague c1, c2;
        c1 = new SimpleConcreteColleague1();
        c2 = new SimpleConcreteColleague2();
        c1.send();
        System.out.println("==============");
        c2.send();
    }
}

/**
 * 简单单例中介者
 */
class SimpleMediator {
    private static SimpleMediator smd = new SimpleMediator();
    private List<SimpleColleague> colleagues = new ArrayList<SimpleColleague>();

    private SimpleMediator() {
    }

    public static SimpleMediator getMedium() {
        return (smd);
    }

    public void register(SimpleColleague colleague) {
        if (!colleagues.contains(colleague)) {
            colleagues.add(colleague);
        }
    }

    public void relay(SimpleColleague scl) {
        for (SimpleColleague ob : colleagues) {
            if (!ob.equals(scl)) {
                ((SimpleColleague) ob).receive();
            }
        }
    }
}

/**
 * 抽象同事类
 */
interface SimpleColleague {
    void receive();

    void send();
}

/**
 * 具体同事类
 */
class SimpleConcreteColleague1 implements SimpleColleague {
    SimpleConcreteColleague1() {
        SimpleMediator smd = SimpleMediator.getMedium();
        smd.register(this);
    }

    @Override
    public void receive() {
        System.out.println("具体同事类1:收到请求。");
    }

    @Override
    public void send() {
        SimpleMediator smd = SimpleMediator.getMedium();
        System.out.println("具体同事类1:发出请求...");
        //请中介者转发
        smd.relay(this);
    }
}

/**
 * 具体同事类
 */
class SimpleConcreteColleague2 implements SimpleColleague {
    SimpleConcreteColleague2() {
        SimpleMediator smd = SimpleMediator.getMedium();
        smd.register(this);
    }

    @Override
    public void receive() {
        System.out.println("具体同事类2:收到请求。");
    }

    @Override
    public void send() {
        SimpleMediator smd = SimpleMediator.getMedium();
        System.out.println("具体同事类2:发出请求...");
        // 请中介者转发
        smd.relay(this);
    }
}

image-20211109125804646

源码中的应用

java.util.Timer
java.util.concurrent.Executer#execute()
java.util.concurrent.ExecuterService#submit()
java.lang.reflect.Method#invoke()
posted @ 2021-11-10 09:13  技术扫地生—楼上老刘  阅读(33)  评论(0编辑  收藏  举报