自学内容网 自学内容网

百日筑基第二十八天-23种设计模式-行为型总汇

百日筑基第二十八天-23种设计模式-行为型总汇

前言

设计模式可以说是对于七大设计原则的实现。

总体来说设计模式分为三大类:

  • 创建型模式,共五种:单例模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式。
  • 结构型模式,共七种:代理模式、外观模式、享元模式、组合模式、桥接模式、装饰器模式、适配器模式。
  • 行为型模式,共十一种:模板方法模式、迭代器模式、访问者模式、观察者模式、命令模式、状态模式、策略模式、备忘录模式、中介者模式、解释器模式、责任链模式。

模板方法模式

简介

在面向对象程序设计过程中,程序员常常会遇到如下情况:设计一个系统时知道算法所需的关键步骤,且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。例如:去医院看病一般都要经历以下流程:挂号、排队、就诊、取药等,其中挂号和排队对每个客户都是一样的,可以在父类中实现,但是就诊和取药是因人而异的。可以延迟到子类中实现。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它。

1)模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),在一个抽象类公开定义了执行此方法的模板。它的子类可以按照需要重写方法实现,但调用将以抽象类中定义的方式进行。
2)简单说,模板方法模式定义了一个操作中的算法骨架,而将步骤延迟到子类中,使得子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤。
3)这种类型的设计模式属于行为型模式。

模板方式的特点

模板方式的优点:
【1】封装了不变的部分,扩展可变部分。将不变部分的算法封装到父类中实现,而把可变部分的算法由各子类实现。便于子类继续扩展。
【2】在父类中提取了公共的部分代码,便于代码复用。
【3】部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

模板方式的缺点:
【1】对每个不同的实现都需要定义一个子类,这个导致类的个数增加,系统更加庞大,设计也更加抽象。
【2】父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,提高了代码的阅读难度。

使用场景:
【1】当多个子类具有公用的方法,却执行流程逻辑相同时。
【2】重要的、复杂的方法,可以考虑作为模板方法。

注意事项: 为了防止恶意操作,一般模板方法上都加有 final 关键字

模板方法模式结构类图

模板方法模式包含以下主要角色:
【1】抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成,如下:
 ① 抽象方法:在抽象类中申明,由具体子类实现。
 ② 具体方法:在抽象类中已经实现,再具体子类中可以继承或重写它。
 ③ 钩子方法:在抽象类中已经实现,例如:用于判断的逻辑方法或者定义一个空方法。子类根据情况要不要重写此方法,此方法为钩子方法。例如下面实例中的 isRecipe() 方法。
【2】具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

在这里插入图片描述

模板方式模式案例分析

【1】抽象类(医院看病时,整个流程的抽象类)整个流程属于不可变的,因此我使用了 final 修饰。

public abstract class AbstractHospital {
    //流程
    public final void procedure() {
        //1、挂号
        this.regiest();
        //2、排队
        this.queue();
        //3、看病
        this.treat();
        //4、取药,有点人不用开药,只是小事
        if(isRecipe()) {
            this.recipe();
        }
    }
    //挂号
    public String regiest() {
        String regiest = "你的订单号为"+((int) (Math.random()*10));
        System.out.println(regiest);
        return regiest;
    }
    //排队
    public void queue() {
        System.out.println("排队中。。。。");
    }
    //看病
    public abstract String  treat();
    //取药
    public abstract String recipe();

    //钩子方法
    boolean isRecipe() {
        return true;
    }
}

【2】抽象类的具体实现类(有多个,我们只写一个)

public class Patient_A extends AbstractHospital{
    @Override
    public String treat() {
        String treat = "胃病";
        System.out.println("你的病为"+treat);
        return treat;
    }
    @Override
    public String recipe() {
        String recipe = "健胃消食片";
        System.out.println("药单:"+recipe);
        return recipe;
    }

    //重写钩子方法
    @Override
    boolean isRecipe() {
        return false;
    }
}

【3】客户端类:创建子类对象,流程调用的是父类公共的流程方法。

public class Client {
    public static void main(String[] args) {
        //A 病人看病
        Patient_A patient_A = new Patient_A();
        //看病的流程
        patient_A.procedure();
        /**
         * 输出如下:
         * 你的订单号为2
         * 排队中。。。。
         * 你的病为胃病
         * 药单:健胃消食片 ==== 加了钩子程序则不显示
         */
        //B 病人看病,的流程也是一样,只需要实例化B,并调用公共的模板流程即可,提高代码的利用率
    }
}

模板方法模式应用源码分析

模板方法模式在 Spring 框架应用的源码分析:Spring IOC 容器初始化时运用到了模板方法模式;

【1】抽象类: AbstractApplicationContext继承接口(ConfigurableApplicationContext)其中的 refresh 方法就是模板方法,定义了执行的流程,方法中包含钩子方法、子类需要实现的抽象方法(refreshBeanFactory)、父类已实现的方法等等。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
        implements ConfigurableApplicationContext, DisposableBean {
    ......
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();

            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

            prepareBeanFactory(beanFactory);

            try {
                // 钩子方法,子类自行选择是否重写
                postProcessBeanFactory(beanFactory);

                invokeBeanFactoryPostProcessors(beanFactory);

                registerBeanPostProcessors(beanFactory);

                // 父类已实现
                initMessageSource();

                // 由父类进行实现
                initApplicationEventMulticaster();

                // 钩子方法
                onRefresh();

                registerListeners();

                finishBeanFactoryInitialization(beanFactory);

                // 父类实现
                finishRefresh();
            }

            catch (BeansException ex) {

                destroyBeans();

                cancelRefresh(ex);

                throw ex;
            }
        }
    }
    ......
}

【2】构建 AbstractApplicationContext 类图:

在这里插入图片描述

模板方法模式的注意事项和细节

【1】基本思想是:算法只存在于一个地方,也就是父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
【2】实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承且直接使用。
【3】既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构不变,同时由子类提供部分步骤的实现。
【4】该模式的不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增多,使得系统更加庞大。
【5】模板方式的使用场景:存在执行一系列步骤,且一类产品的此步骤基本相同,但其中个别步骤的实现细节不同时,通常可以考虑使用模板方法模式。

迭代器模式

在程序设计中,经常需要访问一个聚合对象中的各个元素,例如:我们使用 list 存储元素,通常的做法是将 list 的创建和遍历放在同一个类中。但这种方法不利于扩展,如果将存储方式更改为数组时,就需要更换迭代方式。违背了 “开闭原则”。“迭代器模式” 能较好的克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与遍历行为,对客户也隐藏了其内部细节,满足 “单一职责原则” 和 “开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含迭代器。

迭代器模式结构类图

迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。

