GoF23 - 设计模式小结

OOP 七大原则

  • 开闭原则

对扩展开放,对修改关闭。

  • 里氏替换原则

继承必须确保超类(父类)所拥有的性质在子类中仍然成立。

  • 依赖倒置原则

要面向接口编程,而不是面向实现编程。

  • 单一职责原则

控制类的粒度大小、将对象解耦、提高其内聚性。

  • 接口隔离原则

要为每个类建立它们需要的专用接口。

  • 迪米特法则

解耦,只与你的直接朋友交谈,不跟“陌生人”说话。

  • 合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。

奖励的发放策略

假设现在要做一个营销,需要用户参与一个活动,然后完成一系列的任务,最后可以得到一些奖励作为回报。活动的奖励包含美团外卖、酒旅和美食等多种品类券,现在需要你帮忙设计一套奖励发放方案。

原始 Demo:

// 奖励服务
class RewardService {
    // 外部服务
    private WaimaiService waimaiService;
    private HotelService hotelService;
    private FoodService foodService;
    
    // 使用对入参的条件判断进行发奖
    public void issueReward(String rewardType, Object ... params) {
        if ("Waimai".equals(rewardType)) {
            WaimaiRequest request = new WaimaiRequest();
            // 构建入参
            request.setWaimaiReq(params);
            waimaiService.issueWaimai(request);
        } else if ("Hotel".equals(rewardType)) {
            HotelRequest request = new HotelRequest();
            request.addHotelReq(params);
            hotelService.sendPrize(request);
        } else if ("Food".equals(rewardType)) {
            FoodRequest request = new FoodRequest(params);
            foodService.getCoupon(request);
        } else {
           throw new IllegalArgumentException("rewardType error!");
        }
    }
}

存在问题:

  • 假如即将接入新的打车券,这是否意味着必须要修改这部分代码;
  • 假如后面美团外卖的发券接口发生了改变或者替换,这段逻辑是否必须要同步进行修改;
  • 这段代码有两个主要问题,一是不符合开闭原则,可以预见,如果后续新增品类券的话,需要直接修改主干代码,而我们提倡代码应该是对修改封闭的;二是不符合迪米特法则,发奖逻辑和各个下游接口高度耦合,这导致接口的改变将直接影响到代码的组织,使得代码的可维护性降低

使用策略模式 & 适配器模式来优化

  • 策略模式

    • 定义了一系列的算法,并将每一个算法封装起来,使它们可以相互替换;

    • 策略模式通常包含以下角色:

      • 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现;
      • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现;
      • 环境(Context)类:持有一个策略类的引用,最终给客户端调用
  • 适配器模式

    • 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作;

    • 适配器模式包含以下主要角色:

      • 目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口;
      • 适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口;
      • 适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者

优化 Demo:

// 策略接口
interface Strategy {
    void issue(Object ... params);
}

// 外卖策略
class Waimai implements Strategy {
    private WaimaiService waimaiService;

    @Override
    public void issue(Object... params) {
        WaimaiRequest request = new WaimaiRequest();

        // 构建入参
        request.setWaimaiReq(params);
        waimaiService.issueWaimai(request);
    }
}

// 酒旅策略
class Hotel implements Strategy {
    private HotelService hotelService;

    @Override
    public void issue(Object... params) {
        HotelRequest request = new HotelRequest();

        request.addHotelReq(params);
        hotelService.sendPrize(request);
    }
}

// 美食策略
class Food implements Strategy {
    private FoodService foodService;
    
    @Override
    public void issue(Object... params) {
        FoodRequest request = new FoodRequest(params);
        foodService.payCoupon(request);
    }
}

// 使用分支判断获取的策略上下文
class StrategyContext {
    public static Strategy getStrategy(String rewardType) {
        switch (rewardType) {
            case "Waimai":
                return new Waimai();
            case "Hotel":
                return new Hotel();
            case "Food":
                return new Food();
            default:
                throw new IllegalArgumentException("rewardType error!");
        }
    }
}

// 优化后的策略服务
class RewardService {
    public void issueReward(String rewardType, Object ... params) {
        Strategy strategy = StrategyContext.getStrategy(rewardType);
        strategy.issue(params);
    }
}

代码耦合度降低,但仍存在以下问题:

  • 策略类是有状态的模型吗?如果不是是否可以考虑做成单例的;
  • 环境类的获取策略方法职责很明确,但是依然没有做到完全对修改封闭

使用单例模式来优化

  • 单例模式

    • 创建型模式,它提供了一种创建对象的最佳方式;
    • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
// 策略接口
interface Strategy {
    void issue(Object ... params);
}

// 策略上下文, 用于管理策略的注册和获取
class StrategyContext {
    private static final Map<String, Strategy> registerMap = new HashMap<>();

