【设计模式】- 行为型模式1

模板方法模式

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

主要角色】:

  • 抽象类:给出一个算法的轮廓和骨架(包括一个模板方法 和 若干基本方法)
    • 模板方法:定义了算法的骨架(执行顺序),按某种顺序调用其包含的基本方法
    • 基本方法:实现算法各个步骤的方法
      • 抽象方法:由抽象类声明,具体子类实现
      • 具体方法:由抽象类或具体类声明或实现,子类可以覆盖也可以继承
      • 钩子方法:在抽象类中已经实现,包含用于判断的逻辑方法和需要子类重写的空方法(一般是用于判断的逻辑方法,方法名为isXxx,返回值为boolean类型)
  • 具体子类:实现抽象类中所定义的抽象方法和钩子方法

案例:炒菜

需求】:炒菜的步骤是固定的,分为倒油、热油、倒蔬菜、倒调料品、翻炒等步骤。

抽象类(定义模板方法和基本方法):

java 复制代码
public abstract class AbstractClass {
    // 模板方法定义(子类不能修改模板方法)
    public final void cookProcess() {
        pourOil();
        heatOil();
        pourVegetable();
        pourSauce();
        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("翻炒");
    }
}

炒包菜类、炒菜心类(具体子类):

java 复制代码
public class ConcreteClassBaoCai extends AbstractClass {
    @Override
    public void pourVegetable() {
        System.out.println("下锅的蔬菜是包菜");
    }

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

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

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

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建对象
        AbstractClass baoCai = new ConcreteClassBaoCai();
        // 调用炒菜功能
        baoCai.cookProcess();
    }
}

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

适用场景

  1. 算法的整体部分比较固定,个别部分容易改变,就可以使用模板方法,将容易变化的部分抽象出来,让子类去实现这些方法。
  2. 通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

策略模式

该模式定义了一系列算法, 使他们可以互相替换,算法的变化不会影响使用算法的客户。

主要角色】:

  • 抽象策略类:由一个接口或抽象类实现,给出所有具体策略所需的接口
  • 具体策略类:实现了抽象策略类定义的接口,提供具体的算法实现或行为
  • 环境类:持有一个策略类的引用,最终给客户端调用

案例:促销活动

需求】:超市争对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员(环境类)将促销活动展示给客户。

抽象策略类:

java 复制代码
public interface Strategy {
    void show();
}

策略A、B、C类(具体策略类):

java 复制代码
public class StrategyA implements Strategy {
    @Override
    public void show() {
        System.out.println("买一送一");
    }
}

public class StrategyB implements Strategy {
    @Override
    public void show() {
        System.out.println("满200减50");
    }
}

public class StrategyC implements Strategy {
    @Override
    public void show() {
        System.out.println("打8折");
    }
}

销售员类(环境类):

java 复制代码
public class SalesMan {
    private Strategy strategy;

    public SalesMan(Strategy strategy) {
        this.strategy = strategy;
    }

    // 由促销员展示促销活动给普通用户
    public void salesManShow() {
        strategy.show();
    }
}

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 切换策略A
        SalesMan salesMan = new SalesMan(new StrategyA());
        salesMan.salesManShow();
        // 切换策略B
        salesMan = new SalesMan(new StrategyB());
        salesMan.salesManShow();
        // 切换策略C
        salesMan = new SalesMan(new StrategyC());
        salesMan.salesManShow();
    }
}

策略类之间可以自由切换;

增加一个新的策略只需要添加一个具体的策略类

策略模式可以避免使用多重的if else 和 switch case

适用场景

  1. 需要动态在几种算法中选择一种,可以把每个算法封装到策略类中
  2. 大量的if else和switch cash完全可以用策略模式代替,让代码更加美观优雅

命令模式

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

主要角色

  • 抽象命令角色:定义命令的接口,声明执行方法
  • 具体命令角色:实现命令接口,通常会持有接收者,并调用接收者的功能来完成命令要执行的操作
  • 实现者 / 接收者角色:真正执行命令的对象
  • 调用者 / 请求者角色:要求命令对象执行请求,通常会持有命令对象