迭代器模式主要包含以下角色:
【1】抽象聚合(Aggregate)角色:定义了存储、添加、删除聚合对象以及创建迭代对象的接口。
【2】具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。
【3】抽象迭代器(Iterator)角色:定义访问和遍历和聚合元素接口,通常包含 hasNext()、next()等方法。
【4】具体迭代器(ConcreteIterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

在这里插入图片描述

迭代器模式案例分析

【1】抽象迭代器: 使用 JDK 自带的 Iterator 接口,我们将源码粘贴过来,无需自行实现。子类需要实现 hasNext 和 next 方法

public interface Iterator<E> {
 
    boolean hasNext();
    //使用泛型 E
    E next();
 
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
 
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

【2】具体迭代器: 定义将 List 集合包装为 Iterator 遍历的对象 ListIterator

public class ListIterator implements Iterator<Object>{
    //定义一个 List 集合
    List<Phone> list;
 
    //构造器
    public ListIterator(List<Phone> list) {
        this.list = list;
    }
 
    //获取位数
    int index = 0;
    @Override
    public boolean hasNext() {
        if(list != null && list.size()>index) {
            return true;
        }else {
            return false;
        }
    }
 
    @Override
    public Object next() {
        Phone object = list.get(index);
        index+=1;
        return object;
    }
 
}

【3】具体迭代器: 定义将 数组集合包装为 Iterator 遍历的对象 ArrayIterator

public class ArrayIterator implements Iterator<Object>{
    //定义 电话数组
    Phone[] phones;
    //下标
    private int index = 0;
    //构造器
    public ArrayIterator(Phone[] phones) {
        this.phones = phones;
    }
 
    @Override
    public boolean hasNext() {
        if(phones[index] != null && phones.length > index) {
            return true;
        }else {
            return false;
        }
    }
 
    @Override
    public Object next() {
        Phone phone = phones[index];
        index+=1;
        return phone;
    }
}

【4】 定义 List 与 数组中存储的对象 Phone。

public class Phone {
    public String name;
    public String money;
    /**
     * @param name
     * @param money
     */
    public Phone(String name, String money) {
        super();
        this.name = name;
        this.money = money;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getMoney() {
        return money;
    }
    public void setMoney(String money) {
        this.money = money;
    }
}

【5】抽象聚合角色: 手机的抽象接口 IPhone

public interface IPhone {
    //获取手机名称
    public String getName();
    //创建一个获取 Iterator 实例的方法
    public Iterator<Object> createIterator();
    //增加手机的方法
    public void add(String name,String type);
}

【6】具体聚合角色: 小米手机集合类,通过 List 进行封装,并创建一个返回 Iterator 的方法,使用迭代的方式遍历。

public class XiaoMiPhoneImpl implements IPhone{
    //小米使用 list 存储产品
    private List<Phone> xiaoMis = new ArrayList<Phone>();
    //构造器
    public XiaoMiPhoneImpl() {
        add("红米", "1200");
        add("小米6", "2300");
        add("小米7", "3200");
    }
 
    @Override
    public String getName() {
        return "====小米手机====";
    }
    //创建遍历器
    @Override
    public Iterator<Object> createIterator() {
        return new ListIterator(xiaoMis);
    }
        //集合中添加小米产品
    @Override
    public void add(String name, String money) {
        xiaoMis.add(new Phone(name, money));
    }
}

【7】具体聚合角色: 华为手机集合类,通过 数组 进行封装,并创建一个返回 Iterator 的方法,使用迭代的方式遍历。

public class HuaWeiPhoneImpl implements IPhone{
    //存储华为手机的数据
    Phone phone[];
    private int index = 0;
 
    //构造器
    public HuaWeiPhoneImpl() {
        phone = new Phone[5];
        add("荣耀", "1300");
        add("华为P8", "2000");
        add("华为P20", "8000");
    }
 
    @Override
    public String getName() {
        return "====华为手机====";
    }
 
    @Override
    public Iterator<Object> createIterator() {
        return new ArrayIterator(phone);
    }
 
    @Override
    public void add(String name, String money) {
        phone[index] = new Phone(name,money);
        index +=1;
    }
}

【8】构造一个数据的工厂类: 将所有的品牌集合在一块,通过 Iterator 接口的方式进行遍历输出。

public class OutputImpl {
    //定义一个集合
    private List<IPhone> phones;
 
    //构造器
    public OutputImpl(List<IPhone> phones) {
        this.phones = phones;
    }
 
    //输入方法
    public void outPrit() {
        //先调用 list 自身带的迭代器 Iterator
        Iterator<IPhone> iterator = phones.iterator();
        // 调用hasNext 方法
        while(iterator.hasNext()) {
            IPhone iPhones = iterator.next();
            //手机品牌名称
            System.out.println(iPhones.getName());
            //遍历品牌的所有手机,使用我们自己实现的Iterator
            pritlnPhone(iPhones.createIterator());
        }
    }
 
 
    private void pritlnPhone(Iterator<Object> createIterator) {
        while(createIterator.hasNext()) {
            Phone phone = (Phone) createIterator.next();
            System.out.println("品牌="+phone.getName()+"-----金额="+phone.getMoney());
        }
    }
}

【9】客户端调用: 创建手机品牌实体类,并将其组合在 List 中,调用输出工厂集合类即可。

public class Client {
 
    public static void main(String[] args) {
        //手机店集合
        List<IPhone> phones = new ArrayList<IPhone>();
        //创建华为手机 == 数组
        HuaWeiPhoneImpl huaWeis = new HuaWeiPhoneImpl();
        //创建小米手机 == list
        XiaoMiPhoneImpl xiaoMis = new XiaoMiPhoneImpl();
 
        //将其都加入到手机店集合
        phones.add(huaWeis);
        phones.add(xiaoMis);
 
        //调用公共的输入类
        OutputImpl outputImpl = new OutputImpl(phones);
        outputImpl.outPrit();
        /**
         * 结构如下:
         * ====华为手机====
        品牌=荣耀-----金额=1300
        品牌=华为P8-----金额=2000
        品牌=华为P20-----金额=8000
        ====小米手机====
        品牌=红米-----金额=1200
        品牌=小米6-----金额=2300
        品牌=小米7-----金额=3200
         */
    }
}

迭代器模式应用源码分析

分析一下 arrayList 的 iterator 的使用

【1】先了解下 ArrayList 的 Iterator 的使用:

public class IteratorDemo {
 
    public static void main(String[] args) {
        List<String> a = new ArrayList<>();
        a.add("t");// ..
        // 获取到迭代器
        Iterator<String> Itr = a.iterator();
        while (Itr.hasNext()) {
            System.out.println(Itr.next());
        }
    }
}

【2】进入 ArrayList 的源码: 实现了 List 接口,实现了 Iterator 方法,返回遍历对象:Iterator。相当于具体聚合对象。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>
{
    ...
        public Iterator<E> iterator() {
        return new Itr();
    }
    ...
}

【3】进入 List 接口查看: 发现包含一个 Iterator 的抽象方法 。相当于抽象聚合对象

public interface List<E> extends Collection<E> {
    ...
    Iterator<E> iterator();
    ...
}

【4】我们进入返回的 Iterator 对象的类 Itr ,是 ArraList 类的内部类。查看 hasNext()方法,会发现遍历的对象是 Object[] 数组,具体的迭代器类(实现 hasNext 和 Next 方法)

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    ......
}

【5】上述具体迭代器角色实现了 Iterator 接口,也就是抽象迭代器角色。与我们的案例中实现同一个接口,就不展示了。

【6】源码类图展示:同时多添加了两个具体的实现类:KeyIterator 与 LinkedList

在这里插入图片描述

访问者模式

简介

在开发中,有些集合存在多种不同的对象实例(例如:男人、女人),且每个对象也存在多种不同的访问者或处理方式(性格:暴躁、温和)。这样的例子还有很多,例如:好声音节目中不同评委,以及评委对他们的评价的选项,等等。这些被处理的数据元素相对稳定,而访问方式多种多样的数据结构,如果使用 “访问者模式” 来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方式,且不用修改原来的程序代码与数据结构,这提高了代码的扩展性和灵活性。

【1】访问者模式(Visitor Pattern): 封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新操作。为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
【2】主要将数据结构与数据操作分离: 解决数据结构和操作耦合性问题。
【3】访问者模式的基本工作原理: 在被访问者(上面提到的评委)的类里面加一个对外提供接待访问者的接口。
【4】访问者模式主要应用场景: 需要对一个对象结构中的对象进行很多不同的操作(这些操作彼此没有关联),同时避免让这些操作 “污染” 这些类对象,可以选用访问者模式。

访问者模式的优缺点

【访问者(Visitor)模式,其主要优点如下】:
 ● 扩展性好:能够在不修改对象结构中元素的情况下,为对象结构中的元素添加新功能;
 ● 复用性好:可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用功能;
 ● 灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由;

【访问者(Visitor)模式,其主要缺点如下】:
 ● 增加新的元素类很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加响应的具体操作,违背了 “开闭原则”
 ● 破坏封装:访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
 ● 违反了依赖倒置原则:访问模式依赖了具体类,而没有依赖抽象类。
 ● 具体元素对访问者公布细节:也就是说访问者关注了其他类的内部细节,这是迪米特法则所不建议的,这样造成了具体元素变得更比较困难。

访问者模式结构类图

访问者模式的关键在于:如何将元素的操作分离出来封装成独立的类,其基本结构如下:
【1】抽象访问者角色(Visitor): 定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作,该操作中的参数类型标识了被访问的具体元素。
【2】具体访问者角色(ConcreteVisitor): 实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
【3】抽象元素角色(Element): 声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept 方法的参数。
【4】具体元素角色(ConcreteElement): 实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this),另外具体元素中可能包含本身业务逻辑的相关操作。
【5】对象结构角色(Object Structure): 是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由List、Set、Map 等聚合类实现。

在这里插入图片描述

访问者模式案例分析

评委评价歌手是否晋级的案例

【1】抽象访问者角色

//访问者接口
public interface Action {
    //得到评委A的评价  JudgesImpl_A 具体实现类
    public void getJudage_A(JudgesImpl_A judageA);
    //得到评委B的评价
    public void getJudage_B(JudgesImpl_B judageB);
}

【2】抽象访问者的具体实现类——晋级

//晋级
public class Promotion implements Action {
 
    @Override
    public void getJudage_A(JudgesImpl_A judageA) {
        System.out.println("评委A名称:"+judageA.getName());
        System.out.println("给的评价是=========晋级");
    }
 
    @Override
    public void getJudage_B(JudgesImpl_B judageB) {
        System.out.println("评委B名称:"+judageB.getName());
        System.out.println("给的评价是=========晋级");
    }
 
}

【3】抽象访问者的具体实现类——淘汰

public class Eleminate implements Action{
 
    @Override
    public void getJudage_A(JudgesImpl_A judageA) {
        System.out.println("评委A名称:"+judageA.getName());
        System.out.println("给的评价是=========淘汰");
    }
 
    @Override
    public void getJudage_B(JudgesImpl_B judageB) {
        System.out.println("评委B名称:"+judageB.getName());
        System.out.println("给的评价是=========淘汰");
    }
 
}

【4】 抽象元素接口,包含访问者方法 accept ——评委接口

//评委接口  被访问者接口
public interface IJudges {
    //提供一个访问方法,出入访问者实例
    public void accept(Action action);
}

【5】抽象元素接口的具体实现类——评委A

//评委A  被访问者
public class JudgesImpl_A implements IJudges{
    //传入一个评委名称
    private String name;
    //构造器
    public JudgesImpl_A(String name) {
        super();
        this.name = name;
    }
 
    @Override
    public void accept(Action action) {
        System.out.println("具体被访问者(评委A)对象,给出的评价是==");
        action.getJudage_A(this);
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

【6】抽象元素接口的具体实现类——评委B

//评委B  被访问者
public class JudgesImpl_B implements IJudges{
    private String name;
    //构造器
    public JudgesImpl_B(String name) {
        super();
        this.name = name;
    }
 
    @Override
    public void accept(Action action) {
        System.out.println("具体被访问者(评委B)对象,给出的评价是==");
        action.getJudage_B(this);
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

【7】对象结构实现类,将被访问类进行封装。

//将评委评论进行统计
public class ObjectStrFactory {
    //维护一个被访问者集合
    private List<IJudges> judage = new LinkedList();
 
    //添加评委
    public void add(IJudges jud) {
        judage.add(jud);
    }
    //移除评委
    public void remove(IJudges jud) {
        judage.remove(jud);
    }
    //展示评论
    public void disply(Action action) {
        for (IJudges iJudges : judage) {
            iJudges.accept(action);
        }
    }
}

【8】客户端调用

public class Client {
    public static void main(String[] args) {
        //创建结构体工厂
        ObjectStrFactory objectStrFactory = new ObjectStrFactory();
        //添加评委
        objectStrFactory.add(new JudgesImpl_A("周杰伦"));
        objectStrFactory.add(new JudgesImpl_B("蔡徐坤"));
        //展示评价:晋级的评价
        /**
         * 输出:
         * 具体被访问者(评委A)对象,给出的评价是==
         * 评委A名称:周杰伦
         * 给的评价是=========晋级
         * 具体被访问者(评委B)对象,给出的评价是==
         * 评委B名称:蔡徐坤
         * 给的评价是=========晋级
         */
        objectStrFactory.disply(new Promotion());
        //如果评价是 淘汰
        /**
         * 输入:
         * 具体被访问者(评委A)对象,给出的评价是==
         * 评委A名称:周杰伦
         * 给的评价是=========淘汰
         * 具体被访问者(评委B)对象,给出的评价是==
         * 评委B名称:蔡徐坤
         * 给的评价是=========淘汰
         */
        objectStrFactory.disply(new Eleminate());
    }
}

访问者模式的优点实操

当需要扩展一个 “待定” 的元素选项时,非常方便,简单两步操作即可完成:
【1】添加 “待定” 元素的实体类,如下:并实现所有评委给的 “待定评价” 即可。

//待定
public class Wait implements Action{
 
    @Override
    public void getJudage_A(JudgesImpl_A judageA) {
        System.out.println("评委A名称:"+judageA.getName());
        System.out.println("给的评价是=========待定");
    }
 
    @Override
    public void getJudage_B(JudgesImpl_B judageB) {
        System.out.println("评委B名称:"+judageB.getName());
        System.out.println("给的评价是=========待定");
    }
 
}

【2】客户端调用时,只需传入待定的实体类,便可获取待定评价。通过扩展我们才看出来访问者模式的强大之处。

public class Client {
    public static void main(String[] args) {
        //输出
        /**
         * 具体被访问者(评委B)对象,给出的评价是==
         * 评委B名称:蔡徐坤
         * 给的评价是=========淘汰
         * 具体被访问者(评委A)对象,给出的评价是==
         * 评委A名称:周杰伦
         * 给的评价是=========待定
         * 具体被访问者(评委B)对象,给出的评价是==
         * 评委B名称:蔡徐坤
         * 给的评价是=========待定
         */
        objectStrFactory.disply(new Wait());
    }
}

观察者模式

简介

生活中,许多事物不是单独存在的,其中一个事物发生变化可能会导致一个或多个其他事物的行为也发生变化。例如:公众号的博主与用户之间(每当推送一篇文章,我们就能被动的接收到一篇文章,前提是你关注了它)。在软件设计中也是一样,例如:MVC 模式中的模型与视图的关系。此类场景使用观察者模式来实现的话,就非常方便。

观察者模块的定义与优缺点

观察者模式(Observer Pattern): 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。

观察者模式的优点如下: ①、降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。②、目标与观察者之间建立了一套触发机制。

观察者模式的缺点如下: ①、目标与观察者之间的依赖关系并没有完全解除。②、当观察者对象很多时,通知的发布会花费很长时间,影响程序的效率。③、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。④、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

观察者模式的结构与类图

实现观察者模式时,需要注意具体目标对象和具体观察者对象之间不能直接调用,否则会使两者之间紧密耦合起来,这违反了面向对象的设计原则。

观察者模式主要包含以下角色:
【1】抽象主题角色(Subject): 也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
【2】具体主题角色(Concrete Subject): 也叫具体目标类,它实现了抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
【3】抽象观察者角色(Observer): 它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
【4】具体观察者角色(Concrete Observer): 实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

在这里插入图片描述

观察者模式案例分析

【1】抽象主题角色(Subject): 包含对观察者对象的增加、删除和通知等方法的抽象接口。

//抽象主题类
public interface ISubject {
    //添加观察者对象
    public void addObserver(IObserver o);
    //删除观察者对象
    public void removeObserver(IObserver o);
    //通知方法
    public void notifyObserver();
}

【2】具体主题角色(Concrete Subject): 需要创建一个集合,存放观察者对象。并实现 ISubject 接口,并实现增删和通知方法。同时添加修改数据的方法 setDate。

public class SubjectImpl implements ISubject{
    private String name;//博客名
    private String content;//博客内容
    private String type;//博客类型
 
    public void setDate(String name,String content,String type) {
        this.name=name;
        this.content=content;
        this.type = type;
    }
 
    //定义一个集合存放 观察者对象
    List<IObserver> observers;
 
    //构造器
    public SubjectImpl() {
        observers = new ArrayList<IObserver>();
    }
    //添加
    @Override
    public void addObserver(IObserver o) {
        observers.add(o);
    }
    //删除
    @Override
    public void removeObserver(IObserver o) {
        observers.remove(o);
    }
 
    @Override
    public void notifyObserver() {
        for (IObserver iObserver : observers) {
            if(iObserver != null) {
                iObserver.update(name,content,type);
            }
        }
    }
}

【3】抽象观察者角色(Observer):抽取观察者们的共同方法 update 获取目标对象传递过来的信息。

//抽象观察者
public interface IObserver {
    //接受主题类的信息
    public void update(String name,String content,String type);
}

【4】具体观察者角色(Concrete Observer):实现抽象的观察者角色,并获取目标对象传递的值,进行逻辑处理。

public class ObserverImpl_A implements IObserver{
 
    @Override
    public void update(String name, String content, String type) {
        //对接受到的信息进行逻辑处理
        System.out.println("===观察者A====");
        System.out.println("***博客名称 : " + name + "***");
        System.out.println("***博客内容: " + content + "***");
        System.out.println("***博客类型: " + type + "***");
    }
}

【5】客户端:创建目标对象和观察者对象,并将观察者对象添加到目标对象的观察者集合中,并进行通知。

public class Client {
 
    public static void main(String[] args) {
        //目标对象 博客博主
        SubjectImpl subjectImpl = new SubjectImpl();
        //创建观察者对象 用户A
        ObserverImpl_A observerImpl_A = new ObserverImpl_A();
        //将观察对象 关注 表表对象
        subjectImpl.addObserver(observerImpl_A);
        //博客开始更新博客
        subjectImpl.setDate("Java设计模式", "访问者模式", "技术类");
        //通知观察者  关注的用户门
        subjectImpl.notifyObserver();
    }
}

观察者模式扩展优点

【1】只需要新增观察者对象类。

package obsever;
 
public class ObserverImpl_B implements IObserver{
 
    @Override
    public void update(String name, String content, String type) {
        //对接受到的信息进行逻辑处理
        System.out.println("===观察者B====");
        System.out.println("***博客名称 : " + name + "***");
        System.out.println("***博客内容: " + content + "***");
        System.out.println("***博客类型: " + type + "***");
    }
}

【2】客户端调用时,将其注册到目标对象中即可。符合 OCP 原则。

ObserverImpl_B observerImpl_B = new ObserverImpl_B();
//将观察对象 关注 表表对象
subjectImpl.addObserver(observerImpl_B);

观察者模式应用源码分析

【1】查看一下 Observable 的源码,发现 Observable 是一个具体的主题类,此处与我们的例子不同之处是没有实现接口,我们细想也会发现,其实主题类也无需实现接口。

public class Observable {
    private Vector<Observer> obs;
 
    /** 创建集合 存放观察者对象 Observer */
 
    public Observable() {
        obs = new Vector<>();
    }
    //添加
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }
    //删除
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    //通知
    public void notifyObservers() {
        notifyObservers(null);
    }
    public void notifyObservers(Object arg) {
 
        Object[] arrLocal;
 
        synchronized (this) {
 
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }
        //循环调用 观察者对象的 update 通知方法
        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

【2】抽象观察者接口 Observer。

public interface Observer {
    void update(Observable o, Object arg);
}

命令模式

简介

软件开发中,通常会存在 “方法的请求者” 与 “方法的实现者” 之间存在紧密的耦合关系。这不利于软件功能的扩展与维护。特别是针对行为进行(撤销、重做、记录)一系列操作时很不方便,因此 “如何将方法的请求者与方法的实现者解耦”,是命令模式的主要任务和功能。在现实生活中,这样的例子也很多,例如,电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)

1)、命令模式(Command Pattern): 是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传递给对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
2)、命令模式使得请求发送者与请求接受者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
3)、在命令模式中,会将一个请求封装为一个对象,以便使用不同的参数(执行者)来表示不同的请求。同时命令模式也支持撤销的操作。
4)、增加或删除命令非常方便。采用命令模式增加和删除命令不会影响其他类,它满足 “开闭原则” ,即扩展灵活。
5)、可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
6)、方便实现 Undo 和 Redo 操作(适合命令模式)。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
7)、其缺点是:可能产生大量具体命令类。因为对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。

