关于设计模式:设计模式二三事

32次阅读

共计 17020 个字符,预计需要花费 43 分钟才能阅读完成。

设计模式是泛滥软件开发人员通过长时间的试错和利用总结进去的,解决特定问题的一系列计划。现行的局部教材在介绍设计模式时,有些会因为案例脱离实际利用场景而令人费解,有些又会因为场景简略而显得有些小题大做。本文会联合在美团金融服务平台设计开发时的教训,结合实际的案例,并采纳“师生对话”这种绝对滑稽的模式去解说三类罕用设计模式的利用。心愿能对想晋升零碎设计能力的同学有所帮忙或启发。

引言

话说这是在程序员世界里一对师徒的对话:

“老师,我最近在写代码时总感觉本人的代码很不优雅,有什么方法能优化吗?”

“嗯,能够思考通过教材零碎学习,从正文、命名、办法和异样等多方面实现整洁代码。”

“然而,我想说的是,我的代码是合乎各种编码标准的,然而从实现上却总是感觉不够简洁,而且总是须要重复批改!”学生小明叹气道。

老师看了看小明的代码说:“我明确了,这是零碎设计上的缺点。总结就是形象不够、可读性低、不够强壮。”

“对对对,那怎么能迅速进步代码的可读性、健壮性、扩展性呢?”小明急不可耐地问道。

老师敲了敲小明的头:“不要太塌实,没有什么办法能让你立即成为零碎设计专家。然而对于你的问题,我想 设计模式 能够帮到你。”

“设计模式?”小明不解。

“是的。”老师点了拍板,“世上本没有路,走的人多了,便变成了路。在程序员的世界中,本没有设计模式,写代码是人多了,他们便总结出了一套能进步开发和保护效率的套路,这就是设计模式。设计模式不是什么教条或者范式,它能够说是一种在特定场景下普适且可复用的解决方案,是一种能够用于进步代码可读性、可扩展性、可维护性和可测性的最佳实际。”

“哦哦,我懂了,那我应该如何去学习呢?”

“不急,接下来我来带你缓缓理解设计模式。”

处分的发放策略

第一天,老师问小明:“你晓得流动营销吗?”

“这我晓得,流动营销是指企业通过参加社会关注度高的已有流动,或整合无效的资源自主策动大型流动,从而迅速进步企业及其品牌的知名度、美誉度和影响力,常见的比方有抽奖、红包等。”

老师点点头:“是的。咱们假如当初就要做一个营销,须要用户参加一个流动,而后实现一系列的工作,最初能够失去一些处分作为回报。流动的处分蕴含美团外卖、酒旅和美食等多种品类券,当初须要你帮忙设计一套处分发放计划。”

因为之前有过相似的开发教训,拿到需要的小明二话不说开始了编写起了代码:

// 处分服务
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!");
        }
    }
}

小明很快写好了 Demo,而后发给老师看。

“如果咱们行将接入新的打车券,这是否意味着你必须要批改这部分代码?”老师问道。

小明愣了一愣,没等反馈过去老师又问:”如果前面美团外卖的发券接口产生了扭转或者替换,这段逻辑是否必须要同步进行批改?”

小明陷入了思考之中,一时间没法答复。

经验丰富的老师切中时弊地指出了这段设计的问题:“你这段代码有两个次要问题,一是不合乎 开闭准则 ,能够预感,如果后续新增品类券的话,须要间接批改骨干代码,而咱们提倡代码应该是对批改关闭的;二是不合乎 迪米特法令,发奖逻辑和各个上游接口高度耦合,这导致接口的扭转将间接影响到代码的组织,使得代码的可维护性升高。”

小明豁然开朗:“那我将各个同上游接口交互的性能形象成独自的服务,封装其参数组装及异样解决,使得发奖主逻辑与其解耦,是否就能更具备扩展性和可维护性?”

“这是个不错的思路。之前跟你介绍过设计模式,这个案例就能够应用 策略模式 适配器模式 来优化。”

小明借此机会学习了这两个设计模式。首先是策略模式:

策略模式 [1-5] 定义了一系列的算法,并将每一个算法封装起来,使它们能够互相替换。策略模式通常蕴含以下角色:

  • 形象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的形式实现这个接口,环境角色应用这个接口调用不同的算法,个别应用接口或抽象类实现。
  • 具体策略(Concrete Strategy)类:实现了形象策略定义的接口,提供具体的算法实现。
  • 环境(Context)类:持有一个策略类的援用,最终给客户端调用。

而后是适配器模式:

适配器模式[1-5]:将一个类的接口转换成客户心愿的另外一个接口,使得本来因为接口不兼容而不能一起工作的那些类能一起工作。适配器模式蕴含以下次要角色:

  • 指标(Target)接口:以后零碎业务所期待的接口,它能够是抽象类或接口。
  • 适配者(Adaptee)类:它是被拜访和适配的现存组件库中的组件接口。
  • 适配器(Adapter)类:它是一个转换器,通过继承或援用适配者的对象,把适配者接口转换成指标接口,让客户按指标接口的格局拜访适配者。

联合优化思路,小明首先设计出了策略接口,并通过适配器的思维将各个上游接口类适配成策略类:

// 策略接口
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);
    }
}

小明的代码通过优化后,尽管构造和设计上比之前要简单不少,但思考到健壮性和拓展性,还是十分值得的。

“看,我这次优化后的版本是不是很完满?”小明怏怏不乐地说。

“耦合度的确升高了,但还能做的更好。”

“怎么做?”小明有点纳闷。

“我问你,策略类是有状态的模型吗?如果不是是否能够思考做成单例的?”

“的确如此。”小明仿佛明确了。

“还有一点,环境类的获取策略办法职责很明确,然而你仍然没有做到齐全对批改关闭。”

通过老师的点拨,小明很快也领悟到了要点:“那我能够将策略类单例化以缩小开销,并实现自注册的性能彻底解决分支判断。”

小明列出单例模式的要点:

单例模式 [1-5] 设计模式属于创立型模式,它提供了一种创建对象的最佳形式。

这种模式波及到一个繁多的类,该类负责创立本人的对象,同时确保只有单个对象被创立。这个类提供了一种拜访其惟一的对象的形式,能够间接拜访,不须要实例化该类的对象。

最终,小明在策略环境类中应用一个注册表来记录各个策略类的注册信息,并提供接口供策略类调用进行注册。同时应用 饿汉式单例模式 去优化策略类的设计:

// 策略上下文,用于管理策略的注册和获取
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);
    }
}

最终,小明设计实现的构造类图如下:

如果应用了 Spring 框架,还能够利用 Spring 的 Bean 机制来代替上述的局部设计,间接应用 @Component@PostConstruct注解即可实现单例的创立和注册,代码会更加简洁。

至此,通过了屡次探讨、反思和优化,小明终于失去了一套低耦合高内聚,同时合乎开闭准则的设计。

“老师,我开始学会利用设计模式去解决已发现的问题。这次我做得怎么样?”

“合格。然而,仍然要戒骄戒躁。”

任务模型的设计

“之前让你设计处分发放策略你还记得吗?”老师突然问道。

“当然记得。一个好的设计模式,能让工作事倍功半。”小明答道。

“嗯,那会提到了流动营销的组成部分,除了处分之外,貌似还有工作吧。”

小明点了拍板,老师接着说:“当初,我想让你去实现任务模型的设计。你须要重点关注状态的流转变更,以及状态变更后的音讯告诉。”

小明怅然接下了老师给的难题。他首先定义了一套工作状态的枚举和行为的枚举:

// 工作状态枚举
@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;}
        }
    }
}

在上述的实现中,小明在 updateState 办法中实现了 2 个重要的性能:

  1. 接管不同的行为,而后更新当前任务的状态;
  2. 当工作过期时,告诉工作所属的流动和工作管理器。

诚然,随着小明的零碎开发能力和代码品质意识的晋升,他可能意识到这种功能设计存在缺点。

“老师,我的代码还是和之前说的那样,不够优雅。”

“哦,你本人说说看有什么问题?”

“第一,办法中应用条件判断来管制语句,然而当条件简单或者状态太多时,条件判断语句会过于臃肿,可读性差,且不具备扩展性,保护难度也大。且减少新的状态时要增加新的 if-else 语句,这违反了开闭准则,不利于程序的扩大。”

老师表示同意,小明接着说:“第二,工作类不够高内聚,它在告诉实现中感知了其余畛域或模块的模型,如流动和工作管理器,这样代码的耦合度太高,不利于扩大。”

老师赞叹地说道:“很好,你无意识可能自主发现代码问题所在,曾经是很大的提高了。”

“那这个问题应该怎么去解决呢?”小明持续提问。

“这个同样能够通过设计模式去优化。首先是状态流转的管制能够应用 状态模式 ,其次,工作实现时的告诉能够用到 观察者模式。”

收到批示后,小明马上去学习了状态模式的构造:

状态模式[1-5]:对有状态的对象,把简单的“判断逻辑”提取到不同的状态对象中,容许状态对象在其外部状态产生扭转时扭转其行为。状态模式蕴含以下次要角色:

  • 环境类(Context)角色:也称为上下文,它定义了客户端须要的接口,外部保护一个以后状态,并负责具体状态的切换。
  • 形象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,能够有一个或多个行为。
  • 具体状态(Concrete State)角色:实现形象状态所对应的行为,并且在须要的状况下进行状态切换。