案例:点餐

  • 服务员:调用者角色,由服务员发起命令
  • 厨师:接收者命令,真正命令执行的对象
  • 订单:命令中包含订单

订单类:

java 复制代码
@Data
public class Order {
    // 餐桌号码
    private int diningTable;
    // 所下餐品和份数
    private Map<String, Integer> foodDir = new HashMap<>();
    // 添加食物
    public void addFood(String name, int num) {
        foodDir.put(name, num);
    }
}

厨师类(接收者):

java 复制代码
public class Chef {
    // 制作食物
    public void makeFood(String name, int num) {
        System.out.println(num + "份" + name);
    }
}

抽象命令类:

java 复制代码
public interface Command {
    void execute();
}

具体命令类:

java 复制代码
public class OrderCommand implements Command {
    // 持有接收者对象
    private Chef receiver;
    
    private Order order;
    public OrderCommand(Chef receiver, Order order) {
        this.receiver = receiver;
        this.order = order;
    }
    @Override
    public void execute() {
        System.out.println(order.getDiningTable() + "桌的订单:");
        Map<String, Integer> foodDir = order.getFoodDir();
        foodDir.forEach((foodName, num)->{
            receiver.makeFood(foodName, num); // 让厨师去完成订单里的菜品
        });
        System.out.println(order.getDiningTable() + "桌的饭准备完毕");
    }
}

服务员类(调用者角色):

java 复制代码
public class Waitor {
    // 持有多个命令对象
    private List<Command> commands = new ArrayList<>();
    public void addCommand(Command command) {
        // 将command存储到List集合中
        commands.add(command);
    }
    // 发起命令
    public void orderUp() {
        System.out.println("服务员:厨师,订单来啦");
        commands.forEach(command -> {
            if(command != null) command.execute();
        });
    }
}

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建第一个订单对象
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.addFood("西红柿鸡蛋面", 1);
        order1.addFood("小杯可乐", 2);
        // 创建第二个订单对象
        Order order2 = new Order();
        order2.setDiningTable(2);
        order2.addFood("油闷大虾", 1);
        order2.addFood("小杯雪碧", 1);
        // 创建厨师对象
        Chef receiver = new Chef();
        // 创建命令对象
        OrderCommand cmd1 = new OrderCommand(receiver, order1);
        OrderCommand cmd2 = new OrderCommand(receiver, order2);
        // 创建调用者
        Waitor invoke = new Waitor();
        invoke.addCommand(cmd1);
        invoke.addCommand(cmd2);
        // 让服务员发起命令
        invoke.orderUp();
    }
}

适用场景

  1. 系统需要将请求调用这和请求接收者解耦
  2. 系统需要在不同时间指定请求、将请求排队和执行请求
  3. 系统需要支持命令的撤销(undo)和恢复(redo)操作

JDK源码解析:Runnable类

Runnable类就是一个命令模式,Thread是调用者,start()方法就是执行方法

责任链模式

避免发送者和多个请求处理者耦合在一起,将所有请求的处理者通过钱一个对象记住其下一个对象的引用而连成一条链;当请求发生时,可以将请求沿着这条链传递,直到有对象处理它为止

问题】公司员工请假,可以批假的领导有:部门负责人、副总经理、总经理,但是每个领导可以批准的天数不同。员工需要根据自己要请假的天数去找不同的领导签字。

解决】:比如有个员工要请假,他只需要去找部门负责人,如果部门负责人发现他请假的时间自己处理不了,就会把这个请假单交给副总经理,如果副总经理处理不了,就会交给总经理处理。

主要角色】:

  • 抽象处理者角色:定义一个处理请求的接口,包含抽象处理方法和后继处理方法
  • 具体处理者角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理就处理;如果不能处理就把它转发给后继的处理者
  • 客户类角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程

案例:请假流程

需求】:请假一天以下的只需要小组长同意;请假1-3天需要部门经理同意;请假3-7天需要总经理同意。

请假条类:

java 复制代码
@AllArgsConstructor
@Data
public class LeaveRequest {
    // 姓名
    private String name;
    // 请假天数
    private int num;
    // 请假内容
    private String content;
}

