CleanCode之方法级模板方法

812 阅读4分钟

banner窄.png

铿然架构  |  作者  /  铿然一叶 这是铿然架构的第 82 篇原创文章

相关阅读:

JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(五)事件通知模式解耦过程
Java编程思想(六)事件通知模式解耦过程
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
HikariPool源码(二)设计思想借鉴
【极客源码】JetCache源码(一)开篇
【极客源码】JetCache源码(二)顶层视图
人在职场(一)IT大厂生存法则


1. 起因

今天看到RocketMQ源码的KVConfigManager类,其中的部分代码有重复,如下:

1.1 put方法

1.2 delete方法

红框内的代码是重复的(除了log日志打印的描述信息不同外),对于代码极简主义者,不由想把它优化掉。

2. 重构

2.1. 初次重构想法

代码中多个方法有固定部分,又有变化部分,让人首先想到了模板方法模式:
看起来真的很接近,但模板方法模式是”多个子类“继承一个父类,每个子类可以有不同的行为(可变部分),而当前的场景是”一个类中的多个方法“需要用到模板内容,并且不可能为了适配模板方法模式,将这些方法拆到多个类中。

2.2. 二次重构

从代码中可以看到,可变部分也是一段代码,如果这段代码能动态传入,将固定部分和动态部分组成一个模板方法就是不是就可以了,而java支持函数式接口,因此这个想法变得可能,由此得到如下类结构图:
下面看代码。

2.2.1. 重构前

为了方便调试和说明,对代码做了简化:

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName KVConfigManager
 * @Description
 * @Author 铿然一叶
 * @Date 2021/1/24 13:39
 * @Version 1.0
 * 掘金:https://juejin.im/user/5d48557d6fb9a06ae071e9b0
 **/
public class KVConfigManager {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private Map<String, String> map = new HashMap<>();

    public static void main(String[] args) {
        KVConfigManager manager = new KVConfigManager();
        String key = "server";
        manager.put(key, "10.25.33.45");
        System.out.println(manager.get(key));
        manager.delete(key);
        System.out.println(manager.get(key));
    }

    public void put(String key, String value) {
        try {
            this.lock.writeLock().lockInterruptibly();
            try {
                map.put(key, value);
            } finally {
                this.lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("putKVConfig InterruptedException" + e);
        }
    }

    public void delete(String key) {
        try {
            this.lock.writeLock().lockInterruptibly();
            try {
                map.remove(key);
            } finally {
                this.lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("deleteKVConfig InterruptedException" + e);
        }
    }

    public String get(String key) {
        try {
            this.lock.readLock().lockInterruptibly();
            try {
                return map.remove(key);
            } finally {
                this.lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("getKVConfig InterruptedException" + e);
        }
        return null;
    }
}

2.2.2. 重构后

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName CleanCodeKVConfigManager
 * @Description
 * @Author 铿然一叶
 * @Date 2021/1/24 13:51
 * @Version 1.0
 * 掘金:https://juejin.im/user/5d48557d6fb9a06ae071e9b0
 **/
public class CleanCodeKVConfigManager {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private Map<String, String> map = new HashMap<>();

    public static void main(String[] args) {
        CleanCodeKVConfigManager manager = new CleanCodeKVConfigManager();
        String key = "server";
        manager.put(key, "10.25.33.45");
        System.out.println(manager.get(key));
        manager.delete(key);
        System.out.println(manager.get(key));
    }

    public void put(String key, String value) {
        writeTemplate((args)->{ map.put(args[0], args[1]); }, key, value);
    }

    public void delete(String key) {
        writeTemplate((args)->{ map.remove(args); }, key);
    }

    public String get(String key) {
        try {
            this.lock.readLock().lockInterruptibly();
            try {
                return map.remove(key);
            } finally {
                this.lock.readLock().unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("getKVConfig InterruptedException" + e);
        }
        return null;
    }

    private void writeTemplate(WriteFun writeFun, String... args) {
        try {
            this.lock.writeLock().lockInterruptibly();
            try {
                writeFun.exec(args);
            } finally {
                this.lock.writeLock().unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("deleteKVConfig InterruptedException" + e);
        }
    }

    private interface WriteFun {
        void exec(String... args);
    }
}

2.2.3. 遗留部分

  1. 模板方法里的打印描述是固定的,这个增加一个参数传入就好
System.out.println("deleteKVConfig InterruptedException" + e);
  1. 读的部分是否也要封装?例如增加一个参数表达是读还是写,在模板方法中判断此参数做不同处理。

这个可以结合实际情况来,目的是减少重复代码和使代码变得简洁,能达到此目的就重构,否则可先保留。

3. 总结

1. 对于模板式的重复代码,除了想到使用模板方法设计模式重构以外,还可以使用方法级模板方法重构。

2. 减少重复代码的目的是只维护一份,有变化时只需要维护一处,如果维护多处则可能由于疏忽导致某处遗漏,最终行为不一致,因此减少重复代码的目的不仅仅是减少代码行,不要觉得没有减少代码行就没有必要重构。

3. 本次重构仅仅是个例子,以了解方法级模板方法的应用场景,什么场景需要重构看企业文化,有的企业可能会觉得例子中的代码很稳定,将来不会变化,不强制要求重构。


end.


<--阅过留痕,左边点赞!