设计模式---行为模式(模板方法模式,命令模式,迭代器模式)

此篇博客讲述设计模式中行为模式

视频教程:

【黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)】 https://www.bilibili.com/video/BV1Np4y1z7BU/?p=92&share_source=copy_web&vd_source=b2e9b9ed746acda34f499009647748ed

一,模板方法模式

1.概述

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。

定义:

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。

2.结构

模板方法(Template Method)模式包含以下主要角色:

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

3.案例实现

【例】炒菜

炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。现通过模板方法模式来用代码模拟。类图如下:

代码如下:

AbstractClass.java
package template;

public abstract class AbstractClass {
    
    

    public final void cookProcess() {
    
    
        //第一步:倒油
        this.pourOil();
        //第二步:热油
        this.heatOil();
        //第三步:倒蔬菜
        this.pourVegetable();
        //第四步:倒调味料
        this.pourSauce();
        //第五步:翻炒
        this.fry();
    }

    public void pourOil() {
    
    
        System.out.println("倒油");
    }

    //第二步:热油是一样的,所以直接实现
    public void heatOil() {
    
    
        System.out.println("热油");
    }

    //第三步:倒蔬菜是不一样的(一个下包菜,一个是下菜心)
    public abstract void pourVegetable();

    //第四步:倒调味料是不一样
    public abstract void pourSauce();


    //第五步:翻炒是一样的,所以直接实现
    public void fry(){
    
    
        System.out.println("炒啊炒啊炒到熟啊");
    }
}
Client.java
package template;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //炒手撕包菜
        ConcreteClass_BaoCai baoCai = new ConcreteClass_BaoCai();
        baoCai.cookProcess();

        //炒蒜蓉菜心
        ConcreteClass_CaiXin caiXin = new ConcreteClass_CaiXin();
        caiXin.cookProcess();
    }
}

ConcreteClass_BaoCai.java
package template;

public class ConcreteClass_BaoCai extends AbstractClass {
    
    

    @Override
    public void pourVegetable() {
    
    
        System.out.println("下锅的蔬菜是包菜");
    }

    @Override
    public void pourSauce() {
    
    
        System.out.println("下锅的酱料是辣椒");
    }
}

ConcreteClass_CaiXin.java
package template;

public class ConcreteClass_CaiXin extends AbstractClass {
    
    
    @Override
    public void pourVegetable() {
    
    
        System.out.println("下锅的蔬菜是菜心");
    }

    @Override
    public void pourSauce() {
    
    
        System.out.println("下锅的酱料是蒜蓉");
    }
}

实现效果:

image-20230222154532788

注意:为防止恶意操作,一般模板方法都加上 final 关键词。

4.优缺点

优点:

  • 提高代码复用性

    将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。

  • 实现了反向控制

    通过一个父类调用其子类的操作,通过对子类的具体实现扩展不同的行为,实现了反向控制 ,并符合“开闭原则”。

缺点:

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

5.适用场景

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

6.JDK源码解析

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:

public abstract class InputStream implements Closeable {
    
    
    //抽象方法,要求子类必须重写
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
    
    
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
    
    
        if (b == null) {
    
    
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
    
    
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
    
    
            return 0;
        }