    // 注册策略
    public static void registerStrategy(String rewardType, Strategy strategy) {
        registerMap.putIfAbsent(rewardType, strategy);
    }

    // 获取策略
    public static Strategy getStrategy(String rewardType) {
        return registerMap.get(rewardType);
    }
}

// 抽象策略类
abstract class AbstractStrategy implements Strategy {
    // 类注册方法
    public void register() {
        StrategyContext.registerStrategy(getClass().getSimpleName(), this);
    }
}

// 单例外卖策略
class Waimai extends AbstractStrategy implements Strategy {
    private static final Waimai instance = new Waimai();
    private WaimaiService waimaiService;

    private Waimai() {
        register();
    }

    public static Waimai getInstance() {
        return instance;
    }

    @Override
    public void issue(Object... params) {
        WaimaiRequest request = new WaimaiRequest();

        // 构建入参
        request.setWaimaiReq(params);
        waimaiService.issueWaimai(request);
    }
}

// 单例酒旅策略
class Hotel extends AbstractStrategy implements Strategy {
    private static final Hotel instance = new Hotel();
    private HotelService hotelService;

    private Hotel() {
        register();
    }

    public static Hotel getInstance() {
        return instance;
    }

    @Override
    public void issue(Object... params) {
        HotelRequest request = new HotelRequest();
        request.addHotelReq(params);
        hotelService.sendPrize(request);
    }
}

// 单例美食策略
class Food extends AbstractStrategy implements Strategy {
    private static final Food instance = new Food();
    private FoodService foodService;

    private Food() {
        register();
    }

    public static Food getInstance() {
        return instance;
    }
    
    @Override
    public void issue(Object... params) {
        FoodRequest request = new FoodRequest(params);
        foodService.payCoupon(request);
    }
}
Strategy strategy = StrategyContext.getStrategy("Waimai"); 
strategy.issue("外卖详情参数"); 

// Waimai waimai = Waimai.getInstance(); 
// waimai.issue("外卖详情参数"); 

GoF23 - 设计模式小结_第1张图片

任务模型的设计

完成任务模型的设计。你需要重点关注状态的流转变更,以及状态变更后的消息通知。

原始 Demo:

// 任务状态枚举
@AllArgsConstructor
@Getter
enum TaskState {
    INIT("初始化"),
    ONGOING( "进行中"),
    PAUSED("暂停中"),
    FINISHED("已完成"),
    EXPIRED("已过期");
    private final String message;
}

// 行为枚举
@AllArgsConstructor
@Getter
enum ActionType {
    START(1, "开始"),
    STOP(2, "暂停"),
    ACHIEVE(3, "完成"),
    EXPIRE(4, "过期");
    private final int code;
    private final String message;
}

class Task {
    private Long taskId;
    // 任务的默认状态为初始化
    private TaskState state = TaskState.INIT;
    // 活动服务
    private ActivityService activityService;
    // 任务管理器
    private TaskManager taskManager;

    // 使用条件分支进行任务更新
    public void updateState(ActionType actionType) {
        if (state == TaskState.INIT) {
            if (actionType == ActionType.START) {
                state = TaskState.ONGOING;
            }
        } else if (state == TaskState.ONGOING) {
            if (actionType == ActionType.ACHIEVE) {
                state = TaskState.FINISHED;
                
                // 任务完成后进对外部服务进行通知
                activityService.notifyFinished(taskId);
                taskManager.release(taskId);
            } else if (actionType == ActionType.STOP) {
                state = TaskState.PAUSED;
            } else if (actionType == ActionType.EXPIRE) {
                state = TaskState.EXPIRED;
            }
        } else if (state == TaskState.PAUSED) {
            if (actionType == ActionType.START) {
                state = TaskState.ONGOING;
            } else if (actionType == ActionType.EXPIRE) {
                state = TaskState.EXPIRED;
            }
        }
    }
}

存在问题:

  • 方法中使用条件判断来控制语句,但是当条件复杂或者状态太多时,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了开闭原则,不利于程序的扩展;
  • 任务类不够高内聚,它在通知实现中感知了其他领域或模块的模型,如活动和任务管理器,这样代码的耦合度太高,不利于扩展

使用状态模式 & 观察者模式来优化

  • 状态模式

    • 对有状态的对象,把复杂的「判断逻辑」提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为;

    • 状态模式包含以下主要角色:

      • 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换;
      • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为;
      • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换
  • 观察者模式

    • 指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作「发布-订阅」模式、「模型-视图」模式,它是对象行为型模式;

    • 观察者模式的主要角色如下:

      • 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法;
      • 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象;
      • 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用;
      • 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