命令模式结构类图

命令模式包含以下主要角色:
【1】接口命令(Command)角色: 声明执行命令的接口,拥有执行命令的抽象方法。
【2】具体命令(Concrete Command)角色: 是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
【3】接收者(Receiver)角色: 执行命令功能的相关操作,是具体命令对象业务的真正实现者。
【4】调用者(Invoker)角色: 是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,不直接访问接收者。

在这里插入图片描述

命令模式案例分析

我们通过写一个空调遥控器按钮的案例来体会命令模式的特点:

【1】接口命令角色:Command,其包含两个主要方法(execute() 与 undo())

public interface Command {
    //命令的执行方法
    public void execute();
    //撤销操作
    public void undo();
}

【2】 具体命令实现类:写一个制热的命令类,实现命令接口,并组合接受者角色,调用目标方法。类似的类还有制冷等等。

//制热命令
public class HeadCommand implements Command{
    //组合空调具体执行类
    private AirCondition airCondition;
    //构造器
    public HeadCommand(AirCondition airCondition) {
        super();
        this.airCondition = airCondition;
    }
 
    @Override
    public void execute() {
        //调用空调的制热方法
        airCondition.Head();
    }
 
    @Override
    public void undo() {
        //返回上一次操作
        airCondition.refrigeration();
    }
}