        int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
        if (c == -1) {
    
    
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
    
    
            for (; i < len ; i++) {
    
    
                c = read();
                if (c == -1) {
    
    
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
    
    
        }
        return i;
    }
}

从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。

在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。

总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现。

二,命令模式

1.概述

日常生活中,我们出去吃饭都会遇到下面的场景。

定义:

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。

2.结构

命令模式包含以下主要角色:

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

3.案例实现

将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。

服务员: 就是调用者角色,由她来发起命令。

资深大厨: 就是接收者角色,真正命令执行的对象。

订单: 命令中包含订单。

类图如下:

代码如下:

Client.java
package command;

public class Client {
    
    
    public static void main(String[] args) {
    
    
        //创建2个order
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.getFoodDic().put("西红柿鸡蛋面", 1);
        order1.getFoodDic().put("小杯可乐", 2);

        Order order2 = new Order();
        order2.setDiningTable(3);
        order2.getFoodDic().put("尖椒肉丝盖饭", 1);
        order2.getFoodDic().put("小杯雪碧", 1);

        //创建接收者
        SeniorChef receiver = new SeniorChef();
        //将订单和接收者封装成命令对象
        OrderCommand cmd1 = new OrderCommand(receiver, order1);
        OrderCommand cmd2 = new OrderCommand(receiver, order2);
        //创建调用者 waitor
        Waitor invoker = new Waitor();
        invoker.setCommand(cmd1);
        invoker.setCommand(cmd2);

        //将订单带到柜台 并向厨师喊 订单来了
        invoker.orderUp();
    }
}
Command.java
package command;

import java.util.ArrayList;

public interface Command {
    
    
    void execute();//只需要定义一个统一的执行方法
}
Order.java
package command;

import java.util.HashMap;
import java.util.Map;

public class Order {
    
    
    // 餐桌号码
    private int diningTable;

    // 用来存储餐名并记录份数
    private Map<String, Integer> foodDic = new HashMap<String, Integer>();

    public int getDiningTable() {
    
    
        return diningTable;
    }

    public void setDiningTable(int diningTable) {
    
    
        this.diningTable = diningTable;
    }

    public Map<String, Integer> getFoodDic() {
    
    
        return foodDic;
    }

    public void setFoodDic(String name, int num) {
    
    
        foodDic.put(name,num);
    }
}

OrderCommand.java
package command;
import java.util.Set;

public class OrderCommand implements Command {
    
    

    //持有接受者对象
    private SeniorChef receiver;
    private Order order;

    public OrderCommand(SeniorChef receiver, Order order){
    
    
        this.receiver = receiver;
        this.order = order;
    }

    public void execute()  {
    
    
        System.out.println(order.getDiningTable() + "桌的订单:");
        Set<String> keys = order.getFoodDic().keySet();
        for (String key : keys) {
    
    
            receiver.makeFood(order.getFoodDic().get(key),key);
        }

        try {
    
    
            Thread.sleep(100);//停顿一下 模拟做饭的过程
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }


        System.out.println(order.getDiningTable() + "桌的饭弄好了");
    }
}
SeniorChef.java
package command;

// 资深大厨类 是命令的Receiver
public class SeniorChef {
    
    

    public void makeFood(int num, String foodName) {
    
    
        System.out.println(num + "份" + foodName);
    }
}

Waitor.java
package command;

import java.util.ArrayList;

public class Waitor {
    
    

    private ArrayList<Command> commands;//可以持有很多的命令对象

    public Waitor() {
    
    
        commands = new ArrayList();
    }

    public void setCommand(Command cmd) {
    
    
        commands.add(cmd);
    }

    // 发出命令 喊 订单来了,厨师开始执行
    public void orderUp() {
    
    
        System.out.println("美女服务员:叮咚,大厨,新订单来了.......");
        for (int i = 0; i < commands.size(); i++) {
    
    
            Command cmd = commands.get(i);
            if (cmd != null) {
    
    
                cmd.execute();
            }
        }
    }
}

实现效果:

image-20230222161250269

4.优缺点

1,优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

2,缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 系统结构更加复杂。

5.使用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

6.JDK源码解析

Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法

//命令接口(抽象命令角色)
public interface Runnable {
    
    
	public abstract void run();
}

//调用者
public class Thread implements Runnable {
    
    
    private Runnable target;
    
    public synchronized void start() {
    
    
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        group.add(this);

        boolean started = false;
        try {
    
    
            start0();
            started = true;
        } finally {
    
    
            try {
    
    
                if (!started) {
    
    
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
    
    
            }
        }
    }
    
    private native void start0();
}

会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。

/**
 * jdk Runnable 命令模式
 *		TurnOffThread : 属于具体
 */
public class TurnOffThread implements Runnable{
    
    
     private Receiver receiver;
    
