代码优雅之道

平时我们写代码呢,多数情况都是流水线式写代码 基本就可以实现业务逻辑了。如何在写代码中找到乐趣呢,我觉得,好的方式就是:使用设计模式优化自己的代码。今天记录下日常工作中,我都使用过哪些设计模式。

image.png

1.策略模式:
假设有这样的业务场景,扫码登录:根据移动端扫码动作采取不同的逻辑。
示例:

/**
 * 如果是依靠if else来写这个场景:
 * 如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低。
 * 如果你需要接入一种新的解析类型,那只能在原有代码上修改。
 * 说得专业一点的话,就是if else代码,违背了面向对象编程的开闭原则以及单一原则。
 * 如果你的代码就是酱紫:有多个if...else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。
 */

/**
 * 策略模式酱紫实现的: 一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法),和不同策略的差异化实现。
 */
public abstract class AbstractWebLoginStrategyService {
    // 属于哪种动作类型
    abstract WebLoginStrategyEnum getType();
    // 封装的公用算法(具体的动作逻辑)
    public abstract void userLogin();
}

/**
 * @author xx
 * @since 不同策略的差异化实现:扫码动作
 */
@Service
public class ScanQrServiceImpl extends AbstractWebLoginStrategyService {
    @Autowired
    public ScanQrServiceImpl() {}
    @Override
    public WebLoginStrategyEnum getType() {
        return WebLoginStrategyEnum.SCAN_QR;
    }
    @Override
    public void userLogin() {
        System.out.println("二维码状态变更为已扫码");
    }
}

/**
 * @author xx
 * @since 不同策略的差异化实现: 确认登录动作
 */
@Service
public class ConfirmLoginServiceImpl extends AbstractWebLoginStrategyService {
    @Override
    public WebLoginStrategyEnum getType() {
        return WebLoginStrategyEnum.CONFIRM_LOGIN;
    }
    @Override
    public void userLogin() {
        System.out.println("二维码状态变更为已登录");
    }
}

/**
 * @author xx
 * @since 不同策略的差异化实现:取消扫码动作
 */
@Service
public class CancelLoginServiceImpl extends AbstractWebLoginStrategyService {
    @Override
    public WebLoginStrategyEnum getType() {
        return WebLoginStrategyEnum.CANCEL_LOGIN;
    }
    @Override
    public void userLogin() {
        System.out.println("二维码状态变更为已失效");
    }
}

/**
 * 工厂模式与策略模式配合使用
 */
@Component
public class WebLoginStrategyFactory{
    private static final Map<WebLoginStrategyEnum, AbstractWebLoginStrategyService> STRATEGY_SERVICE_MAP = new HashMap<>();
    /**
     * 把对应的策略初始化到map中
     * 借助spring的特性定义的工厂模式
     * 一般情况下工厂与其它模式配合使用,如当前的策略模式
     * @param strategyServices 所有注入的实现类
     */
    @Autowired
    public WebLoginStrategyFactory(List<AbstractWebLoginStrategyService> strategyServices) {
        Map<WebLoginStrategyEnum, AbstractWebLoginStrategyService> itemDataMap = strategyServices.stream().collect(Collectors.toMap(AbstractWebLoginStrategyService::getType, service -> service));
        STRATEGY_SERVICE_MAP.putAll(itemDataMap);
    }
    public AbstractWebLoginStrategyService getService(WebLoginStrategyEnum strategyEnum){
        return STRATEGY_SERVICE_MAP.get(strategyEnum);
    }
}

/**
 * 动作枚举
 */
public enum WebLoginStrategyEnum {
    SCAN_QR(1,"移动端扫描PC二维码"),
    CONFIRM_LOGIN(2,"移动端确认登录"),
    CANCEL_LOGIN(3,"移动端取消登录"),
    ;

    WebLoginStrategyEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    private Integer code;

    private String message;

    /**
     * Gets code.
     *
     * @return the code
     */
    public Integer getCode() {
        return code;
    }

    /**
     * Gets message.
     *
     * @return the message
     */
    public String getMessage() {
        return message;
    }

