业务战略的变化导致了需求的变化,这是业内许多技术团队面临的最具挑战性的问题之一。那么,如何设计一个容易扩展和维护的营销系统呢?
今天的文章来自美团外卖营销技术团队。他们分享了从领域模型到代码工程的转变DDD介绍了美团营销业务中工厂方法模式、战略模式、责任链模式和状态模式的具体实现,将理论与实践深度结合。
一、前言
随着美团外卖业务的不断迭代和发展,外卖用户数量也在快速增长。在这个过程中,外卖营销发挥了支柱的作用,因为用户的快速增长与高效的营销策略是分不开的。由于市场环境和业务环境的变化,营销策略往往是复杂和多变的。作为营销业务的支持部门,营销技术团队需要快速有效地响应营销策略变化带来的需求变化。因此,设计和实现易于扩展和维护的营销体系是美团外卖营销技术团队不懈追求的目标和必要的基本技能。
本文介绍了如何帮助我们建立一个易于扩展和维护的营销系统。本文将首先介绍设计模式和领域驱动设计( Do ** in-Driven Design,以下简称为DDD)然后阐述外卖营销业务引入业务中使用的设计模式及其具体实践案例。
二、设计模式和领域驱动设计
设计一个营销系统,我们通常的做法是自上而下解构业务,所以我们介绍了它DDD。战略层面,DDD它可以引导我们完成从问题空间到解决方案的分析,并将业务需求映射到上下文和上下文之间的映射关系。从战术层面,DDD能够细化领域上下文,形成有效、细化的领域模型,指导项目实践。建立领域模型的关键意义之一是确保不断扩展和变化的需求在领域模型中不断演变和发展,而不是模型腐败和领域逻辑溢出。DDD在实践中,您可以参考美团技术团队在互联网业务发展中推出的领域驱动设计文。
同时,我们也需要在代码工程中贯彻和实现领域模型。因为代码工程是领域模型在工程实践中的直观体现,也是领域模型在技术层面的直接表述。而设计模式,可以说是连接领域模型与代码工程的一座桥梁,它能有效地解决从领域模型到代码工程的转化。
为什么设计模式自然会成为领域模型与代码工程之间的桥梁?事实上,2003年出版的《领域驱动设计》的作者Eric Evans这部开山作品已经给出了解释。他认为,不同的立场会影响人们对什么是模式的看法。因此,无论是领域驱动模式还是设计模式,本质上都是模式DDD该模型解决了如何建模该领域。从代码实践的角度来看,设计模式主要关注代码的设计和实现。由于本质是模型,它们自然有一些共同点。
所谓模式,就是一套反复使用或验证的方 ** 。从抽象或更宏观的角度来看,只要符合使用场景并能解决实际问题,模式就应该应用于DDD也可应用于设计模式。事实上,Evans也是如此。他在作品中阐述了这一点。Strategy和Composite这两个传统GOF如何解决现场模型建设的设计模式。因此,当需要将现场模型转换为代码工程时,同构模型自然可以将现场模型转换为代码模型。
三、外卖营销业务设计模式的具体案例
3.11 为什么需要设计模式?
营销业务的特点
正如上述,营销业务与交易等其他模式相对稳定的业务的区别在于,营销需求将随着市场、用户和环境的不断变化而调整。因此,外卖营销技术团队选择了它DDD在适用的场景下,用设计模式实践和反映代码工程层面的领域模型。为了支持业务变化,使领域和代码模型健康发展,避免模型腐败。
了解设计模式
软件设计模式(Design pattern),也被称为设计模式,是一套重复使用、大多数人知道、分类目的、代码设计经验的总结。设计模式的使用是重用代码,使代码更容易被他人理解,以确保代码的可靠性和程序的重用性。可以理解为:世界上没有设计模式,更多的人使用它,所以我总结了一套设计模式。
设计模式原则
面向对象的设计模式有七个基本原则:
开关原则( Open Closed Principle,OCP Single Responsibility Principle,SRP Liskov Substitution Principle,LSP 依赖倒转原则( ) Dependency Inversion Principle,DIP Inte ** ce Segregation Principle,ISP Composite/Aggregate Reuse Principle,CARP 知识最少原则(Least Knowledge Principle,LKP)或者迪米特法则( Law of Demeter,LOD )简单的理解是: 开关原则是大纲,它指导我们扩大开放,修改和关闭;单一责任原则指导我们实现单一责任;里氏替换原则指导我们不破坏继承系统;依靠倒置原则指导我们进行接口编程;接口隔离原则指导我们简化接口;迪米特规则指导我们减少耦合。
设计模式是通过这七个原则来指导我们如何做一个好的设计。但设计模式不是一套奇怪的技巧** ,高内聚、低耦合的设计理念。在此基础上,我们可以自由发挥,甚至设计自己的设计模式。
当然,学习设计模式或在项目中实践设计模式,必须深入到特定的业务场景中,然后结合对业务场景的理解和领域模型的建立,才能实现设计模式理念的本质。学习或使用设计模式是非常空洞的。接下来,我们将讨论如何使用设计模式来实现可重用和易于维护的代码。
3.2 邀请订单业务设计模式的实践
3.2.1 业务简介
邀请订单是美团外卖用户下单后给予奖励的平台。A邀请用户B,并且用户B在美团下单后,给用户A一定的现金奖励(以下简称返还奖励)。同时,为了协调成本与收入之间的关系,返还奖励将有多种计算策略。邀请下单的背景主要涉及两个技术要点:
返奖金额的计算涉及不同的计算规则。 从邀请到返奖结束的整个过程。3.2.2 返回规则和设计模式的实践
业务建模
如图为返奖规则计算的业务逻辑视图:
从这个业务逻辑图中可以看到退款金额计算的规则。首先,用户是否符合退款条件应根据用户状态确定。如果满足退款条件,继续判断当前用户是新用户还是老用户,以便给出不同的奖励方案。共有以下不同的奖励方案:
新用户
普通奖励( 固定金额) 梯度奖( 根据用户邀请的人数给予不同的奖励金额,邀请的人越多,奖励金额越多)老用户
返还金额根据老用户的用户属性计算。为了评估不同的邀请效果,老用户将有多种返还机制。计算奖励金额后,还需要更新用户奖金信息,并通知结算服务结算用户金额。这两个模块对所有奖励都是一样的。
可以看出,无论什么样的用户,整体奖励流程都是不变的,唯一的变化是奖励规则。在这里,我们可以参考 开放和关闭的原则,保持奖励流程的关闭,并对可能扩展的奖励规则开放。我们将奖励规则抽象为 奖励策略,即对于不同用户类型的不同奖励计划,我们将被视为不同的奖励策略,不同的奖励策略将产生不同的奖励金额结果。
在我们的领域模型中,奖励策略是 值对象,我们通过工厂为不同用户生产奖励策略值对象。下面,我们将介绍上述领域模型的工程实现,即 工厂模型和 战略模型的实际应用。
模式:工厂模式
工厂模式分为工厂方法模式和抽象工厂模式要介绍工厂模式。
工厂模式分为工厂方法模式和抽象工厂模式要介绍工厂模式。
模式定义:定义一个用于创建对象的接口,让子类决定哪个类别是实例。工厂的方法是将一个类的实例延迟到其子类。
一般工厂模式类图如下:
如何使用工厂模式,我们通过一个更通用的代码来解释:
///抽象产品
publicabstractclassProduct{
publicabstractvoidmethod;
}
//定义一个特定的产品 (可以定义多个特定的产品)
classProductAextendsProduct{
@Override
publicvoidmethod{} ///具体执行逻辑
}
///抽象工厂
abstractclassFactory< T> {
abstractProduct createProduct(Class<T> c);
}
///具体工厂可生产相应的产品
classFactoryAextendsFactory{
@Override
Product createProduct(Class c){
Product product = (Product) Class.forName(c.getName).newInstance;
returnproduct;
}
}
模式:战略模式
模式定义:定义一系列算法,包装每个算法,可以交换。战略模式是对象行为模式。
一般战略模式类图如下:
我们解释了如何通过一个更通用的代码使用策略模式:
///定义一个战略界面
publicinte ** ceStrategy{
voidstrategyImplementation;
}
//具体策略实现(可定义多个具体策略实现)
publicclassStrategyAimplementsStrategy{
@Override
publicvoidstrategyImplementation{
System.out.println( \"正在策略A\");
}
}
//包装策略,屏蔽高层模块直接访问策略和算法,屏蔽可能的策略变化
publicclassContext{
privateStrategy strategy = null;
publicContext(Strategy strategy){
this.strategy = strategy;
}
publicvoiddoStrategy{
strategy.strategyImplementation;
}
}
工程实践
通过以上介绍的回扣业务模式,我们可以看到回扣的主要过程是选择不同的回扣策略。每个回扣策略包括三个步骤:计算回扣金额、更新用户奖金信息和结算。我们可以使用工厂模式生产不同的策略,并使用不同的策略实施。首先,确保我们需要生成n种不同的返奖策略,其编码如下:
///抽象策略
publicabstractclassRewardStrategy{
publicabstractvoidreward( longuserId) ;
publicvoidinsertRewardAndSettlement( longuserId,intreward) {}
}
///A
publicclassnewUserRewardStrategyAextendsRewardStrategy{
@Override
publicvoidreward( longuserId) {} //具体计算逻辑,...
}
////老用户返奖具体策略A
publicclassOldUserRewardStrategyAextendsRewardStrategy{
@Override
publicvoidreward( longuserId) {} //具体计算逻辑,...
}
///抽象工厂
publicabstractclassStrategyFactory< T> {
abstractRewardStrategy createStrategy(Class<T> c);
}
////具体工厂创建具体策略
publicclassFactorRewardStrategyFactoryextendsStrategyFactory{
@Override
RewardStrategy createStrategy(Class c){
RewardStrategy product = null;
try{
product = (RewardStrategy) Class.forName(c.getName).newInstance;
} catch(Exception e) {}
returnproduct;
}
}
通过工厂模式生产出具体的策略之后,根据我们之前的介绍,很容易就可以想到使用策略模式来执行我们的策略。具体代码如下:
publicclassRewardContext{
privateRewardStrategy strategy;
publicRewardContext(RewardStrategy strategy){
this.strategy = strategy;
}
publicvoiddoStrategy( longuserId) {
intrewardMoney = strategy.reward(userId);
insertRewardAndSettlement( longuserId, intreward) {
insertReward(userId, rewardMoney);
settlement(userId);
}
}
}
接下来我们将工厂模式和策略模式结合在一起,就完成了整个返奖的过程:
publicclassInviteRewardImpl{
//返奖主流程
publicvoidsendReward( longuserId) {
FactorRewardStrategyFactory strategyFactory = newFactorRewardStrategyFactory; //创建工厂
Invitee invitee = getInviteeByUserId(userId); //根据用户id查询用户信息
if(invitee.userType == UserTypeEnum.NEW_USER) { //新用户返奖策略
NewUserBasicReward newUserBasicReward = (NewUserBasicReward) strategyFactory.createStrategy(NewUserBasicReward.class);
RewardContext rewardContext = newRewardContext(newUserBasicReward);
rewardContext.doStrategy(userId); //执行返奖策略
} if(invitee.userType == UserTypeEnum.OLD_USER){} //老用户返奖策略,...
}
}
工厂方法模式帮助我们直接产生一个具体的策略对象,策略模式帮助我们保证这些策略对象可以自由地切换而不需要改动其他逻辑,从而达到解耦的目的。通过这两个模式的组合,当我们系统需要增加一种返奖策略时,只需要实现RewardStrategy接口即可,无需考虑其他的改动。当我们需要改变策略时,只要修改策略的类名即可。不仅增强了系统的可扩展性,避免了大量的条件判断,而且从真正意义上达到了高内聚、低耦合的目的。
3.2.3 返奖流程与设计模式实践
业务建模
我们对上述业务流程进行领域建模:
在接收到订单消息后,用户进入待校验状态; 在校验后,若校验通过,用户进入预返奖状态,并放入延迟队列。若校验未通过,用户进入不返奖状态,结束流程; T+N天后,处理延迟消息,若用户未退款,进入待返奖状态。若用户退款,进入失败状态,结束流程; 执行返奖,若返奖成功,进入完成状态,结束流程。若返奖不成功,进入待补偿状态; 待补偿状态的用户会由任务定期触发补偿机制,直至返奖成功,进入完成状态,保障流程结束。可以看到,我们通过建模将返奖流程的多个步骤映射为系统的状态。对于系统状态的表述,DDD中常用到的概念是领域事件,另外也提及过事件溯源的实践方案。当然,在设计模式中,也有一种能够表述系统状态的代码模型,那就是状态模式。在邀请下单系统中,我们的主要流程是返奖。对于返奖,每一个状态要进行的动作和操作都是不同的。因此,使用状态模式,能够帮助我们对系统状态以及状态间的流转进行统一的管理和扩展。
模式:状态模式
模式定义:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
状态模式的通用类图如下图所示:
对比策略模式的类型会发现和状态模式的类图很类似,但实际上有很大的区别,具体体现在concrete class上。策略模式通过Context产生唯一一个ConcreteStrategy作用于代码中,而状态模式则是通过context组织多个ConcreteState形成一个状态转换图来实现业务逻辑。接下来,我们通过一段通用代码来解释怎么使用状态模式:
//定义一个抽象的状态类
publicabstractclassState{
Context context;
publicvoidsetContext(Context context){
this.context = context;
}
publicabstractvoidhandle1;
publicabstractvoidhandle2;
}
//定义状态A
publicclassConcreteStateAextendsState{
@Override
publicvoidhandle1{} //本状态下必须要处理的事情
@Override
publicvoidhandle2{
super.context.setCurrentState(Context.contreteStateB); //切换到状态B
super.context.handle2; //执行状态B的任务
}
}
//定义状态B
publicclassConcreteStateBextendsState{
@Override
publicvoidhandle2{} //本状态下必须要处理的事情,...
@Override
publicvoidhandle1{
super.context.setCurrentState(Context.contreteStateA); //切换到状态A
super.context.handle1; //执行状态A的任务
}
}
//定义一个上下文管理环境
publicclassContext{
publicfinalstaticConcreteStateA contreteStateA = newConcreteStateA;
publicfinalstaticConcreteStateB contreteStateB = newConcreteStateB;
privateState CurrentState;
publicState getCurrentState{ returnCurrentState;}
publicvoidsetCurrentState(State currentState){
this.CurrentState = currentState;
this.CurrentState.setContext( this);
}
publicvoidhandle1{ this.CurrentState.handle1;}
publicvoidhandle2{ this.CurrentState.handle2;}
}
//定义client执行
publicclassclient{
publicstaticvoid ** in(String[] args){
Context context = newContext;
context.setCurrentState( newContreteStateA);
context.handle1;
context.handle2;
}
}
工程实践
通过前文对状态模式的简介,我们可以看到当状态之间的转换在不是非常复杂的情况下,通用的状态模式存在大量的与状态无关的动作从而产生大量的无用代码。在我们的实践中,一个状态的下游不会涉及特别多的状态 转换,所以我们简化了状态模式。当前的状态只负责当前状态要处理的事情,状态的流转则由第三方类负责。其实践代码如下:
//返奖状态执行的上下文
publicclassRewardStateContext{
privateRewardState rewardState;
publicvoidsetRewardState(RewardState currentState){ this.rewardState = currentState;}
publicRewardState getRewardState{ returnrewardState;}
publicvoidecho(RewardStateContext context, Request request){
rewardState.doReward(context, request);
}
}
publicabstractclassRewardState{
abstractvoiddoReward(RewardStateContext context, Request request);
}
//待校验状态
publicclassOrderCheckStateextendsRewardState{
@Override
publicvoiddoReward(RewardStateContext context, Request request){
orderCheck(context, request); //对进来的订单进行校验,判断是否用券,是否满足优惠条件等等
}
}
//待补偿状态
publicclassCompensateRewardStateextendsRewardState{
@Override
publicvoiddoReward(RewardStateContext context, Request request){
compensateReward(context, request); //返奖失败,需要对用户进行返奖补偿
}
}
//预返奖状态,待返奖状态,成功状态,失败状态(此处逻辑省略)
//..
publicclassInviteRewardServiceImpl{
publicbooleansendRewardForInvtee( longuserId, longorderId) {
Request request = newRequest(userId, orderId);
RewardStateContext rewardContext = newRewardStateContext;
rewardContext.setRewardState( newOrderCheckState);
rewardContext.echo(rewardContext, request); //开始返奖,订单校验
//此处的if-else逻辑只是为了表达状态的转换过程,并非实际的业务逻辑
if(rewardContext.isResultFlag) { //如果订单校验成功,进入预返奖状态
rewardContext.setRewardState( newBeforeRewardCheckState);
rewardContext.echo(rewardContext, request);
} else{ //如果订单校验失败,进入返奖失败流程,...
rewardContext.setRewardState( newRewardFailedState);
rewardContext.echo(rewardContext, request);
returnfalse;
}
if(rewardContext.isResultFlag) { //预返奖检查成功,进入待返奖流程,...
rewardContext.setRewardState( newSendRewardState);
rewardContext.echo(rewardContext, request);
} else{ //如果预返奖检查失败,进入返奖失败流程,...
rewardContext.setRewardState( newRewardFailedState);
rewardContext.echo(rewardContext, request);
returnfalse;
}
if(rewardContext.isResultFlag) { //返奖成功,进入返奖结束流程,...
rewardContext.setRewardState( newRewardSuccessState);
rewardContext.echo(rewardContext, request);
} else{ //返奖失败,进入返奖补偿阶段,...
rewardContext.setRewardState( newCompensateRewardState);
rewardContext.echo(rewardContext, request);
}
if(rewardContext.isResultFlag) { //补偿成功,进入返奖完成阶段,...
rewardContext.setRewardState( newRewardSuccessState);
rewardContext.echo(rewardContext, request);
} else{ //补偿失败,仍然停留在当前态,直至补偿成功(或多次补偿失败后人工介入处理)
rewardContext.setRewardState( newCompensateRewardState);
rewardContext.echo(rewardContext, request);
}
returntrue;
}
}
状态模式的核心是封装,将状态以及状态转换逻辑封装到类的内部来实现,也很好的体现了“开闭原则”和“单一职责原则”。每一个状态都是一个子类,不管是修改还是增加状态,只需要修改或者增加一个子类即可。在我们的应用场景中,状态数量以及状态转换远比上述例子复杂,通过“状态模式”避免了大量的if-else代码,让我们的逻辑变得更加清晰。同时由于状态模式的良好的封装性以及遵循的设计原则,让我们在复杂的业务场景中,能够游刃有余地管理各个状态。
3.3 点评外卖投放系统中设计模式的实践
3.3.1 业务简介
继续举例,点评App的外卖频道中会预留多个资源位为营销使用,向用户展示一些比较精品美味的外卖食品,为了增加用户点外卖的意向。当用户点击点评首页的“美团外卖”入口时,资源位开始加载,会通过一些规则来筛选出合适的展示Banner。
3.3.2 设计模式实践
业务建模
对于投放业务,就是要在这些资源位中展示符合当前用户的资源。其流程如下图所示:
从流程中我们可以看到,首先运营人员会配置需要展示的资源,以及对资源进行过滤的规则。我们资源的过滤规则相对灵活多变,这里体现为三点:
过滤规则大部分可重用,但也会有扩展和变更。 不同资源位的过滤规则和过滤顺序是不同的。 同一个资源位由于业务所处的不同阶段,过滤规则可能不同。过滤规则本身是一个个的值对象,我们通过领域服务的方式,操作这些规则值对象完成资源位的过滤逻辑。下图介绍了资源位在进行用户特征相关规则过滤时的过程:
为了实现过滤规则的解耦,对单个规则值对象的修改封闭,并对规则 ** 组成的过滤链条开放,我们在资源位过滤的领域服务中引入了责任链模式。
模式:责任链模式
模式定义:使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。
责任链模式通用类图如下:
我们通过一段比较通用的代码来解释如何使用责任链模式:
//定义一个抽象的handle
publicabstractclassHandler{
privateHandler nextHandler; //指向下一个处理者
privateintlevel; //处理者能够处理的级别
publicHandler( intlevel) {
this.level = level;
}
publicvoidsetNextHandler(Handler handler){
this.nextHandler = handler;
}
// 处理请求传递,注意final,子类不可重写
publicfinalvoidhandleMessage(Request request){
if(level == request.getRequstLevel) {
this.echo(request);
} else{
if( this.nextHandler != null) {
this.nextHandler.handleMessage(request);
} else{
System.out.println( \"已经到最尽头了\");
}
}
}
// 抽象方法,子类实现
publicabstractvoidecho(Request request);
}
// 定义一个具体的handleA
publicclassHandleRuleAextendsHandler{
publicHandleRuleA( intlevel) {
super(level);
}
@Override
publicvoidecho(Request request){
System.out.println( \"我是处理者1,我正在处理A规则\");
}
}
//定义一个具体的handleB
publicclassHandleRuleBextendsHandler{} //...
//客户端实现
classClient{
publicstaticvoid ** in(String[] args){
HandleRuleA handleRuleA = newHandleRuleA( 1);
HandleRuleB handleRuleB = newHandleRuleB( 2);
handleRuleA.setNextHandler(handleRuleB); //这是重点,将handleA和handleB串起来
handleRuleA.echo( newRequest);
}
}
工程实践
下面通过代码向大家展示如何实现这一套流程:
//定义一个抽象的规则
publicabstractclassBasicRule< CORE_ITEM, TextendsRuleContext< CORE_ITEM>> {
//有两个方法,evaluate用于判断是否经过规则执行,execute用于执行具体的规则内容。
publicabstractbooleanevaluate(T context);
publicabstractvoidexecute(T context){
}
//定义所有的规则具体实现
//规则1:判断服务可用性
publicclassServiceAvailableRuleextendsBasicRule< UserPortrait, UserPortraitRuleContext> {
@Override
publicbooleanevaluate(UserPortraitRuleContext context){
TakeawayUserPortraitBasicInfo basicInfo = context.getBasicInfo;
if(basicInfo.isServiceFail) {
returnfalse;
}
returntrue;
}
@Override
publicvoidexecute(UserPortraitRuleContext context){}
}
//规则2:判断当前用户属性是否符合当前资源位投放的用户属性要求
publicclassUserGroupRuleextendsBasicRule< UserPortrait, UserPortraitRuleContext> {
@Override
publicbooleanevaluate(UserPortraitRuleContext context){}
@Override
publicvoidexecute(UserPortraitRuleContext context){
UserPortrait userPortraitPO = context.getData;
if(userPortraitPO.getUserGroup == context.getBasicInfo.getUserGroup.code) {
context.setValid( true);
} else{
context.setValid( false);
}
}
}
//规则3:判断当前用户是否在投放城市,具体逻辑省略
publicclassCityInfoRuleextendsBasicRule< UserPortrait, UserPortraitRuleContext> {}
//规则4:根据用户的活跃度进行资源过滤,具体逻辑省略
publicclassUserPortraitRuleextendsBasicRule< UserPortrait, UserPortraitRuleContext> {}
//我们通过spring将这些规则串起来组成一个一个请求链
<bean name= \"serviceAvailableRule\"class= \"com.dianping.takeaway.ServiceAvailableRule\"/>
<bean name= \"userGroupValidRule\"class= \"com.dianping.takeaway.UserGroupRule\"/>
<bean name= \"cityInfoValidRule\"class= \"com.dianping.takeaway.CityInfoRule\"/>
<bean name= \"userPortraitRule\"class= \"com.dianping.takeaway.UserPortraitRule\"/>
<util:list id= \"userPortraitRuleChain\"value-type= \"com.dianping.takeaway.Rule\">
<ref bean= \"serviceAvailableRule\"/>
<ref bean= \"userGroupValidRule\"/>
<ref bean= \"cityInfoValidRule\"/>
<ref bean= \"userPortraitRule\"/>
</util:list>
//规则执行
publicclassDefaultRuleEngine{
@Autowired
List<BasicRule> userPortraitRuleChain;
publicvoidinvokeAll(RuleContext ruleContext){
for(Rule rule : userPortraitRuleChain) {
rule.evaluate(ruleContext)
}
}
}
责任链模式最重要的优点就是解耦,将客户端与处理者分开,客户端不需要了解是哪个处理者对事件进行处理,处理者也不需要知道处理的整个流程。
在我们的系统中,后台的过滤规则会经常变动,规则和规则之间可能也会存在传递关系,通过责任链模式,我们将规则与规则分开,将规则与规则之间的传递关系通过Spring注入到List中,形成一个链的关系。当增加一个规则时,只需要实现BasicRule接口,然后将新增的规则按照顺序加入Spring中即可。当删除时,只需删除相关规则即可,不需要考虑代码的其他逻辑。从而显著地提高了代码的灵活性,提高了代码的开发效率,同时也保证了系统的稳定性。
四、总结
本文从营销业务出发,介绍了领域模型到代码工程之间的转化,从DDD引出了设计模式,详细介绍了工厂方法模式、策略模式、责任链模式以及状态模式这四种模式在营销业务中的具体实现。
除了这四种模式以外,我们的代码工程中还大量使用了代理模式、单例模式、适配器模式等等,例如在我们对DDD防腐层的实现就使用了适配器模式,通过适配器模式屏蔽了业务逻辑与第三方服务的交互。因篇幅原因,这里不再进行过多的阐述。
对于营销业务来说,业务策略多变导致需求多变是我们面临的主要问题。如何应对复杂多变的需求,是我们提炼领域模型和实现代码模型时必须要考虑的内容。DDD以及设计模式提供了一套相对完整的方 ** 帮助我们完成了领域建模及工程实现。其实,设计模式就像一面镜子,将领域模型映射到代码模型中,切实地提高代码的复用性、可扩展性,也提高了系统的可维护性。
当然,设计模式只是软件开发领域内多年来的经验总结,任何一个或简单或复杂的设计模式都会遵循上述的七大设计原则,只要大家真正理解了七大设计原则,设计模式对我们来说应该就不再是一件难事。但是,使用设计模式也不是要求我们循规蹈矩,只要我们的代码模型设计遵循了上述的七大原则,我们会发现原来我们的设计中就已经使用了某种设计模式。
五、参考资料
软件设计模式-百度百科 快速理解-设计模式六大原则 Software design pattern 《设计模式之禅》,秦小波,机械工业出版社 《领域驱动设计-软件核心复杂性应对之道》,Eric Evans,人民邮电出版社。六、作者简介
亮亮,2017年加入美团外卖,美团外卖营销后台团队开发工程师。
---------- END ----------
扫码咨询与免费使用
申请免费使用