【3】接收者角色:空调类(AirCondition )

//空调类
public class AirCondition {
    //制热
    public void Head() {
        System.out.println("空调制热.......");
    }
    //制冷
    public void  refrigeration() {
        System.out.println("空调开始制冷......");
    }
}

【4】调用者角色:遥控器类 (RemoteController )

//调用者( 遥控器 ),也是命令模式的精华
public class RemoteController {
    //添加命令按钮
    Command[] commands;
    //撤销按钮
    Command undo;
    //构造器
    public RemoteController() {
        //初始化按钮
        commands = new Command[5];
        for(int i=0;i<5;i++) {
            commands[i] = new NoCommand();
        }
    }
 
    //给遥控器添加按钮
    public void setCommand(int n , Command command) {
        commands[n]=command;
    }
 
    //调用制热按钮
    public void headCommonButton(int n) {
        commands[n].execute();
    }
 
    //撤回
    public void undoButton() {
        undo.undo();
    }
}

【5】客户端调用

public class Client {
    public static void main(String[] args) {
        //创建空调实例
        AirCondition airCondition = new AirCondition();
        //调用命令类
        RemoteController remoteController = new RemoteController();
        //将命令添加至遥控按钮中
        HeadCommand headCommand = new HeadCommand(airCondition);
        remoteController.setCommand(0,headCommand);
        //调用制热功能
        remoteController.headCommonButton(0);
    }
}

【注意】 命令模式的好处:当增加新产品时,只需要创建新产品类即可。无需修改命令类,符合开闭原则。例如我们增加一个冰箱的制热功能。只需要添加冰箱实体类和制热命令类,同时在客户端将其添加至命令类中即可,无需修改命令类。

状态模式

在现实生活中,常常会出现这样的事例:一个人的情绪高兴时,会做出一些助人为乐的事情。情绪低落的时候,会做出一些伤天害理的事情。这里的情绪就是状态,对应做的事情就是行为。在软件开发中也是类似的,有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。

基本介绍

