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

设计模式学习笔记 - 项目实践 2:设计和实现通用接口闲置框架(实现) - 重构最小原型代码

最编程 2024-04-30 18:57:31
...

实际上,问题找到了,修改起来就容易多了。针对刚刚罗列的问题,我们对 MVP 代码进行重构,重构之后的代码如下所示。

// 代码目录结构
com.example.idempotence
 --Idempotence
 --IdempotenceIdGenerator (幂等号生成类)
 --IdempotenceStorage (接口: 用来读写幂等号)
 --RedisClusterIdempotenceStorage (IdempotenceStorage的实现类)

// 每个代码的实现类
public class Idempotence {
    private IdempotenceStorage storage;

    public Idempotence(IdempotenceStorage storage) {
        this.storage = storage;
    }

    public boolean saveIfAbsent(String idempotenceId) {
        return storage.saveIfAbsent(idempotenceId);
    }

    public void delete(String idempotenceId) {
        storage.delete(idempotenceId);
    }
}

public class IdempotenceIdGenerator {
    public String generateId() {
        return UUID.randomUUID().toString();
    }
}

public interface IdempotenceStorage {
    boolean saveIfAbsent(String idempotenceId);
    void delete(String idempotenceId);
}

public class RedisClusterIdempotenceStorage implements IdempotenceStorage {
    private JedisCluster jedisCluster;

    /**
     * Constructor
     * @param redisClusterAddress the format is 128.91.12.1:3455;128.91.12.2:3452;128.91.12.3:3453;...
     * @param config should not be null
     */
    public RedisClusterIdempotenceStorage(String redisClusterAddress, GenericObjectPoolConfig config) {
        Set<HostAndPort> redisNodes = parseHostAndPorts(redisClusterAddress);
        this.jedisCluster = new JedisCluster(redisNodes, config);
    }

    public RedisClusterIdempotenceStorage(JedisCluster jedisCluster) {
        this.jedisCluster = jedisCluster;
    }

    /**
     * Save {@idempotenceId} into storage if it does not exist.
     * @param idempotenceId the idempotence ID
     * @return true if the {@idempotenceId} is saved, otherwise retyrn false
     */
    @Override
    public boolean saveIfAbsent(String idempotenceId) {
        Long success = jedisCluster.setnx(idempotenceId, "1");
        return success == 1;
    }

    @Override
    public void delete(String idempotenceId) {
        jedisCluster.del(idempotenceId);
    }

    @VisibleForTesting
    protected Set<HostAndPort> parseHostAndPorts(String redisClusterAddress) {
        String[] addressArray = redisClusterAddress.split(";");
        Set<HostAndPort> redisNodes = new HashSet<>();
        for (String address : addressArray) {
            String[] hostAndPort = address.split(":");
            redisNodes.add(new HostAndPort(hostAndPort[0], Integer.parseInt(hostAndPort[1])));
        }
        return redisNodes;
    }
}

接下来,我们再总结罗列一下,针对之前发现的问题,我们都做了哪些改动。主要有下面这样几点。

在代码可读性方面,我们对构造函数、saveIfAbsent() 函数的参数和返回值做了注释,并将 genId() 函数改为全拼的 generateId()

在扩展性方面,我们按照基于接口而非实现编程原则,将幂等号的读写独立出来,设计成 IdempotenceStorage 接口和 RedisClusterIdempotenceStorage 实现类。RedisClusterIdempotenceStorage 实现了基于 Redis Cluster 的幂等号读写。如果我们需要替换新的幂等号读写方式,比如基于单个 Redis 而非 Redis Cluster,我们就可以在定义一个实现了 IdempotenceStorage 接口的实现类:RedisIdempotenceStorage

此外,按照接口隔离原则,我们将生成幂等号的代码抽离出来,放到 IdempotenceIdGenerator 类中。这样,调用方只需要依赖这个类的代码就可以了。幂等号生成算法的修改,跟幂等号存储逻辑的修改,两者完全独立,一个修改并不会影响到另一个。

在代码可测试性方面,我们把原本放在构造函数中的逻辑抽离出来,放到了 parseHostAndPorts() 中。这个函数本应该是 private 访问权限的,但为了方便编写单元测试,我们把它设置成了 protected 访问权限,并通过注释 @VisibleForTesting 做了标明。

在代码灵活性方面,为了方便复用业务系统已经建立好的 jedisCluster,我们提供了一个新的构造函数,支持业务系统直接传递 jedisCluster 来创建 RedisClusterIdempotenceStorage 对象。

推荐阅读