    /**
     * Get by code enable enum.
     *
     * @param code the code
     * @return the enable enum
     */
    public static WebLoginStrategyEnum getByCode(Integer code) {
        if (code == null) {
            return null;
        }
        return Arrays.stream(WebLoginStrategyEnum.values())
                .filter(goodsTypeEnums -> goodsTypeEnums.getCode() == code).findFirst().orElse(null);
    }
}

    /**
     * 策略模式使用
     */
    @RequestMapping("/strategy")
    public void strategy(@RequestParam(value = "qrCode") Integer type){
        AbstractWebLoginStrategyService service = strategyFactory.getService(WebLoginStrategyEnum.getByCode(type));
        service.userLogin();
    }

2.责任链模式:
我们来看一个常见的业务场景,下订单接口基本的逻辑,一般有参数非空校验、安全校验、黑名单校验、规则拦截等等..,当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。
示例:

/**
 * 责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递。
 * 责任链的使用:1.一个接口或者抽象类,2.每个对象差异化处理,3.对象链(数组)初始化(连起来)
 */
public abstract class AbstractHandler {
    //责任链中的下一个对象
    private AbstractHandler nextHandler;
    /**
     * 责任链的下一个对象
     */
    public void setNextHandler(AbstractHandler nextHandler){
        this.nextHandler = nextHandler;
    }
    /**
     * 具体参数拦截逻辑,给子类去实现
     */
    public void filter(String param) {
        boolean b = doFilter(param);
        if(!b){
            throw new RuntimeException("检查不通过");
        }
        if (getNextHandler() != null) {
            getNextHandler().filter(param);
        }
    }
    public AbstractHandler getNextHandler() {
        return nextHandler;
    }
    abstract boolean doFilter(String param);
}

/**
 *  差异化处理:黑名单校验对象
 */
@Component
@Order(3) //校验顺序排第3
public class CheckBlackFilterObject extends AbstractHandler {
    @Override
    public boolean doFilter(String param) {
        //invoke black list check
        System.out.println("校验黑名单");
        return true;
    }
}

@Component
@Order(1) //顺序排第1,最先校验
public class CheckParamFilterObject extends AbstractHandler {
    @Override
    public boolean doFilter(String param) {
        System.out.println("非空参数检查");
        return true;
    }
}

@Component
@Order(2) //校验顺序排第2
public class CheckSecurityFilterObject extends AbstractHandler {
    @Override
    public boolean doFilter(String param) {
        //invoke Security check
        System.out.println("安全调用校验");
        return true;
    }
}

@Component
@Order(4) //校验顺序排第4
public class CheckRuleFilterObject extends AbstractHandler {
    @Override
    public boolean doFilter(String param) {
        //check rule
        System.out.println("check rule");
        return true;
    }
}

/**
 * 责任链服务: 对象链连起来(初始化)&& 使用
 */
@Component("ChainPatternService")
public class ChainPatternService {
    //自动注入各个责任链的对象
    @Autowired
    private List<AbstractHandler> abstractHandleList;
    private AbstractHandler abstractHandler;
    //spring注入后自动执行,责任链的对象连接起来
    @PostConstruct
    public void initializeChainFilter(){
        for(int i = 0;i<abstractHandleList.size();i++){
            if(i == 0){
                abstractHandler = abstractHandleList.get(0);
            }else{
                AbstractHandler currentHander = abstractHandleList.get(i - 1);
                AbstractHandler nextHander = abstractHandleList.get(i);
                currentHander.setNextHandler(nextHander);
            }
        }
    }
    //直接调用这个方法使用
    public void exec(String param) {
        abstractHandler.filter(param);
    }
}

     /**
     * 责任链模式:执行
     */
    @RequestMapping("/responsibility")
    public void responsibility(){
        String param = "";
        chainPatternService.exec(param);
        System.out.println("执行具体业务");
    }

3.模板方法模式:
若一些通用的方法却在每一个子类都重复写了一遍, 则用模板方法模式优化。
示例:

/**
 * 假设我们有这么一个业务场景:内部系统不同商户,调用我们系统接口,去跟外部第三方系统交互(http方式)
 * 会经历这几个流程:查询商户信息->对请求报文加签->发送http请求出去->对返回的报文验签
 * 这里,有的商户可能是走代理请求出去的,有的是走直连请求。
 * 那么除了发送请求是不一样 其它的(查询商户信息,对请求报文加密,对返回的报文验签)都是重复的。
 * 那就把重复的代码用模板方法模式优化。
 */