抽象处理者类:

java 复制代码
@Data
public abstract class Handler {
    protected final static int NUM_ONE = 1;
    protected final static int NUM_THREE = 3;
    protected final static int NUM_SEVEN = 7;

    // 领导了可以处理的天数区间
    private int numStart;
    private int numEnd;

    public Handler(int numStart, int numEnd) {
        this.numStart = numStart;
        this.numEnd = numEnd;
    }

    // 声明后继者(上级领导)
    private Handler nextHandler;

    // 各级领导处理请假条的方法
    protected abstract void handleLeave(LeaveRequest leave);

    // 提交请假条(不能被继承)
    public final void submit(LeaveRequest leave) {
        // 该领导审批
        handleLeave(leave);
        if(nextHandler != null && leave.getNum() > numEnd) {
            // 交给上级领导审批
            nextHandler.handleLeave(leave);
        } else {
            System.out.println("流程结束");
        }
    }
}

小组长类、部门经理类、总经理类(具体的处理者)

java 复制代码
public class GroupLeader extends Handler {
    public GroupLeader() {
        super(0, Handler.NUM_ONE);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天,原因:" + leave.getContent());
        System.out.println("小组长审批:同意");
    }
}

public class Manager extends Handler {
    public Manager() {
        super(Handler.NUM_ONE, Handler.NUM_THREE);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天,原因:" + leave.getContent());
        System.out.println("部门经理审批:同意");
    }
}

public class GeneralManager extends Handler {
    public GeneralManager() {
        super(Handler.NUM_THREE, Handler.NUM_SEVEN);
    }

    @Override
    protected void handleLeave(LeaveRequest leave) {
        System.out.println(leave.getName() + "请假" + leave.getNum() + "天,原因:" + leave.getContent());
        System.out.println("总经理类审批:同意");
    }
}

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 1. 创建请假条对象
        LeaveRequest leave = new LeaveRequest("小明", 1, "身体不适");
        // 2. 创建各级领导对象
        GroupLeader groupLeader = new GroupLeader();
        Manager manager = new Manager();
        GeneralManager generalManager = new GeneralManager();
        // 3. 设置处理者链
        groupLeader.setNextHandler(manager);
        manager.setNextHandler(generalManager);
        // 4. 小明提交请假
        groupLeader.submit(leave); // 调用小组长里的submit()方法
    }
}

降低了请求发送者 和 请求接收者的耦合度

每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成

状态模式

案例引入:电梯

电梯接口:

java 复制代码
public interface ILift {
    // 电梯状态常量
    int OPENING_STATE = 1;
    int CLOSING_STATE = 2;
    int RUNNING_STATE = 3;
    int STOPPING_STATE = 4;

    // 设置电梯状态
    void settState(int state);
    // 电梯操作功能
    void open(); // 开门
    void close(); // 关门
    void run(); // 运行
    void stop(); // 停止
}

电梯类(ILift的子实现类):

java 复制代码
public class Lift implements ILift {
    // 记录当前电梯的状态
    private int state;

    @Override
    public void settState(int state) {
        this.state = state;
    }

    @Override
    public void open() {
        if(state == STOPPING_STATE || state == CLOSING_STATE) {
            System.out.println("电梯打开");
            settState(OPENING_STATE);
        }
    }

    @Override
    public void close() {
        if(state == OPENING_STATE) {
            System.out.println("电梯关闭");
            settState(CLOSING_STATE);
        }
    }

    @Override
    public void run() {
        if(state == CLOSING_STATE || state == STOPPING_STATE) {
            System.out.println("电梯运行");
            settState(RUNNING_STATE);
        }
    }

    @Override
    public void stop() {
        if(state == CLOSING_STATE || state == RUNNING_STATE) {
            System.out.println("电梯停止");
            settState(STOPPING_STATE);
        }
    }
}

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 1. 创建电梯对象
        Lift lift = new Lift();
        // 2. 设置电梯状态
        lift.settState(ILift.OPENING_STATE);
        // 3. 操作电梯
        lift.open();
        lift.close();
        lift.run();
        lift.stop();
    }
}