依据状态模式的定义,小明将 TaskState 枚举类扩大成多个状态类,并具备实现状态的流转的能力;而后优化了工作类的实现:

// 工作状态形象接口
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 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 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);
    }
}

小明欣慰地看到,通过状态模式解决后的工作类的耦合度失去升高,合乎开闭准则。状态模式的长处在于合乎繁多职责准则,状态类职责明确,有利于程序的扩大。然而这样设计的代价是状态类的数目减少了,因而状态流转逻辑越简单、须要解决的动作越多,越有利于状态模式的利用。除此之外,状态类的本身对于开闭准则的反对并没有足够好,如果状态流转逻辑变动频繁,那么可能要谨慎应用。

解决完状态后,小明又依据老师的领导应用 观察者模式 去优化工作实现时的告诉:

观察者模式[1-5]:指多个对象间存在一对多的依赖关系,当一个对象的状态产生扭转时,所有依赖于它的对象都失去告诉并被自动更新。这种模式有时又称作公布 - 订阅模式、模型 - 视图模式,它是对象行为型模式。观察者模式的次要角色如下。

  • 形象主题(Subject)角色:也叫形象指标类,它提供了一个用于保留观察者对象的汇集类和减少、删除观察者对象的办法,以及告诉所有观察者的形象办法。
  • 具体主题(Concrete Subject)角色:也叫具体指标类,它实现形象指标中的告诉办法,当具体主题的外部状态产生扭转时,告诉所有注册过的观察者对象。
  • 形象观察者(Observer)角色:它是一个抽象类或接口,它蕴含了一个更新本人的形象办法,当接到具体主题的更改告诉时被调用。
  • 具体观察者(Concrete Observer)角色:实现形象观察者中定义的形象办法,以便在失去指标的更改告诉时更新本身的状态。

小明首先设计好形象指标和形象观察者,而后将流动和工作管理器的接管告诉性能定制成具体观察者:

// 形象观察者
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);
    }
}

最初,小明将工作进行状态类优化成应用通用的告诉办法,并在工作初始态执行状态流转时定义工作进行态所需的观察者:

// 工作进行状态
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 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);
        }
    }
}

最终,小明设计实现的构造类图如下:

通过观察者模式,小明让工作状态和告诉方实现松耦合(实际上观察者模式还没能做到齐全的解耦,如果要做进一步的解耦能够思考学习并应用 公布 - 订阅模式,这里也不再赘述)。

至此,小明胜利应用状态模式设计出了高内聚、高扩展性、繁多职责的工作的整个状态机实现,以及做到松耦合的、合乎依赖倒置准则的工作状态变更告诉形式。

“老师,我逐步能意识到代码的设计缺点,并学会利用较为简单的设计模式做优化。”

“不错,再接再厉!”

流动的迭代重构

“小明,这次又有一个新的工作。”老师呈现在正在认真浏览《设计模式》的小明的背后。

“好的。刚好我曾经学习了设计模式的原理,终于能够派上用场了。”

“之前你设计开发了流动模型,当初咱们须要在工作型流动的参加办法上减少一层危险管制。”

“OK。借此机会,我也想重构一下之前的设计。”

流动模型的特点在于其组成部分较多,小明原先的流动模型的构建形式是这样的:

// 形象流动接口
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);
    }
}

通过自主剖析,小明发现流动的结构不够正当,次要问题体现在:

  1. 流动的结构组件较多,导致能够组合的构造函数太多,尤其是在模型减少字段时还须要去批改构造函数;
  2. 局部组件的结构存在肯定的程序关系,然而以后的实现没有体现程序,导致结构逻辑比拟凌乱,并且存在局部反复的代码。

发现问题后,小明回顾本人的学习成绩,马上想到能够应用创立型模式中的 建造者模式 去做重构:

建造者模式[1-5]:指将一个简单对象的结构与它的示意拆散,使同样的构建过程能够创立不同的示意。它是将一个简单的对象合成为多个简略的对象,而后一步一步构建而成。它将变与不变相拆散,即产品的组成部分是不变的,但每一部分是能够灵便抉择的。建造者模式的次要角色如下:

  1. 产品角色(Product):它是蕴含多个组成部件的简单对象,由具体建造者来创立其各个零部件。
  2. 形象建造者(Builder):它是一个蕴含创立产品各个子部件的形象办法的接口,通常还蕴含一个返回简单产品的办法 getResult()。
  3. 具体建造者(Concrete Builder):实现 Builder 接口,实现简单产品的各个部件的具体创立办法。
  4. 指挥者(Director):它调用建造者对象中的部件结构与拆卸办法实现简单对象的创立,在指挥者中不波及具体产品的信息。

依据建造者模式的定义,上述流动的每个字段都是一个产品。于是,小明能够通过在流动外面实现动态的建造者类来繁难地实现:

// 流动类
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 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);
        }
    }
}

小明发现,下面的建造器没有应用诸如形象建造器类等残缺的实现,然而根本是实现了流动各个组件的建造流程。应用建造器的模式下,能够先按程序构建字段 type,而后顺次构建其余组件,最初应用 build 办法获取建造实现的流动。这种设计一方面封装性好,构建和示意拆散;另一方面扩展性好,各个具体的建造者互相独立,有利于零碎的解耦。能够说是一次比拟有价值的重构。在理论的利用中,如果字段类型多,同时各个字段只须要简略的赋值,能够间接援用 Lombok 的 @Builder 注解来实现轻量的建造者。

重构完流动构建的设计后,小明开始对加入流动办法减少风控。最简略的形式必定是间接批改指标办法:

public void participate(Long userId) {
    // 对指标用户做危险管制,失败则抛出异样
    Risk.doControl(userId);
    // 更新工作状态为进行中
    task.state.update(task, ActionType.START);
}

然而思考到,最好能尽可能防止对旧办法的间接批改,同时为办法减少风控,也是一类比拟常见的性能新增,可能会在多处应用。

“老师,危险管制会呈现在多种流动的参加办法中吗?”

“有这个可能性。有的流动须要危险管制,有的不须要。风控像是在适当的时候对参加这个办法的装璜。”

“对了,装璜器模式!”

小明马上想到用 装璜器模式 来实现设计:

装璜器模式 [1-5] 的定义:指在不扭转现有对象构造的状况下,动静地给该对象减少一些职责(即减少其额定性能)的模式,它属于对象结构型模式。装璜器模式次要蕴含以下角色:

  1. 形象构件(Component)角色:定义一个形象接口以标准筹备接管附加责任的对象。
  2. 具体构件(ConcreteComponent)角色:实现形象构件,通过装璜角色为其增加一些职责。
  3. 形象装璜(Decorator)角色:继承形象构件,并蕴含具体构件的实例,能够通过其子类扩大具体构件的性能。
  4. 具体装璜(ConcreteDecorator)角色:实现形象装璜的相干办法,并给具体构件对象增加附加的责任。

小明应用了装璜器模式后,新的代码就变成了这样:

// 形象装璜角色
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);
    }
}

最终,小明设计实现的构造类图如下:

最终,小明通过本人的思考剖析,联合学习的设计模式常识,实现了流动模型的重构和迭代。

“老师,我曾经能做到自主剖析性能特点,并正当利用设计模式去实现程序设计和代码重构了,切实太感谢您了。”

“设计模式作为一种软件设计的最佳实际,你曾经很好地了解并利用于实际了,十分不错。但学海无涯,还需继续精进!”

结语

本文以三个理论场景为出发点,借助小明和老师两个虚构的人物,试图以一种较为滑稽的“对话”形式来讲述设计模式的利用场景、长处和毛病。如果大家想要去系统性地理解设计模式,也能够通过市面上很多的教材进行学习,都介绍了经典的 23 种设计模式的构造和实现 2022 年 3 月 11 日。不过,很多教材的内容即使配合了大量的示例,但有时也会让人感到费解,次要起因在于:一方面,很多案例比拟脱离实际的利用场景;另一方面,局部设计模式显然更实用于大型简单的结构设计,而当其利用到简略的场景时,好像让代码变得更加繁琐、冗余。因而,本文心愿通过这种“对话 + 代码展现 + 构造类图”的形式,以一种更易懂的形式来介绍设计模式。

当然,本文只讲述了局部比拟常见的设计模式,还有其余的设计模式,依然须要同学们去研读经典著作,触类旁通,学以致用。咱们也心愿通过学习设计模式能让更多的同学在零碎设计能力上失去晋升。

参考资料

  • [1] Gamma E. 设计模式: 可复用面向对象软件的根底 [M]. 机械工业出版社, 2007.
  • [2] 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007.
  • [3] oodesign.com
  • [4] java-design-patterns.com
  • [5] Java 设计模式:23 种设计模式全面解析

作者简介

嘉凯、杨柳,来自美团金融服务平台 / 联名卡研发团队。

浏览美团技术团队更多技术文章合集

前端 | 算法 | 后端 | 数据 | 平安 | 运维 | iOS | Android | 测试

| 在公众号菜单栏对话框回复【2021 年货】、【2020 年货】、【2019 年货】、【2018 年货】、【2017 年货】等关键词,可查看美团技术团队历年技术文章合集。

| 本文系美团技术团队出品,著作权归属美团。欢送出于分享和交换等非商业目标转载或应用本文内容,敬请注明“内容转载自美团技术团队”。本文未经许可,不得进行商业性转载或者应用。任何商用行为,请发送邮件至 tech@meituan.com 申请受权。

正文完
 0