     public TurnOffThread(Receiver receiver) {
    
    
     	this.receiver = receiver;
     }
     public void run() {
    
    
     	receiver.turnOFF();
     }
}
/**
 * 测试类
 */
public class Demo {
    
    
     public static void main(String[] args) {
    
    
         Receiver receiver = new Receiver();
         TurnOffThread turnOffThread = new TurnOffThread(receiver);
         Thread thread = new Thread(turnOffThread);
         thread.start();
     }
}

三,迭代器模式

1.概述

定义:

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

2.结构

迭代器模式主要包含以下角色:

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。

  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例。

  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、next() 等方法。

  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。

3.案例实现

【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:

代码如下:

Student.java
package iterator;

public class Student {
    
    
    private String name;
    private String number;

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public String getNumber() {
    
    
        return number;
    }

    public void setNumber(String number) {
    
    
        this.number = number;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}

定义迭代器接口,声明hasNext、next方法

public interface StudentIterator {
    
    
    boolean hasNext();
    Student next();
}

定义具体的迭代器类,重写所有的抽象方法

public class StudentIteratorImpl implements StudentIterator {
    
    
    private List<Student> list;
    private int position = 0;

    public StudentIteratorImpl(List<Student> list) {
    
    
        this.list = list;
    }

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

    @Override
    public Student next() {
    
    
        Student currentStudent = list.get(position);
        position ++;
        return currentStudent;
    }
}

定义抽象容器类,包含添加元素,删除元素,获取迭代器对象的方法

public interface StudentAggregate {
    
    
    void addStudent(Student student);

    void removeStudent(Student student);

    StudentIterator getStudentIterator();
}

定义具体的容器类,重写所有的方法

public class StudentAggregateImpl implements StudentAggregate {
    
    

    private List<Student> list = new ArrayList<Student>();  // 学生列表

    @Override
    public void addStudent(Student student) {
    
    
        this.list.add(student);
    }

    @Override
    public void removeStudent(Student student) {
    
    
        this.list.remove(student);
    }

    @Override
    public StudentIterator getStudentIterator() {
    
    
        return new StudentIteratorImpl(list);
    }
}

4.优缺点

1,优点:

  • 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足 “开闭原则” 的要求。

2,缺点:

增加了类的个数,这在一定程度上增加了系统的复杂性。

5.使用场景

  • 当需要为聚合对象提供多种遍历方式时。
  • 当需要为遍历不同的聚合结构提供一个统一的接口时。
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。

6.JDK源码解析

迭代器模式在JAVA的很多集合类中被广泛应用,接下来看看JAVA源码中是如何使用迭代器模式的。

List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator(); //list.iterator()方法返回的肯定是Iterator接口的子实现类对象
while (iterator.hasNext()) {
    
    
    System.out.println(iterator.next());
}

看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList举例来说明

  • List:抽象聚合类
  • ArrayList:具体的聚合类
  • Iterator:抽象迭代器
  • list.iterator():返回的是实现了 Iterator 接口的具体迭代器对象

具体的来看看 ArrayList的代码实现

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    
    
    public Iterator<E> iterator() {
    
    
        return new Itr();
    }
    
    private class Itr implements Iterator<E> {
    
    
        int cursor;       // 下一个要返回元素的索引
        int lastRet = -1; // 上一个返回元素的索引
        int expectedModCount = modCount;

        Itr() {
    
    }
		
        //判断是否还有元素
        public boolean hasNext() {
    
    
            return cursor != size;
        }

        //获取下一个元素
        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];
        }
        ...
}

这部分代码还是比较简单,大致就是在 iterator 方法中返回了一个实例化的 Iterator 对象。Itr是一个内部类,它实现了 Iterator 接口并重写了其中的抽象方法。

注意:

​ 当我们在使用JAVA开发的时候,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable并实现其中的iterator()方法使其返回一个 java.util.Iterator 的实现类就可以了。

猜你喜欢

转载自blog.csdn.net/m0_63324772/article/details/129167094
今日推荐