通过代码实例说明如何化腐朽为优雅

简介: 一、背景 ​  最近我负责的活动促销系统中要在审批的时候增加计算参加活动的商品的毛利率的需求。但是我负责打辅助,主要是同事负责具体开发,我了解了他的实现方式思路以后,果断拒绝了,并给出了我的解决方案以及优点,他发现我的方案确实扩展性和可维护性更好以后就采用了,本文就来通过这个实例来说明如何让本腐朽的代码变得优雅起来。

一、背景

​  最近我负责的活动促销系统中要在审批的时候增加计算参加活动的商品的毛利率的需求。但是我负责打辅助,主要是同事负责具体开发,我了解了他的实现方式思路以后,果断拒绝了,并给出了我的解决方案以及优点,他发现我的方案确实扩展性和可维护性更好以后就采用了,本文就来通过这个实例来说明如何让本腐朽的代码变得优雅起来。

二、需求描述

​   活动系统中共有7中活动类型,分别为:价格折扣活动、满减活动、满赠活动、换购活动、满折活动、抢购活动、N元任选活动。每种活动类型都有自己的毛利率的计算方式。要求根据不同的活动类型来通过不同的计算方式计算参加活动的商品的毛利率。

三、开发运行环境

  1. Maven 3.3.9
  2. Spring 4.2.6.RELEASE
  3. JDK 1.7
  4. IDEA 15.04

四、同事方案1

  直接通过switch/case的方式判断不同的活动类型,然后每种类型给出不同的计算方式。

package com.hafiz.www.domain;

/**
 * @author hafiz.zhang
 * @description: 活动毛利率计算器
 * @date Created in 2017/11/28 20:52.
 */
public class Calculator {

    public static String calculate(Integer campaignType) {
        switch (campaignType) {
            case 1:
                return "价格折扣活动计算毛利率";
            case 2:
                return "满减活动计算毛利率";
            case 3:
                return "满赠活动计算毛利率";
            case 4:
                return "换购活动计算毛利率";
            case 5:
                return "满折活动计算毛利率";
            case 6:
                return "抢购活动计算毛利率";
            case 7:
                return "N元任选活动计算毛利率";
            default:
                return "错误的活动类型";
        }
    }
}

 

缺点:虽然写起来很简单,但是可扩展性差,或者说不具备可扩展性,若每种活动类型的计算毛利率方式都比较复杂,则Calculator类就会变得臃肿不堪。可维护性很差。完全就是面向过程的开发方式。被我一票拒绝。并告诉他通过定义接口,然后各种活动类型实现自己的计算方式,然后使用简单工厂模式通过Java的多态来实现。

五、同事方案2

定义计算接口,被针对每种活动给出不同的实现。

1.定义计算接口

package com.hafiz.www.handler;

import com.hafiz.www.enums.CampaignTypeEnum;

/**
 * @author hafiz.zhang
 * @description: 计算毛利率接口
 * @date Created in 2017/11/28 20:57.
 */
public interface ICampaignHandler {
    /**
     * 计算毛利率
     * @return
     */
    String calculate();
}

2.价格折扣活动实现

package com.hafiz.www.handler.impl;

import com.hafiz.www.enums.CampaignTypeEnum;
import com.hafiz.www.event.CampaignHandlerEvent;
import com.hafiz.www.handler.ICampaignHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Description: 价格折扣活动操作器
 * @author hafiz.zhang
 * @create 2017/11/28 20:52.
 */
public class PriceDiscountCampaignHandler implements ICampaignHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(PriceDiscountCampaignHandler.class);

    @Override
    public String calculate() {
        LOGGER.info("价格折扣活动计算毛利率");
        return "价格折扣活动计算毛利率";
    }
}

3.抢购类型活动实现

package com.hafiz.www.handler.impl;

import com.hafiz.www.enums.CampaignTypeEnum;
import com.hafiz.www.event.CampaignHandlerEvent;
import com.hafiz.www.handler.ICampaignHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Description: 抢购活动操作器
 * @author: hafiz.zhang
 * @create: 2017/11/28 20:52.
 */
public class PanicBuyCampaignHandler implements ICampaignHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(PanicBuyCampaignHandler.class);

    @Override
    public String calculate() {
        LOGGER.info("抢购活动计算毛利率");
        return "抢购活动计算毛利率";
    }
}

 

等等还有剩下各种活动自己的实现,此处为避免篇幅过长略去。

4.简单工厂

package com.hafiz.www.handler;

import com.hafiz.www.handler.impl.CheapenOtherCampaignHandler;
import com.hafiz.www.handler.impl.FullCutCampaignHandler;
import com.hafiz.www.handler.impl.FullDiscountCampaignHandler;
import com.hafiz.www.handler.impl.FullGIftCampaignHandler;
import com.hafiz.www.handler.impl.OptionCampaignHandler;
import com.hafiz.www.handler.impl.PanicBuyCampaignHandler;
import com.hafiz.www.handler.impl.PriceDiscountCampaignHandler;