存在问题】使用了大量的if else,使得阅读性变差。而且如果需要新增一个断电的状态,也需要修改上边的代码逻辑

状态模式:对于有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在内部状态发生改变时改变其行为。

主要角色

  • 环境角色:定义了客户程序需要的接口,维护一个当前的状态,并将与状态相关的操作委托给当前状态对象来处理
  • 义一个接口,用来封装环境对象中特定状态所对应的行为
  • 具体状态角色:实现抽象状态所对应的行为

案例:电梯

抽象状态类:

java 复制代码
@Data
public abstract class LiftState {
    // 环境角色变量
    protected Context context;
    // 电梯开启
    public abstract void open();
    // 电梯关闭
    public abstract void close();
    // 电梯运行
    public abstract void run();
    // 电梯停止
    public abstract void stop();
}

环境角色类:

java 复制代码
public class Context {
    // 定义对应状态的常量
    public final static OpeningState OPENING_STATE = new OpeningState();
    public final static ClosingState CLOSING_STATE = new ClosingState();
    public final static RunningState RUNNING_STATE = new RunningState();
    public final static StoppingState STOPPING_STATE = new StoppingState();

    // 定义一个当前状态变量
    private LiftState liftState;

    public LiftState getLiftState() {
        return liftState;
    }

    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        // 设置当前对象的Context对象
        this.liftState.setContext(this);
    }

    public void open() {
        this.liftState.open();
    }

    public void close() {
        this.liftState.close();
    }

    public void run() {
        this.liftState.run();
    }

    public void stop() {
        this.liftState.stop();
    }
}

电梯关闭、开启、运行、停止状态类(具体状态类):

java 复制代码
public class ClosingState extends LiftState {
    @Override
    public void open() {
        super.context.setLiftState(Context.OPENING_STATE);
        super.context.getLiftState().open();
    }

    @Override
    public void close() {
        System.out.println("电梯关闭");
    }

    @Override
    public void run() {
        super.context.setLiftState(Context.RUNNING_STATE);
        super.context.getLiftState().run();
    }

    @Override
    public void stop() {
        super.context.setLiftState(Context.STOPPING_STATE);
        super.context.getLiftState().stop();
    }
}

public class OpeningState extends LiftState {
    // 当前状态要执行的方法
    @Override
    public void open() {
        System.out.println("电梯 开启");
    }

    @Override
    public void close() {
        // 修改状态
        super.context.setLiftState(Context.CLOSING_STATE);
        // 调用当前状态中的close()方法
        super.context.close();
    }

    @Override
    public void run() {
        // 什么都不做
    }

    @Override
    public void stop() {
        // 什么都不做
    }
}

public class RunningState extends LiftState {
    @Override
    public void open() {
        // 什么都不做
    }

    @Override
    public void close() {
        // 什么都不做
    }

    @Override
    public void run() {
        System.out.println("电梯运行");
    }

    @Override
    public void stop() {
        super.context.setLiftState(Context.STOPPING_STATE);
        super.context.stop();
    }
}

public class StoppingState extends LiftState {
    @Override
    public void open() {
        super.context.setLiftState(Context.OPENING_STATE);
        super.context.getLiftState().open();
    }

    @Override
    public void close() {
        super.context.setLiftState(Context.CLOSING_STATE);
        super.context.getLiftState().close();
    }

    @Override
    public void run() {
        super.context.setLiftState(Context.RUNNING_STATE);
        super.context.getLiftState().run();
    }

    @Override
    public void stop() {
        System.out.println("电梯停止");
    }
}

测试类:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 创建环境角色对象
        Context context = new Context();
        // 设置当前电梯状态
        context.setLiftState(new ClosingState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

适用场景

  1. 当一个对象的行为取决于他的状态,并且他必须在运行时根据状态改变他的行为
  2. 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态
相关推荐
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
杨充19 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充19 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充19 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充19 小时前
2.面向对象的特性
设计模式
杨充19 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充19 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充19 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯1 天前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法