王争《设计模式之美》学习笔记
重构 generate() 函数
- ID 由三部分构成:本机名、时间戳和随机数。
- 时间戳和随机数的生成函数不会出错,唯独主机名有可能获取失败。
- 对于 generate() 函数,如果本机名获取失败,函数返回什么?这样的返回值是否合理?
- 如果主机名获取失败,substrOfHostName 为 NULL,那 generate() 函数会返回类似“null-6723733647-83Ab3uK6”这样的数据。
- 如果主机名获取失败,substrOfHostName 为空字符串,那 generate() 函数会返回类似“-6723733647-83Ab3uK6”这样的数据。
- 作者认为以上处理都不理想,更倾向于明确地将异常告知调用者。所以,这里最好是抛出受检异常,而非特殊值。
重构 getLastFieldOfHostName() 函数
- getLastFieldOfHostName() 函数获取主机名失败会影响后续逻辑的处理,并不是我们期望的,所以,它是一种异常行为。这里最好是抛出异常,而非返回 NULL 值。
- getLastFieldOfHostName() 函数用来获取主机名的最后一个字段,UnknownHostException 异常表示主机名获取失败,两者算是业务相关,所以可以直接将 UnknownHostException 抛出,不需要重新包裹成新的异常。
- getLastFieldOfHostName() 函数修改之后,generate() 函数也要做相应的修改。我们需要在 generate() 函数中,捕获 getLastFieldOfHostName() 抛出的 UnknownHostException 异常。
- 在generate() 函数中,我们需要捕获 UnknownHostException 异常,并重新包裹成新的异常 IdGenerationFailureException 往上抛出。原因有三:
- 调用者在使用 generate() 函数的时候,只需要知道它生成的是随机唯一 ID,并不关心 ID 是如何生成的。这是依赖抽象而非实现编程。
- 从代码封装的角度来讲,我们不希望将 UnknownHostException 这个比较底层的异常,暴露给更上层的代码,也就是调用 generate() 函数的代码。而且,调用者拿到这个异常的时候,并不能理解这个异常到底代表了什么,也不知道该如何处理。
- UnknownHostException 异常跟 generate() 函数,在业务概念上没有相关性。
重构 getLastSubstrSplittedByDot() 函数
- 对于 getLastSubstrSplittedByDot(String hostName) 函数,如果 hostName 为 NULL 或者空字符串,这个函数应该返回什么?
- 我们可以不在 private 函数中做 NULL 值或空字符就可以了。
- 如果函数是 public 的,你无法掌控会被谁调用以及如何调 ,为了尽可能提高代码的健壮性,我们最好是在 public 函数中做 NULL 值或空字符串的判断。
- 实际为了方便写单元测试,getLastSubstrSplittedByDot() 是 protected 的。这里我们最好也加上 NULL 值或空字符串的判断逻辑。虽然加上有些冗余,但多加些检验总归不会错的。
重构 generateRandomAlphameric() 函数
- 对于 generateRandomAlphameric(int length) 函数:
- 如果 length < 0,生成一个长度为负值的随机字符串是不符合常规逻辑的,是一种异常行为,我们抛出 IllegalArgumentException 异常。
- 如果 length = 0:
- 我们既可以把它定义为一种异常行为,抛出 IllegalArgumentException 异常。
- 也可以把它定义为一种正常行为,让函数在入参 length = 0 的情况下,直接返回空字符串。
- 最关键的一点是,要在函数注释中,明确告知 length = 0 的情况下,回返回什么样的数据。
重构之后的 RandomIdGenerator 代码
public class RandomIdGenerator implements IdGenerator {
private static final Logger logger = LoggerFactory.getLogger(RandomIdGenerator.class);
@Override
public String generate() throws IdGenerationFailureException {
String substrOfHostName = null;
try {
substrOfHostName = getLastFieldOfHostName();
} catch (UnknownHostException e) {
throw new IdGenerationFailureException("...", e);
}
long currentTimeMillis = System.currentTimeMillis();
String randomString = generateRandomAlphameric(8);
String id = String.format("%s-%d-%s",
substrOfHostName, currentTimeMillis, randomString);
return id;
}
private String getLastFieldOfHostName() throws UnknownHostException{
String substrOfHostName = null;
String hostName = InetAddress.getLocalHost().getHostName();
if (hostName == null || hostName.isEmpty()) {
throw new UnknownHostException("...");
}
substrOfHostName = getLastSubstrSplittedByDot(hostName);
return substrOfHostName;
}
@VisibleForTesting
protected String getLastSubstrSplittedByDot(String hostName) {
if (hostName == null || hostName.isEmpty()) {
throw new IllegalArgumentException("...");
}
String[] tokens = hostName.split("\\.");
String substrOfHostName = tokens[tokens.length - 1];
return substrOfHostName;
}
@VisibleForTesting
protected String generateRandomAlphameric(int length) {
if (length <= 0) {
throw new IllegalArgumentException("...");
}
char[] randomChars = new char[length];
int count = 0;
Random random = new Random();
while (count < length) {
int maxAscii = 'z';
int randomAscii = random.nextInt(maxAscii);
boolean isDigit= randomAscii >= '0' && randomAscii <= '9';
boolean isUppercase= randomAscii >= 'A' && randomAscii <= 'Z';
boolean isLowercase= randomAscii >= 'a' && randomAscii <= 'z';
if (isDigit|| isUppercase || isLowercase) {
randomChars[count] = (char) (randomAscii);
++count;
}
}
return new String(randomChars);
}
}