// 任务状态枚举
@AllArgsConstructor
@Getter
enum TaskState {
    INIT("初始化"),
    ONGOING( "进行中"),
    PAUSED("暂停中"),
    FINISHED("已完成"),
    EXPIRED("已过期");
    private final String message;
}

// 行为枚举
@AllArgsConstructor
@Getter
enum ActionType {
    START(1, "开始"),
    STOP(2, "暂停"),
    ACHIEVE(3, "完成"),
    EXPIRE(4, "过期");
    private final int code;
    private final String message;
}

// 任务状态抽象接口
interface State {
    // 默认实现, 不做任何处理
    default void update(Task task, ActionType actionType) {
        // do nothing
    }
}

// 任务初始状态
// class TaskInit implements State {
//     @Override
//     public void update(Task task, ActionType actionType) {
//         if  (actionType == ActionType.START) {
//             task.setState(new TaskOngoing());
//         }
//     }
// }
class TaskInit implements State {
    @Override
    public void update(Task task, ActionType actionType) {
        if (actionType == ActionType.START) {
            TaskOngoing taskOngoing = new TaskOngoing();
            taskOngoing.add(new ActivityObserver());
            taskOngoing.add(new TaskManageObserver());
            task.setState(taskOngoing);
        }
    }
}

// 任务进行状态
// class TaskOngoing implements State {
//     private ActivityService activityService;
//     private TaskManager taskManager;

//     @Override
//     public void update(Task task, ActionType actionType) {
//         if (actionType == ActionType.ACHIEVE) {
//             task.setState(new TaskFinished());

//             // 通知
//             activityService.notifyFinished(taskId);
//             taskManager.release(taskId);
//         } else if (actionType == ActionType.STOP) {
//             task.setState(new TaskPaused());
//         } else if (actionType == ActionType.EXPIRE) {
//             task.setState(new TaskExpired());
//         }
//     }
// }
class TaskOngoing extends Subject implements State {
    @Override
    public void update(Task task, ActionType actionType) {
        if (actionType == ActionType.ACHIEVE) {
            task.setState(new TaskFinished());

            // 通知
            notifyObserver(task.getTaskId());
        } else if (actionType == ActionType.STOP) {
            task.setState(new TaskPaused());
        } else if (actionType == ActionType.EXPIRE) {
            task.setState(new TaskExpired());
        }
    }
}

// 任务暂停状态
class TaskPaused implements State {
    @Override
    public void update(Task task, ActionType actionType) {
        if (actionType == ActionType.START) {
            task.setState(new TaskOngoing());
        } else if (actionType == ActionType.EXPIRE) {
            task.setState(new TaskExpired());
        }
    }
}

// 任务完成状态
class TaskFinished implements State {
    // ...
}

// 任务过期状态
class TaskExpired implements State {
    // ...
}

@Data
class Task {
    private Long taskId;
    // 初始化为初始态
    private State state = new TaskInit();

    // 更新状态
    public void updateState(ActionType actionType) {
        state.update(this, actionType);
    }
}

// 抽象观察者
interface Observer {
    // 反应
    void response(Long taskId);
}

// 抽象目标
abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();

    // 增加观察者方法
    public void add(Observer observer) {
        observers.add(observer);
    }

    // 删除观察者方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }

    // 通知观察者方法
    public void notifyObserver(Long taskId) {
        for (Observer observer : observers) {
            observer.response(taskId);
        }
    }
}

// 活动观察者
class ActivityObserver implements Observer {
    private ActivityService activityService;

    @Override
    public void response(Long taskId) {
        activityService.notifyFinished(taskId);
    }
}

// 任务管理观察者
class TaskManageObserver implements Observer {
    private TaskManager taskManager;

    @Override
    public void response(Long taskId) {
        taskManager.release(taskId);
    }
}

GoF23 - 设计模式小结_第2张图片

活动的迭代重构

之前设计开发了活动模型,现在我们需要在任务型活动的参与方法上增加一层风险控制。

原始 Demo:

// 抽象活动接口
interface ActivityInterface {
   void participate(Long userId);
}

// 活动类
class Activity implements ActivityInterface {
    private String type;
    private Long id;
    private String name;
    private Integer scene;
    private String material;
      
    public Activity(String type) {
        this.type = type;

        // id的构建部分依赖于活动的type
        if ("period".equals(type)) {
            id = 0L;
        }
    }

    public Activity(String type, Long id) {
        this.type = type;
        this.id = id;
    }

    public Activity(String type, Long id, Integer scene) {
        this.type = type;
        this.id = id;
        this.scene = scene;
    }

    public Activity(String type, String name, Integer scene, String material) {
        this.type = type;
        this.scene = scene;
        this.material = material;

        // name的构建完全依赖于活动的type
        if ("period".equals(type)) {
            this.id = 0L;
            this.name = "period" + name;
        } else {
            this.name = "normal" + name;
        }
    }