【1】状态(State)模式的定义: 对有状态的对象,把复杂的 “判断逻辑” 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
【2】状态模式(State Pattern): 它主要用来解决对象在多种状态转换时,需要对外输出不同的行为问题。状态和行为是一一对应的,状态之间可以相互转换。
【3】当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
【4】这种类型的设计模式属于行为型模式。

状态模式的结构

状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。状态模式包含以下主要角色:
【1】环境(Context)角色: 也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
【2】抽象状态(State)角色: 定义一个接口,用以封装环境对象中的特定状态所对应的行为。
【3】具体状态(Concrete State)角色: 实现抽象状态所对应的行为。

在这里插入图片描述

状态模式的应用案例

使用状态模式解决 APP 抽奖问题:根据如下流程中的状态,完成具体的业务操作。

在这里插入图片描述

【1】应用的结构类图: 精华在RaffleActive(上下文类)和状态的子类中。两者之间相互组合,减少复杂的逻辑判断。

在这里插入图片描述

【2】State 接口的实现如下:

/*
 *  状态对应的抽象行为
 */
public interface State {
    //扣除积分
    public void deduceMoney();
    //是否中奖
    public boolean raffle();
    //发放奖品
    public void dispensePrize();
}

【3】State 子类一:扣除积分类 NoRaffleState 的实现如下,扣除成功后,将 state 设置为抽奖状态。

public class NoRaffleState implements State{
 
    //初始化时传入活动引用,扣除积分后改变其状态
    RaffleActivity active;
 
    //构造器
    public NoRaffleState(RaffleActivity active) {
        this.active = active;
    }
 
    // 当前状态可以扣分,扣分后修改状态
    @Override
    public void deduceMoney() {
        System.out.println("扣除5个积分");
        active.setState(active.getCanRaffeState());
 
    }
    @Override
    public boolean raffle() {
        System.out.println("抽了积分才能抽奖");
        return false;
    }
 
    @Override
    public void dispensePrize() {
        System.out.println("不能方法奖品");
    }
}

​ 【4】 State 子类一:抽奖状态类 CanRaffeState,如果抽中设置状态为抽中状态,未抽中时设置状态为扣积分状态。

public class CanRaffeState implements State{
    RaffleActivity active;
 
    //构造器
    public CanRaffeState(RaffleActivity active) {
        this.active = active;
    }
 
    //扣积分
    @Override
    public void deduceMoney() {
        System.out.println("已扣除积分");
    }
 
    @Override
    public boolean raffle() {
        System.out.println("正在抽奖,请稍等");
        int num = new Random().nextInt(5);
        //20% 的中奖机会,中了则返回true
        if(num == 0) {
            active.setState(active.getDispenseState());
            return true;
 
        }else {
            System.out.println("很遗憾没有抽中奖品");
            active.setState(active.getNoRaffleState());
            return false;
        }
    }
 
    @Override
    public void dispensePrize() {
        System.out.println("抽奖中,不能发送奖品");
    }
}

【5】 State 子类一:抽中状态类 DispenseState,如果礼物未送完,则发送礼物,并设置状态为扣分状态。否则设置为礼物以发放完,且活动结束状态。

public class DispenseState implements State{
    RaffleActivity active;
 
    //构造器
    public DispenseState(RaffleActivity active) {
        this.active = active;
    }
 
    @Override
    public void deduceMoney() {
        System.out.println("不能扣积分");
    }
 
    @Override
    public boolean raffle() {
        System.out.println("不能抽奖");
        return false;
    }
 
    @Override
    public void dispensePrize() {
        if(active.getCount()>0) {
            System.out.println("恭喜中奖了,奖品已发货");
            active.setState(active.getNoRaffleState());
        }else {
            System.out.println("很遗憾,奖品已发完");
            active.setState(active.getDispenseOutState());
        }
    }
}

【6】 State 子类一:礼物发放完,却活动结束状态类 DispenseOutState

public class DispenseOutState implements State{
 
    @Override
    public void deduceMoney() {
        System.out.println("活动结束");
    }
 
    @Override
    public boolean raffle() {
        System.out.println("活动结束");
        return false;
    }
 
    @Override
    public void dispensePrize() {
        System.out.println("活动结束");
        System.exit(0);
    }
}

【7】上下文类 RaffleActivity,主要存储用户的状态和礼物的总记录数等重要信息。并组合所有的状态子类,传入自身对象。

public class RaffleActivity {
    //状态
    private State state;
    //奖品记录数
    private int count;
 
    //构造器,初始化上述两个属性
    public RaffleActivity(int count) {
        //客户端创建的时候,说明开始是开始抽奖状态。
        state = noRaffleState;
        this.count = count;
    }
 
    NoRaffleState noRaffleState = new NoRaffleState(this);
    CanRaffeState canRaffeState = new CanRaffeState(this);
    DispenseState dispenseState = new DispenseState(this);
    DispenseOutState dispenseOutState = new DispenseOutState();
 
    // 当前状态可以扣分,扣分后修改状态
    public void deduceMoney() {
        state.deduceMoney();
    }
    public void raffle() {
        if(state.raffle()) {
            state.dispensePrize();
        }
    }
 
    //需要注意,我们的数量应该是递减的
    public int getCount() {
        int countNum = count;
        count--;
        return countNum;
    }
 
    public void setCount(int count) {
        this.count = count;
    }
 
    public State getState() {
        return state;
    }
 
    public void setState(State state) {
        this.state = state;
    }
 
    public NoRaffleState getNoRaffleState() {
        return noRaffleState;
    }
 
    public CanRaffeState getCanRaffeState() {
        return canRaffeState;
    }
 
    public DispenseState getDispenseState() {
        return dispenseState;
    }
 
    public DispenseOutState getDispenseOutState() {
        return dispenseOutState;
    }
}

【8】客户端端调用类 Client,只需要调用上下文类,便可实现客户端的需求。

public class Client {
 
    public static void main(String[] args) {
        //为了演示方便,就定义只有一个奖品
        RaffleActivity activity = new RaffleActivity(1);
        for(int i=0;i<30;i++) {
            System.out.println("======第"+i+"次,抽取奖品========");
            //扣积分
            activity.deduceMoney();
            //抽奖
            activity.raffle();
        }
    }
}

状态模式的特点

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

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

策略模式

在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。

策略模式基本介绍

【1】策略模式(Strategy Pattern)中,定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的用户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同对象进行管理。
【2】这算法体现了几个设计原则,第一:把变化的代码从不变的代码中分离出来;第二:针对接口编程而不是具体的类(定义了策略接口);第三:多个组合/聚合,少用继承(客户通过组合方式使用策略)
【3】多重条件语句不易维护,而使用策略模式可以避免使用多重条件查询。算法可以自由切换。
【4】策略模式提供了对 “开闭原则” 的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
【5】策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
【6】缺点:客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。同时策略模式造成很多的策略类。

策略模式结构类图

TIP:策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。

策略模式的主要角色如下: 抽象策略(Strategy)类:定义一个公共接口,各种不同的算法以不同的方式实现这个接口,一般使用接口或者抽象类。
具体策略(Concrete Strategy)类: 实现了抽象策略定义的接口,提供具体的算法实现。
上下文(Context)类: 持有一个具体的策略类引用,最终给客户端调用。

在这里插入图片描述

策略模式案例分析

我们通过写一个旅游出行方式的案例来体会策略模式的特点:

【1】交通方式的抽象策略角色

//交通方式的 抽象策略
public interface TripMode {
    public void goOut();
}

【2】具体策略角色(火车、汽车、飞机)

//具体实现类一 : 飞机
public class ByCar implements TripMode{
 
    @Override
    public void goOut() {
        System.out.println("飞机出行,最快的选择");
    }
}
//具体实现类二 : 火车
public class ByTrain implements TripMode{
 
    @Override
    public void goOut() {
        System.out.println("火车出行,最安全的选择");
    }
}
//具体实现类三 : 自驾车
public class SelfDrive implements TripMode{
 
