欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

关于美团设计模式的两三件事

最编程 2024-05-03 17:11:20
...

设计模式是众多软件开发人员经过长时间的试错和应用总结出来的,解决特定问题的一系列方案。现行的部分教材在介绍设计模式时,有些会因为案例脱离实际应用场景而令人费解,有些又会因为场景简单而显得有些小题大做。本文会结合在美团金融服务平台设计开发时的经验,结合实际的案例,并采用“师生对话”这种相对诙谐的形式去讲解三类常用设计模式的应用。希望能对想提升系统设计能力的同学有所帮助或启发。


引言



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


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


“嗯,可以考虑通过教材系统学习,从注释、命名、方法和异常等多方面实现整洁代码。”


“然而,我想说的是,我的代码是符合各种编码规范的,但是从实现上却总是感觉不够简洁,而且总是需要反复修改!”学生小明叹气道。


老师看了看小明的代码说:“我明白了,这是系统设计上的缺陷。总结就是抽象不够、可读性低、不够健壮。”


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

老师敲了敲小明的头:“不要太浮躁,没有什么方法能让你立刻成为系统设计专家。但是对于你的问题,我想设计模式可以帮到你。”


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


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


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


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


奖励的发放策略



第一天,老师问小明:“你知道活动营销吗?”


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


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


因为之前有过类似的开发经验,拿到需求的小明二话不说开始了编写起了代码:


// 奖励服务
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;
}


然后,小明对开始编写状态变更功能:


上一篇: 代码美团代码托管平台的演变与实践

下一篇: 科技年鉴:MMT 技术沙龙汇编赠品 - 85 场演讲,70 多个小时的视频 - MMT 在线沙龙