    // 参与活动
    @Override
    public void participate(Long userId) {
        // do nothing
    }
}

// 任务型活动
class TaskActivity extends Activity {
    private Task task;

    public TaskActivity(String type, String name, Integer scene, String material, Task task) {
        super(type, name, scene, material);
        this.task = task;
    }
    
    // 参与任务型活动
    @Override
    public void participate(Long userId) {
        // 更新任务状态为进行中
        task.getState().update(task, ActionType.START);
    }
}

使用建造者模式来优化

  • 建造者模式

    • 指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的;

    • 建造者模式的主要角色如下:

      • 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件;
      • 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult();
      • 具体建造者(Concrete Builder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法;
      • 指挥者(Director):它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息
  • 装饰器模式

    • 指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式;

    • 装饰器模式主要包含以下角色:

      • 抽象构件(Component)角色:定义一个抽象接口以规范准备接收附加责任的对象;
      • 具体构件(ConcreteComponent)角色:实现抽象构件,通过装饰角色为其添加一些职责;
      • 抽象装饰(Decorator)角色:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能;
      • 具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任
// 抽象活动接口
interface ActivityInterface {
   void participate(Long userId);
}

// 活动类
class Activity implements ActivityInterface {
    protected String type;
    protected Long id;
    protected String name;
    protected Integer scene;
    protected String material;

    // 全参构造函数
    public Activity(String type, Long id, String name, Integer scene, String material) {
        this.type = type;
        this.id = id;
        this.name = name;
        this.scene = scene;
        this.material = material;
    }

    @Override
    public void participate(Long userId) {
        // do nothing
    }

    // 静态建造器类, 使用奇异递归模板模式允许继承并返回继承建造器类
    public static class Builder<T extends Builder<T>> {
        protected String type;
        protected Long id;
        protected String name;
        protected Integer scene;
        protected String material;

        public T setType(String type) {
            this.type = type;
            return (T) this;
        }

        public T setId(Long id) {
            this.id = id;
            return (T) this;
        }

        public T setId() {
            if ("period".equals(this.type)) {
                this.id = 0L;
            }

            return (T) this;
        }

        public T setScene(Integer scene) {
            this.scene = scene;
            return (T) this;
        }

        public T setMaterial(String material) {
            this.material = material;
            return (T) this;
        }

        public T setName(String name) {
            if ("period".equals(this.type)) {
                this.name = "period" + name;
            } else {
                this.name = "normal" + name;
            }

            return (T) this;
        }

        public Activity build(){
            return new Activity(type, id, name, scene, material);
        }
    }
}

// 任务型活动
class TaskActivity extends Activity {
    protected Task task;

    // 全参构造函数
    public TaskActivity(String type, Long id, String name, Integer scene, String material, Task task) {
        super(type, id, name, scene, material);
        this.task = task;
    }

    // 参与任务型活动
    @Override
    public void participate(Long userId) {
        // 更新任务状态为进行中
        task.getState().update(task, ActionType.START);
    }
    // public void participate(Long userId) {
    //     // 对目标用户做风险控制, 失败则抛出异常
    //     Risk.doControl(userId);
    //     // 更新任务状态为进行中
    //     task.state.update(task, ActionType.START);
    // }

    // 继承建造器类
    public static class Builder extends Activity.Builder<Builder> {
        private Task task;

        public Builder setTask(Task task) {
            this.task = task;
            return this;
        }

        public TaskActivity build(){
            return new TaskActivity(type, id, name, scene, material, task);
        }
    }
}

// 抽象装饰角色
abstract class ActivityDecorator implements ActivityInterface {
    protected ActivityInterface activity;

    public ActivityDecorator(ActivityInterface activity) {
        this.activity = activity;
    }

    public abstract void participate(Long userId);
}

// 能够对活动做风险控制的包装类
class RiskControlDecorator extends ActivityDecorator {
    public RiskControlDecorator(ActivityInterface activity) {
        super(activity);
    }

    @Override
    public void participate(Long userId) {
        // 对目标用户做风险控制,失败则抛出异常
        Risk.doControl(userId);
        // 更新任务状态为进行中
        activity.participate(userId);
    }
}
TaskActivity taskActivity = new TaskActivity( /* ... */ );
// 对TaskActivity对象进行风险控制包装
ActivityInterface wrappedTaskActivity = new RiskControlDecorator(taskActivity);
wrappedTaskActivity.participate(userId);

GoF23 - 设计模式小结_第3张图片

参考资料

  • 美团技术团队公众号:https://mp.weixin.qq.com/s/H2toewJKEwq1mXme_iMWkA

你可能感兴趣的:(杂论,设计模式)