/**
 * 模板方法使用:一个骨架流程抽象类(抽象方法放一起)->确定的共同方法步骤放到抽象类(去除抽象方法标记)->不确定的步骤给子类去差异化实现。
 * 抽象类定义骨架流程(查询商户信息,加签,http请求,验签)
 */
public abstract class AbstractMerchantService  {
    public void queryMerchantInfo(){
        System.out.println("查询商户信息");
    }
    public void signature(){
        System.out.println("加签");
    }
    public void httpRequest(){ System.out.println("http 请求");}
    public void verifySinature(){ System.out.println("验签");}
    //模板方法流程
    String handlerTempPlate(String req){
        //查询商户信息
        queryMerchantInfo();
        //加签
        signature();
        //http 请求
        httpRequest();
        // 验签
        verifySinature();
        return "result:".concat(req);
    }
    // Http是否走代理(提供给子类实现)
    abstract boolean isRequestByProxy();
}

@Component
public class CompanyAServiceImpl extends AbstractMerchantService {
    public String hander(String req){
        return handlerTempPlate(req);
    }
    // 走http代理的
    public boolean isRequestByProxy(){
        System.out.println("差异化实现 true");
        return true;
    }
}

@Component
public class CompanyBServiceImpl extends AbstractMerchantService {
    public String hander(String req) {
        return handlerTempPlate(req);
    }
    // 不走代理的
    public boolean isRequestByProxy() {
        System.out.println("差异化实现 false");
        return false;
    }
}
    /**
     * 模板方法模式使用
     */
    @RequestMapping("/templateMethod")
    public void templateMethod(){
        companyBServiceImpl.hander("参数1");
        companyBServiceImpl.isRequestByProxy();
        companyAServiceImpl.hander("参数2");
        companyAServiceImpl.isRequestByProxy();
    }

2.观察者模式:
使用场景: 完成某件事情后,异步通知场景。如登陆成功或注册成功,发个IM消息等等。
示例1:

/**
 * 观察者模式使用: 一个被观察者的类Observerable->多个观察者Observer->观察者的差异化实现。
 */

/**
 * 一个被观察的类
 */
@Component
public class Observerable {
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;
    public int getState() {
        return state;
    }
    public void setState(int state) {
        notifyAllObservers(state);
    }
    //添加观察者
    public Observerable addServer(Observer observer){
        observers.add(observer);
        return this;
    }
    //移除观察者
    public void removeServer(Observer observer){
        observers.remove(observer);
    }
    //通知
    public void notifyAllObservers(int state){
        if(state != 1){
            System.out.println("不是通知的状态");
            return ;
        }
        for (Observer observer : observers) {
            observer.doEvent();
        }
    }
}

/**
 * 观察者
 */
interface Observer {
    void doEvent();
}

//Im消息
public class EmailObserver implements Observer{
    public void doEvent(){
        System.out.println("发送Email消息");
    }
}

//Im消息
public class IMMessageObserver implements Observer{
    public void doEvent(){
        System.out.println("发送IM消息");
    }
}

//Im消息
public class MobileNoObserver implements Observer{
    public void doEvent(){
        System.out.println("发送短信消息");
    }
}

    /**
     * 观察者模式
     */
    @RequestMapping("/observer")
    public void observer(){
        // 没必要链式注入,其实可以考虑利用spring来初始化进观察者的。
        observerableService.addServer(new EmailObserver())
                .addServer(new IMMessageObserver())
                .addServer(new MobileNoObserver());
        observerableService.setState(1);
    }

示例2:

/**
 * 经典观察者模式封装:EventBus实战
 * 自己写一套观察者模式的代码,还是有点小麻烦。实际上,Guava EventBus就封装好了,它提供一套基于注解的事件总线,api可以灵活的使用。
 *         <!--guava依赖-->
 *         <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
 *         <dependency>
 *             <groupId>com.google.guava</groupId>
 *             <artifactId>guava</artifactId>
 *             <version>22.0</version>
 *         </dependency>
 */

/**
 * 类似被观察者
 */