    @Override
    public void goOut() {
        System.out.println("旅游就的是自驾车");
    }
}

【3】我们就举一个最安全的出行实例:组合抽象策略类,实现了具体使用类与策略类的分离。

package stratege;
//注重安全的人,都会选择火车
public class Safe {
    //组合接口
    private TripMode train;
 
    public Safe() {
        //组合一个火车实例
        train = new ByTrain();
    }
 
    //客户端也可以根据情况进行重新赋值
    public void setTrain(TripMode train) {
        this.train = train;
    }
 
    public void toBeijing() {
        //去北京选择的工具
        train.goOut();
    }
}

【4】客户端直接调用即可,或者使用 set 对其策略进行重置。

public class Client {
    public static void main(String[] args) {
        Safe safe = new Safe();
        //火车出行,最安全的选择
        safe.toBeijing();
    }
}

策略接口的源码应用分析

【1】抽象策略类:Comparator 接口

public interface Comparator<T> {
    ...
    int compare(T o1, T o2);
    ...
}

【2】具体策略类:可以根据自己的需求,实现不同的升序和降序等策略。

public static void main(String[] args) {
    Integer[] data = {3,1,5,8,6};
    //实现升序排序,返回-1放左边,1放右边,0不变
    //Comparator 就是一个抽象策略类(接口),我们对其进行实现,就是一个具体的策略
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            if(o1>o2) {
                return 1;
            }else {
                return -1;
            }
        }
    };
    Arrays.sort(data,comparator);
    //[1, 3, 5, 6, 8]
    System.out.println(Arrays.toString(data));
    }
}

备忘录模式

备忘录模式(Memento Pattern): 保存对象的某个状态,以便在未来需要的时候进行数据的恢复。相当容易理解,举个简单的例子:Word 软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;

备忘录模式的基本介绍

【1】备忘录模式(Memento Pattern): 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
【2】现实生活中备忘录是用来记录某些要去做的事情,或者是记录已经达成共同意见的事情,以防忘记。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。
【3】备忘录模式属于行为型模式。
【4】实现了信息的封装,使得用户不需要关心状态和保存细节。符合 “单一职责原则” 。
【5】为了节约内存,备忘录模式可以和原型模式配合使用。

备忘录模式的结构与类图

忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,备忘录模式的主要角色如下:
【1】发起人(Originator)角色: 记录当前对象的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
【2】备忘录(Memetor)角色: 负责存储发起人对象的内部状态,在需要的时候提供这些内部状态给发起人。
【3】管理者(Caretaker)角色: 对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

在这里插入图片描述

备忘录模式案例分析

我们使用备忘录模式写一个学校 100 米考试的案例:5 人一组,进行分组测试。我们需要备份的对象是,一组学生的成绩。

【1】发起人(Originator)角色: 发起人需要依赖备忘录类,对自己每次记录的成绩进行备份 createMemento 。同时需要提供一个还原方法 getOriginalFromMemento 将需要的备份类作为参数传递进来,并将结果赋值给目标类。

//100 米 测试  5人一组 ,这是个人所花费的时间,我们使用备忘录的方式实现一下。
public class Original {
    //姓名
    private String name;
    //时间
    private int timestamp;
 
    public Memento createMemento() {
        return new Memento(name, timestamp);
    }
    //获取目标对象  通过排名
    public void getOriginalFromMemento(Memento memento) {
        name = memento.getName();
        timestamp = memento.getTimestamp();
    }
 
    public int getTimestamp() {
        return timestamp;
    }
 
    public void setTimestamp(int timestamp) {
        this.timestamp = timestamp;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Original [name=" + name + ", timestamp=" + timestamp + "]";
    }
}

【2】备忘录(Memetor)角色: 提供一些目标对象需要备份的属性,通过构造器进行属性传递。

public class Memento {
    //姓名
    private String name;
    //时间
    private int timestamp;
 
    //构造器
    public Memento(String name, int timestamp) {
        super();
        this.name = name;
        this.timestamp = timestamp;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getTimestamp() {
        return timestamp;
    }
 
    public void setTimestamp(int timestamp) {
        this.timestamp = timestamp;
    }
}

【3】管理者(Caretaker)角色:创建一个存储备忘录对象的集合 `` 等,同时创建添加、获取、清空等方法。

public class Caretaker {
    //排名
    private int index = 1;
    //用于顺序存储 参加测试的 十名同学的成绩
    Map<Integer, Memento>  mementos = new HashMap<Integer, Memento>();
    //提供一个 add 方法
    public void add(Memento m) {
        mementos.put(index, m);
        index += 1;
    }
    //获取备份类
    public Memento get(int key) {
        return mementos.get(key);
    }
    //归位
    public void remove() {
        mementos.clear();
    }
}

【4】客户端(Client)角色:首先将顺序按的前五名同学的成绩及姓名,通过集合 Map 按照名次存储。五名同学测试完成后,通过目标类的 getOriginalFromMemento 方法,根据名次获取同学的成绩,并记录在成绩花名册中。最后,清空记录下一组。

public class Client {
    public static void main(String[] args) {
        //创建 目标类
        Original original = new Original();
        //管理类
        Caretaker managerMemento = new Caretaker();
        //记录成绩
        recordResults(original,managerMemento);
        //获取第一名的成绩
        System.out.println("恢复前目标类的记录值"+original.toString());
        //从集合中获取第一名的值
        Memento memento1 = managerMemento.get(1);
        //调用目标类的还原方法
        original.getOriginalFromMemento(memento1);
        System.out.println("恢复第一名的成绩信息:"+original.toString());
 
        //从集合中获取第三名的值
        Memento memento3 = managerMemento.get(3);
        //调用目标类的还原方法
        original.getOriginalFromMemento(memento3);
        System.out.println("恢复第三名的成绩信息:"+original.toString());
 
        //清空记录下一组
        managerMemento.remove();
    }
 
    private static void recordResults(Original original,Caretaker managerMemento) {
        //第一名学生成绩
        original.setName("张三");
        original.setTimestamp(2330);
        //创建一个备份类
        Memento memento1 = original.createMemento();
        //备份类存入 管理类中
        managerMemento.add(memento1);
 
        //第二名学生成绩
        original.setName("李四");
        original.setTimestamp(2550);
        //创建一个备份类
        Memento memento2 = original.createMemento();
        //备份类存入 管理类中
        managerMemento.add(memento2);
 
        //第三名学生成绩
        original.setName("王五");
        original.setTimestamp(2560);
        //创建一个备份类
        Memento memento3 = original.createMemento();
        //备份类存入 管理类中
        managerMemento.add(memento3);
    }
}

【5】结果展示: 备份者模式思想相对简单,主要查看细节上的实现。备份类相对简单,就一个普通类。但是目标类,提供了存储原始对象和获取原始对象的方法,是备份者模式的精华所在。

恢复目标类的记录值Original [name=王五, timestamp=2560]
恢复第一名的成绩信息:Original [name=张三, timestamp=2330]
恢复第三名的成绩信息:Original [name=王五, timestamp=2560]

中介者模式

在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是 “网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,班长和团支书等干部需要记住同学们的电话,且同学中如果有人的电话修改了,需要告诉所有干部,自己的手机号修改了,这叫作 “牵一发而动全身”,非常复杂。如果把这种 “网状结构” 改为 “星形结构” 的话,将大大降低它们之间的 “耦合性”,这时只要找一个 “中介者” 就可以了。如前面所说的问题,只要在网上建立一个每个干部都可以访问的 “通信录”(中介)就解决了。这样的例子还有很多,例如,你刚刚工作想租房,可以找 “房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找 “人才交流中心 ”帮忙。软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者,它将大大降低对象之间的耦合性,提高系统的灵活性。

中介者模式的定义与优缺点

【1】中介者模式(Mediator): 定义一个中介对象来封装一些列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变他们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型代表
【2】中介者模式是一种对象行为模式,其主要优点如下: ①、降低了对象之间的耦合性,使得对象易于独立地被复用。②、将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
【3】中介者模式主要缺点如下: ①、当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。②、中介者承担了较多的责任,一旦中介者出现了问题,整个系统就会受到影响。
【4】使用场景: 1、系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。 2、想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
【5】注意事项: 不应当在职责混乱的时候使用。

中介者模式的结构与类图

中介者模式的关键点是找出 “中介者”,中介者模式包含一下主要角色:
【1】抽象中介者(Mediator)角色: 它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
【2】具体中介者(ConcreteMediator)角色: 实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
【3】抽象同事类(Colleague)角色: 定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
【4】具体同事类(Concrete Colleague)角色: 是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。

在这里插入图片描述

中介者模式案例分析

【1】抽象中介者(Mediator)角色: 包含将租客注入到集合中的方法 register 和 租客发送需求后需要进行业务逻辑处理(调用其他租客的信息或房东信息进行匹配)的方法 getMessage

public interface Mediator {
    //注册同事类
    public void register(Colleague colleague);
    //获取消息
    public void getMessage(Colleague colleague);
}

【2】具体中介者(ConcreteMediator)角色: 对中介者接口的实现,这里存储租客对象的集合为 Map 将租客类名作为 Key 存储,并在 getMessage 方法中,将与请求租客信息相符的需求进行反馈。

package mediator;
 
import java.util.HashMap;
import java.util.Map;
 
public class ConcreteMediator implements Mediator{
    //创建存放 mediator 的集合 map
    Map<String, Colleague> mediators = new HashMap<>();
 
