起因
最近在实习的时候一直在思考怎么提高代码健壮性与可复用性。 工程实践这块确实存在一些欠缺,应当与设计模式相结合,努力提高代码的健壮性的同时,也提高大佬们复用我逻辑的可能性。
设计模式
设计模式应当是最有用的,和本科期间写的玩具项目不同,没有多人维护,生命周期也可能只在一个月,甚至更少。
设计模式原则
面向对象的设计模式有七大基本原则:
- 开闭原则(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)
简单理解就是:开闭原则是总纲,它指导我们要对扩展开放,对修改关闭; 单一职责原则指导我们实现类要职责单一;里氏替换原则指导我们不要破坏继承体系; 依赖倒置原则指导我们要面向接口编程; 接口隔离原则指导我们在设计接口的时候要精简单一; 迪米特法则指导我们要降低耦合。
设计模式就是通过这七个原则,来指导我们如何做一个好的设计。但是设计模式不是一套“奇技淫巧”,它是一套方法论,一种高内聚、低耦合的设计思想。我们可以在此基础上自由的发挥,甚至设计出自己的一套设计模式。
当然,学习设计模式或者是在工程中实践设计模式,必须深入到某一个特定的业务场景中去,再结合对业务场景的理解和领域模型的建立,才能体会到设计模式思想的精髓。如果脱离具体的业务逻辑去学习或者使用设计模式,那是极其空洞的。接下来我们将通过外卖营销业务的实践,来探讨如何用设计模式来实现可重用、易维护的代码。
几个case初探设计模式
言而总之,掌握一个方法论,高内聚、低耦合。
奖励发放逻辑 — 从 if 分支到策略 + 工厂模式
原先的逻辑是RewardService,后续维护起来实在太过繁琐,过多的if else读的头都晕了,维护起来也不方便。
一是不符合开闭原则,可以预见,如果后续新增品类券的话,需要直接修改主干代码,而我们提倡代码应该是对修改封闭的; 二是不符合迪米特法则,发奖逻辑和各个下游接口高度耦合,这导致接口的改变将直接影响到代码的组织,使得代码的可维护性降低。
// 奖励服务
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!");
}
}
}
将各个同下游接口交互的功能抽象成单独的服务,封装其参数组装及异常处理,使得发奖主逻辑与其解耦 用策略模式和适配器模式
- 【策略模式】将多个“奖励发放”策略封装成统一接口,以便可以动态选择要使用的策略,实现行为的可扩展与可替换。
- 【适配器模式】将原本不同的服务类(WaimaiService, HotelService, FoodService)统一“适配”为符合 Strategy 接口的策略类,从而避免客户端直接依赖这些不一致的服务接口。
先实现策略模式,将多个发放策略统一为一个接口
// 策略接口
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);
}
}
优雅,太优雅了,但很显然这些类没必要用有状态的,他们本身并不具有有状态的特质。
实现单例模式,并通过注册表来实现自动注册。
// 策略上下文,用于管理策略的注册和获取
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);
}
}

如果用@Component注解会更简单
- 实战一下

通过策略模式和适配器模式,抽象出一个Stratgy接口,新用户普通奖励、新用户梯度、老用户普通一、老用户普通二实现这个接口。 通过单例Bean注入到一个Map中,然后根据key来选择不同的策略
任务模型的设计 - 状态模式 + 发布-订阅
状态的流转我一般是习惯用DAG来实现,使用状态模式直觉上不太像。
而状态变更的通知,可以用观察者模式或发布-订阅
观察者模式
每个想要跟踪状态流转过程的人,需要自己注册到一个ObserverList中,然后在每次状态流转的时候,事件会主动遍历ObserverList,并主动通知ObserverList。
这个过程显然没有解耦。
发布-订阅
对于每个状态的流转,我们都会将其发布一个主题,然后业务方如果想要了解到事件流转状态的话,可以主动订阅这个主题。
这个过程显然解耦的很好。
实战一下

这个也很显然了,无论你是构建一个DAG,还是通过状态模式都能很好的将状态流转保持下去。 实现一个State接口,里面有update方法,对于每个状态都实现这个接口,
责任链
要擅用stream,用stream来实现一个责任链。 一个责任链可以用策略模式来封装,提高代码的可拓展性。
- 实战一下

要实现两个地方,一个是上面的链是否成功,下面的链是否要走
//定义一个抽象的规则
public abstract class BasicRule<CORE_ITEM, T extends RuleContext<CORE_ITEM>>{
//有两个方法,evaluate用于判断是否经过规则执行,execute用于执行具体的规则内容。
public abstract boolean evaluate(T context);
public abstract void execute(T context) {
}
//定义所有的规则具体实现
//规则1:判断服务可用性
public class ServiceAvailableRule extends BasicRule<UserPortrait, UserPortraitRuleContext> {
@Override
public boolean evaluate(UserPortraitRuleContext context) {
TakeawayUserPortraitBasicInfo basicInfo = context.getBasicInfo();
if (basicInfo.isServiceFail()) {
return false;
}
return true;
}
@Override
public void execute(UserPortraitRuleContext context) {}
}
//规则2:判断当前用户属性是否符合当前资源位投放的用户属性要求
public class UserGroupRule extends BasicRule<UserPortrait, UserPortraitRuleContext> {
@Override
public boolean evaluate(UserPortraitRuleContext context) {}
@Override
public void execute(UserPortraitRuleContext context) {
UserPortrait userPortraitPO = context.getData();
if(userPortraitPO.getUserGroup() == context.getBasicInfo().getUserGroup().code) {
context.setValid(true);
} else {
context.setValid(false);
}
}
}
//规则3:判断当前用户是否在投放城市,具体逻辑省略
public class CityInfoRule extends BasicRule<UserPortrait, UserPortraitRuleContext> {}
//规则4:根据用户的活跃度进行资源过滤,具体逻辑省略
public class UserPortraitRule extends BasicRule<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>
//规则执行
public class DefaultRuleEngine{
@Autowired
List<BasicRule> userPortraitRuleChain;
public void invokeAll(RuleContext ruleContext) {
for(Rule rule : userPortraitRuleChain) {
rule.evaluate(ruleContext)
}
}
}