设计模式在业务中的实践

一、前言

随着美团外卖业务的不断迭代与发展,外卖用户数量也在高速地增长。在这个过程中,外卖营销发挥了“中流砥柱”的作用,因为用户的快速增长离不开高效的营销策略。而由于市场环境和业务环境的多变,营销策略往往是复杂多变的,营销技术团队作为营销业务的支持部门,就需要快速高效地响应营销策略变更带来的需求变动。因此,设计并实现易于扩展和维护的营销系统,是美团外卖营销技术团队不懈追求的目标和必修的基本功。

本文通过自顶向下的方式,来介绍设计模式如何帮助我们构建一套易扩展、易维护的营销系统。本文会首先介绍设计模式与领域驱动设计(Domain-Driven Design,以下简称为DDD)之间的关系,然后再阐述外卖营销业务引入业务中用到的设计模式以及其具体实践案例。

二、设计模式与领域驱动设计

设计一个营销系统,我们通常的做法是采用自顶向下的方式来解构业务,为此我们引入了DDD。从战略层面上讲,DDD能够指导我们完成从问题空间到解决方案的剖析,将业务需求映射为领域上下文以及上下文间的映射关系。从战术层面上,DDD能够细化领域上下文,并形成有效的、细化的领域模型来指导工程实践。建立领域模型的一个关键意义在于,能够确保不断扩展和变化的需求在领域模型内不断地演进和发展,而不至于出现模型的腐化和领域逻辑的外溢。关于DDD的实践,大家可以参考此前的《领域驱动设计在互联网业务开发中的实践》一文。

同时,我们也需要在代码工程中贯彻和实现领域模型。因为代码工程是领域模型在工程实践中的直观体现,也是领域模型在技术层面的直接表述。而设计模式,可以说是连接领域模型与代码工程的一座桥梁,它能有效地解决从领域模型到代码工程的转化。

为什么说设计模式天然具备成为领域模型到代码工程之间桥梁的作用呢?其实,2003年出版的《领域驱动设计》一书的作者Eric Evans在这部开山之作中就已经给出了解释。他认为,立场不同会影响人们如何看待什么是“模式”。因此,无论是领域驱动模式还是设计模式,本质上都是“模式”,只是解决的问题不一样。站在业务建模的立场上,DDD的模式解决的是如何进行领域建模。而站在代码实践的立场上,设计模式主要关注于代码的设计与实现。既然本质都是模式,那么它们天然就具有一定的共通之处。

所谓“模式”,就是一套反复被人使用或验证过的方法论。从抽象或者更宏观的角度上看,只要符合使用场景并且能解决实际问题,模式应该既可以应用在DDD中,也可以应用在设计模式中。事实上,Evans也是这么做的。他在著作中阐述了Strategy和Composite这两个传统的GOF设计模式是如何来解决领域模型建设的。因此,当领域模型需要转化为代码工程时,同构的模式,天然能够将领域模型翻译成代码模型。

三、设计模式在外卖营销业务中的具体案例

3.1 为什么需要设计模式

营销业务的特点

如前文所述,营销业务与交易等其他模式相对稳定的业务的区别在于,营销需求会随着市场、用户、环境的不断变化而进行调整。也正是因此,外卖营销技术团队选择了DDD进行领域建模,并在适用的场景下,用设计模式在代码工程的层面上实践和反映了领域模型。以此来做到在支持业务变化的同时,让领域和代码模型健康演进,避免模型腐化。

理解设计模式

软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性,程序的重用性。可以理解为:“世上本来没有设计模式,用的人多了,便总结出了一套设计模式。”

设计模式原则