/**
 * @author hafiz.zhang
 * @description: 操作器工厂类
 * @date Created in 2017/11/28 22:06.
 */
public class CampaignHandlerFactory {
    public static ICampaignHandler getHandler(Integer campaignType) {
        switch (campaignType) {
            case 1:
                return new PriceDiscountCampaignHandler();
            case 2:
                return new FullCutCampaignHandler();
            case 3:
                return new FullGIftCampaignHandler();
            case 4:
                return new CheapenOtherCampaignHandler();
            case 5:
                return new FullDiscountCampaignHandler();
            case 6:
                return new PanicBuyCampaignHandler();
            case 7:
                return new OptionCampaignHandler();
            default:
                throw new RuntimeException("错误的活动类型");
        }
    }
}

 

这样比第一版稍好一点,代码已经优雅了很多,可扩展性也好了很多,如果一旦增加新的活动类型,只需要新写一个新活动计算毛利率的操作器实现类就好了,然后再在工厂类中增加对应的case.但是还是没有很完美,这样需要每次都修改工厂类,不完美!

六、我的方案:使用Spring事件通知来实现简单工厂

1.接口定义

package com.hafiz.www.handler;

import com.hafiz.www.enums.CampaignTypeEnum;

/**
 * @author hafiz.zhang
 * @description: 计算毛利率接口
 * @date Created in 2017/11/28 20:57.
 */
public interface ICampaignHandler {

    CampaignTypeEnum getCampaignType();
    /**
     * 计算毛利率
     * @return
     */
    String calculate();
}

2.活动操作器自定义事件

package com.hafiz.www.event;

import com.hafiz.www.handler.ICampaignHandler;
import org.springframework.context.ApplicationEvent;

/**
 * @author hafiz.zhang
 * @description: 活动操作器事件
 * @date Created in 2017/11/28 21:02.
 */
public class CampaignHandlerEvent extends ApplicationEvent {

    public CampaignHandlerEvent(ICampaignHandler source) {
        super(source);
    }
}

2.价格折扣现类

package com.hafiz.www.handler.impl;

import com.hafiz.www.enums.CampaignTypeEnum;
import com.hafiz.www.event.CampaignHandlerEvent;
import com.hafiz.www.handler.ICampaignHandler;
import com.hafiz.www.spring.SpringAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Description: 价格折扣活动操作器
 * @author hafiz.zhang
 * @create 2017/11/28 20:52.
 */
@Component
public class PriceDiscountCampaignHandler implements ICampaignHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(PriceDiscountCampaignHandler.class);

    @PostConstruct
    public void init() {
        CampaignHandlerEvent event = new CampaignHandlerEvent(this);
        SpringAware.getApplicationContext().publishEvent(event);
    }

    @Override
    public CampaignTypeEnum getCampaignType() {
        return CampaignTypeEnum.PRICE_DISCOUNT;
    }

    @Override
    public String calculate() {
        LOGGER.info("价格折扣活动计算毛利率");
        return "价格折扣活动计算毛利率";
    }
}

3.抢购类活动实现类

package com.hafiz.www.handler.impl;

import com.hafiz.www.enums.CampaignTypeEnum;
import com.hafiz.www.event.CampaignHandlerEvent;
import com.hafiz.www.handler.ICampaignHandler;
import com.hafiz.www.spring.SpringAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @Description: 抢购活动操作器
 * @author: hafiz.zhang
 * @create: 2017/11/28 20:52.
 */
@Component
public class PanicBuyCampaignHandler implements ICampaignHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(PanicBuyCampaignHandler.class);

    @PostConstruct
    public void init() {
        CampaignHandlerEvent event = new CampaignHandlerEvent(this);
        SpringAware.getApplicationContext().publishEvent(event);
    }

    @Override
    public CampaignTypeEnum getCampaignType() {
        return CampaignTypeEnum.PANIC_BUY;
    }

    @Override
    public String calculate() {
        LOGGER.info("抢购活动计算毛利率");
        return "抢购活动计算毛利率";
    }
}

还有另外几种活动类型的实现方式,为了避免篇幅过长不一一列举。

4.新工厂实现方式

package com.hafiz.www.handler;

import com.hafiz.www.enums.CampaignTypeEnum;
import com.hafiz.www.event.CampaignHandlerEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author hafiz.zhang
 * @description: 活动操作器工厂类
 * @date Created in 2017/11/28 20:59.
 */
@Component
public class CampaignHandlerFactory implements ApplicationListener<CampaignHandlerEvent> {

    private static final Logger LOGGER = LoggerFactory.getLogger(CampaignHandlerFactory.class);

    private static Map<CampaignTypeEnum, ICampaignHandler> handlerMap = new ConcurrentHashMap<>();

