저자 | 두서 프로그래밍
이 문서는 두서 프로그래밍에서 허가 재판된다 (ID : mhcoding)
사업 개발의 경우, 비즈니스 로직의 복잡성은 사업 개발로, 수요는 점점 더 복잡해질 것이다 계정으로 다양한 상황을 위해, 코드가 불가피하게 될 것이다 불가피 많은 경우 - 다른.
경우-다른 일단 너무 많은 코드, 그것은 크게 가독성 및 유지 관리에 영향을 미칠 것입니다.
먼저 가독성, 그것은 너무 많은 중첩 된 경우 - 다른 코드를 말할 필요도 없다 그것이 무엇을 의미하는지 결국 이해하기 어려운 코드를 판독 할 것이다. 코멘트를하지 않고 코드 특히.
당신은 새로운 지점을 추가 할 경우 특히-다른 사람, 다른 지점에 매우 취약, 추가 어려울 것 때문에, 유지 보수를 따랐다.
내가 핵심 응용 프로그램의 지불을 보았다, 응용 프로그램이 비즈니스 온라인 결제 기능을 많이 지원해야하지만, 각 기업은 사용자 정의을 많이 가지고, 핵심 코드의 많은 경우 - 다른 사람의 큰 더미가 있습니다.
각각의 새로운 비즈니스 요구는 시간을 사용자 자신의 논리가 제대로 실행될 수 있도록, 전체 과정의 앞에 배치하면 자신을 볼 수 있습니다. 이 연습은, 그 결과는 상상 할 수 있습니다.
사실, 경우 - 다른 사람이 말을 정확 일반적인 및 널리 사용되는 전략은 모델과 공장 모델을 사용하는 것입니다 당신이 제거 할 수있는 방법은있다 그 생각이 두 가지 디자인 패턴의 사용, IF-다른 코드의 완전한 제거 .
독자는 즉시 자신의 프로젝트에 적용 할 수 있도록 본 논문에서는이 두 가지 디자인 패턴을 결합하는 방법이 기사를 읽은 후, 또한 스프링 프레임 워크와 함께 방법으로, 만약 - 다른 제거하고 있습니다.
이 문서에서는 코드의 일부를 제공하지만, 저자는 간단한 예제와 의사 코드의 다른 형태의 콘텐츠가 너무 지루하지되어 있는지 확인하려고합니다.
오심 경우 - 다른
우리는 이러한 수요가, 테이크 어웨이 플랫폼을 가정 해 봅시다 :
1, 홍보 목적의 플랫폼에서 테이크 아웃 가게, 세 가지가없는 20 % 할인 슈퍼 계열사, 일반 회원 할인 10 % 할인 및 일반 사용자를 포함 회원 다양한 혜택을 설정합니다.
2, 지불은, 사용자의 회원 레벨에 따라, 당신은 어떤 사용자, 다음 할인에 맞춰 할인 전략으로 인해 양을 계산 알 수있는 사용자를 원한다.
3, 사업 개발과 함께, 새로운 요구는 단일 금액으로 가게 독점 회원이 혜택을 누릴 수있을 때보다 $ 30이 필요합니다.
4、接着,又有一个变态的需求,如果用户的超级会员已经到期了,并且到期时间在一周内,那么就对用户的单笔订单按照超级会员进行折扣,并在收银台进行强提醒,引导用户再次开通会员,而且折扣只进行一次。
那么,我们可以看到以下伪代码:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (用户是专属会员) {
if (订单金额大于30元) {
returen 7折价格;
}
}
if (用户是超级会员) {
return 8折价格;
}
if (用户是普通会员) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
return 原价;
}
以上,就是对于这个需求的一段价格计算逻辑,使用伪代码都这么复杂,如果是真的写代码,那复杂度可想而知。
这样的代码中,有很多if-else,并且还有很多的if-else的嵌套,无论是可读性还是可维护性都非常低。
那么,如何改善呢?
策略模式
接下来,我们尝试引入策略模式来提升代码的可维护性和可读性。
首先,定义一个接口:
/**
* @author mhcoding
*/
public interface UserPayService {
/**
* 计算应付价格
*/
public BigDecimal quote(BigDecimal orderPrice);
}
接着定义几个策略类:
/**
* @author mhcoding
*/
public class ParticularlyVipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if (消费金额大于30元) {
return 7折价格;
}
}
}
public class SuperVipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return 8折价格;
}
}
public class VipPayService implements UserPayService {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
}
引入了策略之后,我们可以按照如下方式进行价格计算:
/**
* @author mhcoding
*/
public class Test {
public static void main(String[] args) {
UserPayService strategy = new VipPayService();
BigDecimal quote = strategy.quote(300);
System.out.println("普通会员商品的最终价格为:" + quote.doubleValue());
strategy = new SuperVipPayService();
quote = strategy.quote(300);
System.out.println("超级会员商品的最终价格为:" + quote.doubleValue());
}
}
以上,就是一个例子,可以在代码中new出不同的会员的策略类,然后执行对应的计算价格的方法。这个例子以及策略模式的相关知识,读者可以在《如何给女朋友解释什么是策略模式?》一文中学习。
但是,真正在代码中使用,比如在一个web项目中使用,上面这个Demo根本没办法直接用。
首先,在web项目中,上面我们创建出来的这些策略类都是被Spring托管的,我们不会自己去new一个实例出来。
其次,在web项目中,如果真要计算价格,也是要事先知道用户的会员等级,比如从数据库中查出会员等级,然后根据等级获取不同的策略类执行计算价格方法。
那么,web项目中真正的计算价格的话,伪代码应该是这样的:
/**
* @author mhcoding
*/
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
if (vipType == 专属会员) {
//伪代码:从Spring中获取超级会员的策略对象
UserPayService strategy = Spring.getBean(ParticularlyVipPayService.class);
return strategy.quote(orderPrice);
}
if (vipType == 超级会员) {
UserPayService strategy = Spring.getBean(SuperVipPayService.class);
return strategy.quote(orderPrice);
}
if (vipType == 普通会员) {
UserPayService strategy = Spring.getBean(VipPayService.class);
return strategy.quote(orderPrice);
}
return 原价;
}
通过以上代码,我们发现,代码可维护性和可读性好像是好了一些,但是好像并没有减少if-else啊。
其实,在之前的《如何给女朋友解释什么是策略模式?》一文中,我们介绍了很多策略模式的优点。但是,策略模式的使用上,还是有一个比较大的缺点的:
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
也就是说,虽然在计算价格的时候没有if-else了,但是选择具体的策略的时候还是不可避免的还是要有一些if-else。
另外,上面的伪代码中,从Spring中获取会员的策略对象我们是伪代码实现的,那么代码到底该如何获取对应的Bean呢?
接下来我们看如何借助Spring和工厂模式,解决上面这些问题。
工厂模式
为了方便我们从Spring中获取UserPayService的各个策略类,我们创建一个工厂类:
/**
* @author mhcoding
*/
public class UserPayServiceStrategyFactory {
private static Map<String,UserPayService> services = new ConcurrentHashMap<String,UserPayService>();
public static UserPayService getByUserType(String type){
return services.get(type);
}
public static void register(String userType,UserPayService userPayService){
Assert.notNull(userType,"userType can't be null");
services.put(userType,userPayService);
}
}
这个UserPayServiceStrategyFactory中定义了一个Map,用来保存所有的策略类的实例,并提供一个getByUserType方法,可以根据类型直接获取对应的类的实例。还有一个register方法,这个后面再讲。
有了这个工厂类之后,计算价格的代码即可得到大大的优化:
/**
* @author mhcoding
*/
public BigDecimal calPrice(BigDecimal orderPrice,User user) {
String vipType = user.getVipType();
UserPayService strategy = UserPayServiceStrategyFactory.getByUserType(vipType);
return strategy.quote(orderPrice);
}
以上代码中,不再需要if-else了,拿到用户的vip类型之后,直接通过工厂的getByUserType方法直接调用就可以了。
通过策略+工厂,我们的代码很大程度的优化了,大大提升了可读性和可维护性。
但是,上面还遗留了一个问题,那就是UserPayServiceStrategyFactory中用来保存所有的策略类的实例的Map是如何被初始化的?各个策略的实例对象如何塞进去的呢?
Spring Bean的注册
还记得我们前面定义的UserPayServiceStrategyFactory中提供了的register方法吗?他就是用来注册策略服务的。
接下来,我们就想办法调用register方法,把Spring通过IOC创建出来的Bean注册进去就行了。
这种需求,可以借用Spring种提供的InitializingBean接口,这个接口为Bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。
那么,我们将前面的各个策略类稍作改造即可:
/**
* @author mhcoding
*/
@Service
public class ParticularlyVipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if (消费金额大于30元) {
return 7折价格;
}
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("ParticularlyVip",this);
}
}
@Service
public class SuperVipPayService implements UserPayService ,InitializingBean{
@Override
public BigDecimal quote(BigDecimal orderPrice) {
return 8折价格;
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("SuperVip",this);
}
}
@Service
public class VipPayService implements UserPayService,InitializingBean {
@Override
public BigDecimal quote(BigDecimal orderPrice) {
if(该用户超级会员刚过期并且尚未使用过临时折扣){
临时折扣使用次数更新();
returen 8折价格;
}
return 9折价格;
}
@Override
public void afterPropertiesSet() throws Exception {
UserPayServiceStrategyFactory.register("Vip",this);
}
}
只需要每一个策略服务的实现类都实现InitializingBean接口,并实现其afterPropertiesSet方法,在这个方法中调用UserPayServiceStrategyFactory.register即可。
这样,在Spring初始化的时候,当创建VipPayService、SuperVipPayService和ParticularlyVipPayService的时候,会在Bean的属性初始化之后,把这个Bean注册到UserPayServiceStrategyFactory中。
以上代码,其实还是有一些重复代码的,这里面还可以引入模板方法模式进一步精简,这里就不展开了。
还有就是,UserPayServiceStrategyFactory.register调用的时候,第一个参数需要传一个字符串,这里的话其实也可以优化掉。比如使用枚举,或者在每个策略类中自定义一个getUserType方法,各自实现即可。
总结
本文,我们通过策略模式、工厂模式以及Spring的InitializingBean,提升了代码的可读性以及可维护性,彻底消灭了一坨if-else。
文中的这种做法,大家可以立刻尝试起来,这种实践,是我们日常开发中经常用到的,而且还有很多衍生的用法,也都非常好用。有机会后面再介绍。
其实,如果读者们对策略模式和工厂模式了解的话,文中使用的并不是严格意义上面的策略模式和工厂模式。
首先,策略模式中重要的Context角色在这里面是没有的,没有Context,也就没有用到组合的方式,而是使用工厂代替了。
另外,这里面的UserPayServiceStrategyFactory其实只是维护了一个Map,并提供了register和get方法而已,而工厂模式其实是帮忙创建对象的,这里并没有用到。
所以,读者不必纠结于到底是不是真的用了策略模式和工厂模式。而且,这里面也再扩展一句,所谓的GOF 23种设计模式,无论从哪本书或者哪个博客看,都是简单的代码示例,但是我们日常开发很多都是基于Spring等框架的,根本没办法直接用的。
所以,对于设计模式的学习,重要的是学习其思想,而不是代码实现!!!
如果读者们感兴趣,后续可以出更多的设计模式和Spring等框架结合使用的最佳实践。希望通过这样的文章,读者可以真正的在代码中使用上设计模式。
1月16日晚8:00, 哈工大屈教授在线直播课---『看得见 』的数学,带大家解密计算机视觉背后的数学知识!
热 文 推 荐
你点的每个“在看”,我都认真当成了喜欢