面向对象的设计模式有七大基本原则:

  • 开闭原则(Open Closed Principle,OCP)

  • 单一职责原则(Single Responsibility Principle, SRP)

  • 里氏代换原则(Liskov Substitution Principle,LSP)

  • 依赖倒转原则(Dependency Inversion Principle,DIP)

  • 接口隔离原则(Interface 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一定的现金奖励(以下简称返奖)。同时为了协调成本与收益的关系,返奖会有多个计算策略。邀请下单后台主要涉及两个技术要点:

1.返奖金额的计算,涉及到不同的计算规则。

2.从邀请开始到返奖结束的整个流程。

在这里插入图片描述

3.2.2 返奖规则与设计模式实践

业务建模

如图是返奖规则计算的业务逻辑视图:

在这里插入图片描述
从这份业务逻辑图中可以看到返奖金额计算的规则。首先要根据用户状态确定用户是否满足返奖条件。如果满足返奖条件,则继续判断当前用户属于新用户还是老用户,从而给予不同的奖励方案。一共涉及以下几种不同的奖励方案。

新用户
  • 普通奖励(给予固定金额的奖励)

  • 梯度奖(根据用户邀请的人数给予不同的奖励金额,邀请的人越多,奖励金额越多)

老用户

根据老用户的用户属性来计算返奖金额。为了评估不同的邀新效果,老用户返奖会存在多种返奖机制。

计算完奖励金额以后,还需要更新用户的奖金信息,以及通知结算服务对用户的金额进行结算。这两个模块对于所有的奖励来说都是一样的。

可以看到,无论是何种用户,对于整体返奖流程是不变的,唯一变化的是返奖规则。此处,我们可参考开闭原则,对于返奖流程保持封闭,对于可能扩展的返奖规则进行开放。我们将返奖规则抽象为返奖策略,即针对不同用户类型的不同返奖方案,我们视为不同的返奖策略,不同的返奖策略会产生不同的返奖金额结果。

在我们的领域模型里,返奖策略是一个值对象,我们通过工厂的方式生产针对不同用户的奖励策略值对象。下文我们将介绍以上领域模型的工程实现,即工厂模式策略模式的实际应用。

模式:工厂模式

工厂模式又细分为工厂方法模式和抽象工厂模式,本文主要介绍工厂方法模式。

模式定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法是一个类的实例化延迟到其子类。

工厂模式通用类图如下:

在这里插入图片描述
我们通过一段较为通用的代码来解释如何使用工厂模式:

//抽象的产品
public abstract class Product {
   
    public abstract void method();
}
//定义一个具体的产品 (可以定义多个具体的产品)
class ProductA extends Product {
   
    @Override
    public void method() {
   }  //具体的执行逻辑
}
//抽象的工厂
abstract class Factory<T> {
   
    abstract Product createProduct(Class<T> c);
}
//具体的工厂可以生产出相应的产品
class FactoryA extends Factory{
   
    @Override
    Product createProduct(Class c) {
   
        Product product = (Product) Class.forName(c.getName()).newInstance();
        return product;
    }
}
模式:策略模式

模式定义:定义一系列算法,将每个算法都封装起来,并且它们可以互换。策略模式是一种对象行为模式。

策略模式通用类图如下:

在这里插入图片描述
我们通过一段比较通用的代码来解释怎么使用策略模式:

//定义一个策略接口
public interface Strategy {
   
    void strategyImplementation();
}//具体的策略实现(可以定义多个具体的策略实现)
public class StrategyA implements Strategy{
   
    @Override
    public void strategyImplementation() {
   
        System.out.println("正在执行策略A");
    }
}//封装策略,屏蔽高层模块对策略、算法的直接访问,屏蔽可能存在的策略变化
public class Context {
   
    private Strategy strategy = null;public Context(Strategy strategy) {
   
        this.strategy = strategy;
    }
  
    public void doStrategy() {
   
        strategy.strategyImplementation();
    }
}
工程实践

通过上文介绍的返奖业务模型,我们可以看到返奖的主流程就是选择不同的返奖策略的过程,每个返奖策略都包括返奖金额计算、更新用户奖金信息、以及结算这三个步骤。 我们可以使用工厂模式生产出不同的策略,同时使用策略模式来进行不同的策略执行。首先确定我们需要生成出n种不同的返奖策略,其编码如下:

//抽象策略
public abstract class RewardStrategy {
   
    public abstract void reward(long userId);
  
    public void insertRewardAndSettlement(long userId, int reward) {
   } ; //更新用户信息以及结算
}
//新用户返奖具体策略A
public class newUserRewardStrategyA extends RewardStrategy {
   
    @Override
    public void reward(long userId) {
   }  //具体的计算逻辑,...
}//老用户返奖具体策略A
public class OldUserRewardStrategyA extends RewardStrategy {
   
    @Override
    public void reward(long userId) {
   }  //具体的计算逻辑,...
}//抽象工厂
public abstract class StrategyFact
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值