    /**
     * 通过活动类型获取对应的操作器
     *
     * @param discountType 活动类型
     *
     * @return
     */
    public static ICampaignHandler getHandler(Integer discountType) {
        CampaignTypeEnum discountTypeEnum = CampaignTypeEnum.getEnumById(discountType);
        ICampaignHandler handler = handlerMap.get(discountTypeEnum);
        return handler;
    }

    /**
     * 注册绑定不同类型活动对应的活动操作器
     *
     * @param handler 活动操作器
     *
     */
    private void registerHandler(ICampaignHandler handler) {
        CampaignTypeEnum discountType = handler.getCampaignType();
        LOGGER.info("开始绑定{}类型的活动处理器", discountType.getName());
        handlerMap.put(discountType, handler);
    }

    @Override
    public void onApplicationEvent(CampaignHandlerEvent event) {
        ICampaignHandler handler = (ICampaignHandler) event.getSource();
        this.registerHandler(handler);
    }
}

说明 :新的工厂类中通过实现Spring的事件监听,接收到监听以后,直接获取事件源,保存在本地Map中,就很优雅。这样新增活动类型的时候工厂类完全不需要修改,而且现有类也不需要修改,只需要进行对新的活动类型扩展就好了。符合了软件开发中的开闭环原则。看起来很棒~

5.工具类SpringAware

package com.hafiz.www.spring;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

/**
 * @author hafiz.zhang
 * @description: spring 上下文工具类
 * @date Created in 2017/11/28 21:07.
 */
public class SpringAware implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;

    public SpringAware() {
    }

    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        applicationContext = ac;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    public static <T> T getBean(String name) {
        return (T) applicationContext.getBean(name);
    }

    public static void rollBack() {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

 

其他Spring的配置以及测试用例等代码不再单独贴出,源码地址:https://github.com/hafizzhang/code-optimize.git

七、总结

​  在实际工作中,我们会碰到很多这种可以通过设计模式以及Java特性来实现优雅代码的机会,这个时候我们一定不能只为了省事写出烂代码,这样不但对自己的成长没有任何的好处,而且会对以后维护者造成很大困扰,我们要在保证工期和质量的前提下尽量的把代码写的优雅一点,尽量考虑到可扩展性以及可维护性等。这样才能在技术上有所提高,才能够自我成长。两全其美,何乐而不为呢?

相关文章
|
3天前
|
Web App开发 JSON 定位技术
更多示例代码
这段代码展示了EdgeRoutine的多个功能示例,包括处理不同的请求类型(如hello world、地理位置信息获取、转发请求等)、实现AB测试、多源拼接、预加载、竞速请求、简单边缘侧日志记录、重定向(基于UserAgent和地理位置信息)及拒绝爬虫访问等。每个功能通过独立函数实现,并在主处理函数中根据请求类型调用相应的处理逻辑。具体效果可参考[Yopian的示例](https://www.yopian.com/sitemap/post.xml)。
13 4
|
12天前
|
算法 Java 开发者
你的Java代码还可以这样写:JavaIO的简单代码实例和展示
在编程世界中,Java无疑是璀璨的明星。本文通过两个示例介绍抽象类与接口的魅力:首先,通过抽象类`Shape`及其子类`Circle`和`Rectangle`展示多态性;接着,通过接口`PerimeterCalculator`为形状类添加周长计算功能,展现了接口的灵活性。掌握这两者将助你在Java编程中更进一步,应对复杂项目游刃有余。
29 0
|
2月前
|
Java
for循环的基本使用案例
摘要:本文介绍如何使用for循环在Java中打印一个4行5列的星号(*)矩阵。通过嵌套循环实现,外层循环控制行数,需运行4次,内层循环控制每行的列数,需运行5次以打印5个星号。每次外层循环结束后应添加换行符确保下一行从新开始,否则将无法形成正确的矩阵形状。 字符数:194 Markdown格式: ``` 摘要:本文介绍如何使用for循环在Java中打印一个4x5的星号(*)矩阵。通过嵌套循环实现,外层循环控制4行,内层循环每行打印5个星号。每次外层循环后需添加换行确保下一行从头开始,否则矩阵形状无法正确呈现。 ``` 字符数:168
|
3月前
JavaIO的简单代码实例和展示
JavaIO的简单代码实例和展示
17 1
|
3月前
|
编译器 C语言 C++
|
4月前
|
Java
代码实例演示Java字符串与输入流互转
代码实例演示Java字符串与输入流互转
21 1
|
4月前
|
前端开发 JavaScript
HTML标签导读及实用代码实例
HTML标签导读及实用代码实例
36 0
|
4月前
|
C语言
C语言中的函数定义与调用详解及代码实例
C语言中的函数定义与调用详解及代码实例
40 0
|
4月前
|
存储 C++
【C++】function包装器全解(代码演示,例题演示)
【C++】function包装器全解(代码演示,例题演示)
|
存储 Java
【JavaSE专栏32】Java函数定义、调用和主函数
【JavaSE专栏32】Java函数定义、调用和主函数
282 0
【JavaSE专栏32】Java函数定义、调用和主函数