    @Override
    public void register(Colleague colleague) {
        if(colleague instanceof Colleague_A) {
            mediators.put("colleague_A", colleague);
        }else if(colleague instanceof Colleague_B) {
            mediators.put("colleague_B", colleague);
        }
    }
    //中介获取到 租客A 与 租客 B 的信息后,进行智能分析,并给出以下结论
    @Override
    public void getMessage(Colleague colleague) {
        //当为组合A 调用中介时,中介将租客 B 的需求进行返回 ,表示可以合租
        //这里就是中介的作用,将租客A与租客B进行关联,之间的业务逻辑都在中介中实现,将A与B彻底解耦,便于扩展维护
        if(colleague instanceof Colleague_A) {
            //智能分析A需求
            handler(colleague.needs());
            //调用租客A的需求
            System.out.println("服务的当前对象为====租客A=====");
            System.out.println("租客A 你的需求与 租客B 相吻合,下面将为你展示租客B的需求");
            String need_B = mediators.get("colleague_B").needs();
            System.out.println(need_B);
        }else if(colleague instanceof Colleague_B) {
            //智能分析B需求
            handler(colleague.needs());
            //调用租客B的需求
            System.out.println("服务的当前对象为====租客B=====");
            System.out.println("租客B 你的需求与 租客A 相吻合,下面将为你展示租客A的需求");
            String need_A = mediators.get("colleague_A").needs();
            System.out.println(need_A);
        }
    }
    //通过人工智能对用户需求进行分析 , 非设计模式的重点,省略
    private void handler(String needs) {
 
    }
}

【3】抽象同事类(Colleague)角色: 租客对象的抽象接口,将租客的共同点抽取。sendMessage 方法是通过调用中介的 getMessage 方法进行业务逻辑处理,返回符合用户需求的房屋信息或房客信息。needs 则为用户的需求。

public interface Colleague {
    //租客发出需求
    public void sendMessage();
    //需求方法
    public String needs();
}

【4】具体同事类(Concrete Colleague)角色: 租客的具体信息,我们距离用创建了两个租客A与B,实现租客接口 Colleague 并调用中介的业务逻辑处理方法 getMessage 进行房屋信息匹配。如下:租客A

public class Colleague_A implements Colleague{
    private Mediator mediator;
    //组合中介类
    public Colleague_A(Mediator mediator) {
        this.mediator = mediator;
        //将 租房 客户 A 注册到中介中
        mediator.register(this);
    }
    //租客A 发送自己的需求
    @Override
    public void sendMessage() {
        mediator.getMessage(this);
    }
    public String needs() {
        return "需要租两室一厅,本人女,找一名合租者";
    }
}

【5】租客B

public class Colleague_B implements Colleague{
    //组合中介类
    private Mediator mediator;
 
    //将租客B 注册到中介类中
    public Colleague_B(Mediator mediator) {
        this.mediator = mediator;
        mediator.register(this);
    }
    //租客B 发送自己的需求
    @Override
    public void sendMessage() {
        mediator.getMessage(this);
    }
    public String needs() {
        return "需要一套两室一厅,需要一名女士合租";
    }
}

解释器模式

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的表达式(例如:正则表达式等),那么这些问题实例将是该表达式的一些句子,这样就可以用 “编译原理” 中的解释器模式来实现。

解释器模式基本介绍

【1】解释器模式(Interpreter Pattern): 是指给定一个语言(表达式),定义它的文法的一种表示。并定义一个解释器,使用该解释器来解释语言中的句子(表达式)。
【2】在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器。
【3】解释器模式是一种类行为型模式,其主要优点如下: ①、扩展性好,由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。②、容易实现,在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
【4】解释器模式的主要缺点如下: ①、执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。②、会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。③、可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
【5】应用场景: ①、应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树 。②、一些重复出现的问题可以用一种简单的语言来表达。③、一个简单语法需要解释的场景。
【6】这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等。

解释器模式的结构与类图

解释器模式包含以下主要角色:
【1】抽象表达式(Abstract Expression)角色: 定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
【2】终结符表达式(Terminal Expression)角色: 是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
【3】非终结符表达式(Nonterminal Expression)角色: 也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
【4】环境(Context)角色: 通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
【5】客户端(Client): 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

在这里插入图片描述

解释器模式案例分析

【1】抽象表达式: 抽象类表达式,通过 HashMap 键值对, 可以获取到变量的值。

/**
 * 抽象类表达式,通过HashMap 键值对, 可以获取到变量的值
 */
public abstract class Expression {
    // a + b - c
    // 解释公式和数值, key 就是公式(表达式) 参数[a,b,c], value就是就是具体值
    // HashMap {a=10, b=20}
    public abstract int interpreter(HashMap<String, Integer> var);
}

【2】终结符表达式:

public class Calculator {
 
    // 定义表达式
    private Expression expression;
 
    // 构造函数传参,并解析
    public Calculator(String expStr) { // expStr = a+b
        // 安排运算先后顺序
        Stack<Expression> stack = new Stack<>();
        // 表达式拆分成字符数组
        char[] charArray = expStr.toCharArray();// [a, +, b]
 
        Expression left = null;
        Expression right = null;
        //遍历我们的字符数组, 即遍历  [a, +, b]
        //针对不同的情况,做处理
        for (int i = 0; i < charArray.length; i++) {
            switch (charArray[i]) {
            case '+': //
                left = stack.pop();// 从stack取出left => "a"
                right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表达式 "b"
                stack.push(new AddExpression(left, right));// 然后根据得到left 和 right 构建 AddExpresson加入stack
                break;
            case '-': //
                left = stack.pop();
                right = new VarExpression(String.valueOf(charArray[++i]));
                stack.push(new SubExpression(left, right));
                break;
            default:
                //如果是一个 Var 就创建要给 VarExpression 对象,并push到 stack
                stack.push(new VarExpression(String.valueOf(charArray[i])));
                break;
            }
        }
        //当遍历完整个 charArray 数组后,stack 就得到最后Expression
        this.expression = stack.pop();
    }
 
    public int run(HashMap<String, Integer> var) {
        //最后将表达式a+b和 var = {a=10,b=20}
        //然后传递给expression的interpreter进行解释执行
        return this.expression.interpreter(var);
    }
}

【3】非终结符表达式(抽象): 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关系。

/**
 * 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关系,
 * 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是Expression类的实现类
 *
 * @author Administrator
 *
 */
public class SymbolExpression extends Expression {
 