public class EventBusCenter {
    private static EventBus eventBus = new EventBus();
    private EventBusCenter() {}
    public static EventBus getInstance() {
        return eventBus;
    }
    //添加观察者
    public static void register(Object obj) {
        eventBus.register(obj);
    }
    //移除观察者
    public static void unregister(Object obj) {
        eventBus.unregister(obj);
    }
    //把消息推给观察者
    public static void post(Object obj) {
        eventBus.post(obj);
    }
}

/**
 * 观察者
 */
public class EventListener {
    @Subscribe //加了订阅,这里标记这个方法是事件处理方法
    public void handle(NotifyEvent notifyEvent) {
        System.out.println("发送IM消息" + notifyEvent.getImNo());
        System.out.println("发送短信消息" + notifyEvent.getMobileNo());
        System.out.println("发送Email消息" + notifyEvent.getEmailNo());
    }
}

/**
 * 通知事件类
 */
public class NotifyEvent {
    private String mobileNo;
    private String emailNo;
    private String imNo;
    public NotifyEvent(String mobileNo, String emailNo, String imNo) {
        this.mobileNo = mobileNo;
        this.emailNo = emailNo;
        this.imNo = imNo;
    }
    public String getMobileNo() {
        return mobileNo;
    }
    public String getEmailNo() {
        return emailNo;
    }
    public String getImNo() {
        return imNo;
    }
}

    /**
     * 观察者模式之EventBus实战
     */
    @RequestMapping("/observerEventBus")
    public void observerEventBus(){
        EventListener eventListener = new EventListener();
        EventBusCenter.register(eventListener);
        EventBusCenter.post(new NotifyEvent("13372817283", "123@qq.com", "666"));
    }

2.单例模式:
业务场景: 保证一个类仅有一个实例并提供一个访问它的全局访问点。I/O与数据库的连接,一般就用单例模式实现的。Windows里面的Task Manager(任务管理器)也是很典型的单例模式。
示例:

/**
 * 饿汉式: 实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间。
 * 不像懒汉式不加 synchronized 就会存在安全问题。(这里对于懒汉式示例,懒的写 ...)
 */
public class EHanSingleton {
    private static EHanSingleton instance = new EHanSingleton();
    private EHanSingleton(){}
    public static EHanSingleton getInstance() {
        return instance;
    }
}

/**
 * 双重锁:综合了懒汉式和饿汉式两者的优缺点,在synchronized内外都加了一层if条件,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。
 */
public class DoubleCheckSingleton {
    private volatile static DoubleCheckSingleton instance;
    private DoubleCheckSingleton() { }
    public static DoubleCheckSingleton getInstance(){
        if (instance == null) {
            synchronized (DoubleCheckSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

/**
 * 静态内部类实现方式:效果有点类似双重锁。但这种方式只适用于静态域场景。
 */
public class InnerClassSingleton {
    private static class InnerClassSingletonHolder{
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    private InnerClassSingleton(){}
    public static final InnerClassSingleton getInstance(){
        return InnerClassSingletonHolder.INSTANCE;
    }
}

/**
 * 枚举:代码简洁清晰。并且它还自动支持序列化机制,绝对防止多次实例化。
 */
public enum SingletonEnum {
    INSTANCE;
    public static SingletonEnum getInstance(){
        return INSTANCE;
    }
}

    /**
     * 单例模式使用
     */
    @RequestMapping("/singleton")
    public void singleton(){
        EHanSingleton instance = EHanSingleton.getInstance();
        EHanSingleton instance1 = EHanSingleton.getInstance();
        System.out.println("饿汉式单例:" + Objects.equals(instance, instance1));

        DoubleCheckSingleton instance2 = DoubleCheckSingleton.getInstance();
        DoubleCheckSingleton instance3 = DoubleCheckSingleton.getInstance();
        System.out.println("双重锁单例:" + Objects.equals(instance2, instance3));

        InnerClassSingleton instance4 = InnerClassSingleton.getInstance();
        InnerClassSingleton instance5 = InnerClassSingleton.getInstance();
        System.out.println("静态内部类单例:" + Objects.equals(instance4, instance5));

        SingletonEnum instance6 = SingletonEnum.getInstance();
        SingletonEnum instance7 = SingletonEnum.getInstance();
        System.out.println("枚举单例:" + Objects.equals(instance6, instance7));
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容