    protected Expression left;
    protected Expression right;
 
    public SymbolExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }
 
    //因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
    @Override
    public int interpreter(HashMap<String, Integer> var) {
        // TODO Auto-generated method stub
        return 0;
    }
}

【4】非终结符表达式(实现一):

// 加法解释器
public class AddExpression extends SymbolExpression  {
 
    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }
 
    //处理相加
    //var 仍然是 {a=10,b=20}..
    //一会我们debug 源码,就ok
    public int interpreter(HashMap<String, Integer> var) {
        //super.left.interpreter(var) : 返回 left 表达式对应的值 a = 10
        //super.right.interpreter(var): 返回right 表达式对应值 b = 20
        return super.left.interpreter(var) + super.right.interpreter(var);
    }
}

【5】非终结符表达式(实现二):

public class SubExpression extends SymbolExpression {
 
    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }
 
    //求出left 和 right 表达式相减后的结果
    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }
}

【6】客户端:

public class ClientTest {
 
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        String expStr = getExpStr(); // a+b
        HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
        Calculator calculator = new Calculator(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<String, Integer> getValue(String expStr) throws IOException {
        HashMap<String, Integer> 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;
    }
}

责任链模式

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工差旅费的报销,可审批的领导有部门负责人、副总经理、总经理等,但每个领导能批准的金额不同,员工必须根据自己要批准的金额去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。

在计算机软硬件中也有相关例子,例如异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些,如果用责任链模式都能很好解决。

TIP

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

模式的结构

职责链模式主要包含以下角色:
抽象处理者(Handler)角色: 定义一个处理请求的抽象类或接口,包含抽象处理方法和自己(传入下一个子类时赋值)。
具体处理者(Concrete Handler)角色: 实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
请求对象(Request): 包含很多请求属性。
客户类(Client)角色: 创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。

在这里插入图片描述

职责链模式的应用案例

学校 OA 系统的采购审批项目:采购员采购教学器材费用审批规则如下:
 ■ 如果金额 小于等于 10000,有学校主任审批;
 ■ 如果金额 小于等于 30000,由院长审批;
 ■ 如果金额 大于 30000,由校长审批;

【1】请求者类(普通类): 包含两个属性(编号:id 和价格:price)

public class Request {
        //请求编号
private int id;
        //请求价格
private float price;
/**
 * @param id
 * @param price
 */
public Request(int id, float price) {
super();
this.id = id;
this.price = price;
}
public int getId() {
return id;
}
public float getPrice() {
return price;
}
}

【2】 抽象处理者(Handler)类:特点是包含了一个自己的属性,有点像装饰者模式。用于传入自己的子类,通过 set方法。还有一个抽象的业务处理方法 process。

public abstract class Handler {
//组合一个自己,并创建一个 set 方法,由子类使用
Handler handler;
//处理者的名称
String name;

/**
 * @param name
 * 构造器,传入名称即可
 */
public Handler(String name) {
super();
this.name = name;
}

public void setHandler(Handler handler) {
this.handler = handler;
}

//业务处理方法,抽象的
public abstract void process(Request request);
}

【3】具体处理者(Concrete Handler)类:主任审批类。存在多个相似的类,都继承自抽象处理者。实现抽象的 process 方法,如果能处理则处理,否则抛给其他子类。

public class CollegeHandler extends Handler{

private static final float PRICE_1 = 10000f;

public CollegeHandler(String name) {
super(name);
}

//如果金额 小于等于 10000,有学校主任审批
@Override
public void process(Request request) {
if(request.getPrice() <= PRICE_1) {
System.out.println("请求编号: "+request.getId()+"被"+name + "处理");
}else {
handler.process(request);
}
}

}

【3.1】 具体处理者(Concrete Handler)类:院长审批。与上述的住人审批基本相同,区别就是业务处理方法 process 的处理条件不同而已。处理不了就抛给父类的 Handler 对象。校长审批与之类似省略。。。

public class DepartmentHandler extends Handler{

private static final float PRICE_1 = 10000f;
private static final float PRICE_3 = 30000f;

public DepartmentHandler(String name) {
super(name);
}

//如果金额 小于等于 30000,由院长审批;
@Override
public void process(Request request) {
if(request.getPrice() >  PRICE_1 && request.getPrice() <=  PRICE_3) {
System.out.println("请求编号: "+request.getId()+"被"+name + "处理");
}else {
handler.process(request);
}
}
}

【4】客户类(Client)类:将请求对象和处理类进行关联。特点是将多个处理类的关系进行了链式组装。代码如下:

public class Client {
public static void main(String[] args) {
//创建请求对象
Request request = new Request(1, 300001);
//创建各个处理者
DepartmentHandler departmentHandler = new DepartmentHandler("学校主任");
CollegeHandler collegeHandler = new CollegeHandler("院长");
SchoolMasterHandler schoolMasterHandler = new SchoolMasterHandler("校长");
//对处理者的链进行组装(注意:这里组装成一个环形)
//可能有人认为,会不会出现死循环,这个应该是业务代码考虑的问题。如果有不能处理的情况应该自行抛出错误
departmentHandler.setHandler(collegeHandler);
collegeHandler.setHandler(schoolMasterHandler);
schoolMasterHandler.setHandler(departmentHandler);

//可以随便调用一个处理者来处理对象
collegeHandler.process(request);
departmentHandler.process(request);
/** 输出展示:请求编号: 1被校长处理
 *  请求编号: 1被校长处理
 */
}
}

职责链模式的特点

职责链模式的优点:
1)降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
2)增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
3)增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
4)责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
5)责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

职责链模式的缺点:
1)、不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
2)、对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
3)、职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

模式的应用场景

前边已经讲述了关于责任链模式的结构与特点,下面介绍其应用场景,责任链模式通常在以下几种情况使用。
【1】有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定。
【2】可动态指定一组对象处理请求,或添加新的处理者。
【3】在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。

模式的扩展

职责链模式存在以下两种情况:
【1】纯的职责链模式: 一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。也就是上面的例子。
【2】不纯的职责链模式: 允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。在上面的else中添加部分的业务逻辑处理。

源码分析

职责链模式在 SpringMVC 框架应用中有使用。接下来就对 SpringMVC 的HandleExceptionChain 类进行分析。

在这里插入图片描述

上述图和下述代码说明:SpringMVC 请求的流程图中,执行了拦截器方法 intercepto.preHandler 等等。在处理 SpringMVC 请求时,使用到了职责链模式和适配器模式。HandlerExecutionChain 主要负责的是请求拦截器的执行和请求处理,但是它本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链的实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程。HandlerExecutionChain 维护了 HandlerInterceptor 的集合,可以向其中注册相应的拦截器。

public class DispatcherServlet extends FrameworkServlet {
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerExecutionChain mappedHandler = null;
//获取到 HandlerExecutionChain 对象
mappedHandler = getHandler(processedRequest);

//...... 
//重点:如果执行成功则,直接返回,否则执行下一个 Handler
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

//上述 applyPreHandle 方法的实现
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (getInterceptors() != null) {
for (int i = 0; i < getInterceptors().length; i++) {
HandlerInterceptor interceptor = getInterceptors()[i];
//当 preHandle 链不能处理时,调用 triggerAfterCompletion 链
if (!interceptor.preHandle(request, response, this.handler)) {
//triggerAfterCompletion 内部也是获取所有的拦截器,并调用 afterCompletion
triggerAfterCompletion(request, response, null);
return false;
}
this.interceptorIndex = i;
}
}
return true;
}

//当 handler 不符合 PreHandler 链时,执行 PostHandler 链
mappedHandler.applyPostHandle(processedRequest, response, mv);
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) throws Exception {
for (int i = getInterceptors().length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}

//上述 triggerAfterCompletion 的源码
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, Exception ex)
throws Exception {

for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = getInterceptors()[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}

原文地址:https://blog.csdn.net/qq_45477639/article/details/140621285

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!