用多态处理变体逻辑
鸟类型体系是一个清晰的泛化体系:父类是抽象的“鸟”,子类是各种具体鸟。这是教科书中经常讨论的继承和多态,但并非实践中使用继承的唯一方式。而且这种方式很可能不是最常用或最好。
另一种使用继承:我想表达某个对象与另一个对象大体类似,但又有一些不同之处。
有一家评级机构,要对远洋航船的航行进行投资评级。这家评级机构会给出“A”或“B”两种评级,取决于多种风险和盈利潜力的因素。在评估风险时,既要考虑航程本身的特征,也要考虑船长过往航行的历史。
调用方代码:
const voyage = {zone: "west-indies", length: 10}; const history = [ {zone: "east-indies", profit: 5}, {zone: "west-indies", profit: 15}, {zone: "china", profit: -2}, {zone: "west-africa", profit: 7}, ];const myRating = rating(voyage, history);
代码中有两处同样的条件逻辑,都在询问“是否有到中国的航程”以及“船长是否曾去过中国”。
用继承和多态将处理“中国因素”的逻辑从基础逻辑中分离出来。如果还要引入更多的特殊逻辑,这些重复的“中国因素”会混淆视听,让基础逻辑难以理解。
起初代码里只有一堆函数,引入多态,需要先建立一个类结构,使用【函数组合成类】:
package com.javaedge.refactor.condition;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Arrays;
import java.util.List;
/**
* @author apple
* @date 2022/4/10
*/
@Data
@AllArgsConstructor
public class Rating {
private Voyage voyage;
private History history;
List<String> list = Arrays.asList("china", "east-indies");
/**
* 将3个分数组合到一起,给出一次航行的综合评级
*
* @return
*/
public String rating(Voyage voyage, History history) {
int vpf = voyageProfitFactor(voyage, history);
int vr = getVoyageRisk(voyage);
int chr = getCaptainHistoryRisk(voyage, history);
if (vpf * 3 > (vr + chr * 2)) {
return "A";
} else {
return "B";
}
}
/**
* 打出风险分数
*
* @return
*/
public int getVoyageRisk(Voyage voyage) {
int result = 1;
if (voyage.length > 4) {
result += 2;
}
if (voyage.length > 8) {
result += voyage.length - 8;
}
if (list.contains(voyage.zone)) {
result += 4;
}
return Math.max(result, 0);
}
/**
* 打出风险分数
*/
public int getCaptainHistoryRisk(Voyage voyage, History history) {
int result = 1;
if (history.length < 5) {
result += 4;
}
result += history.length;
if ("china".equals(voyage.zone) && hasChina(history)) {
result -= 2;
}
return Math.max(result, 0);
}
public boolean hasChina(History history) {
return "china".equals(history.zone);
}
/**
* 打出盈利潜力分数
*/
public int voyageProfitFactor(Voyage voyage, History history) {
int result = 2;
if ("china".equals(voyage.zone)) {
result += 1;
}
if ("east-indies".equals(voyage.zone)) {
result += 1;
}
if ("china".equals(voyage.zone) && hasChina(history)) {
result += 3;
if (history.length > 10) {
result += 1;
}
if (voyage.length > 12) {
result += 1;
}
if (voyage.length > 18) {
result -= 1;
}
} else {
if (history.length > 8) {
result += 1;
}
if (voyage.length > 14) {
result -= 1;
}
}
return result;
}
}
于是我就有了一个类,用来安放基础逻辑。建一个空的子类,安放与超类不同的行为。
然后,建立一个工厂函数,用于在需要时返回变体类。
我需要修改所有调用方代码,让它们使用该工厂函数, 而不要直接调用构造函数。还好现在调用构造函数的只有rating函数一处。
有两处行为需要移入子类中,先处理captainHistoryRisk中的逻辑。 在子类中重写:
class ExperiencedChinaRating extends Rating {
public ExperiencedChinaRating(Voyage voyage, History history) {
super(voyage, history);
}
public static Rating createRating(Voyage voyage, History history) {
if ("china".equals(voyage.zone) && "china".equals(history.zone)) {
return new ExperiencedChinaRating(voyage, history);
} else {
return new Rating(voyage, history);
}
}
@Override
public int getCaptainHistoryRisk(Voyage voyage, History history) {
int result = super.getCaptainHistoryRisk(voyage, history) - 2;
return Math.max(result, 0);
}
}
删除父类中对应部分:
分离voyageProfitFactor函数中的变体行为要更麻烦一些。我不能直接从超类中删掉变体行为,因为在超类中还有另一条执行路径。我又不想把整个超类中的函数复制到子类中。
用【提炼函数】将整个条件逻辑块提炼出来。
public int voyageProfitFactor(Voyage voyage, History history) {
int result = 2;
if ("china".equals(voyage.zone)) {
result += 1;
}
if ("east-indies".equals(voyage.zone)) {
result += 1;
}
result += this.voyageAndHistoryLengthFactor();
return result;
}
private int voyageAndHistoryLengthFactor() {
int result = 0;
if ("china".equals(voyage.zone) && hasChina(history)) {
result += 3;
if (history.length > 10) {
result += 1;
}
if (voyage.length > 12) {
result += 1;
}
if (voyage.length > 18) {
result -= 1;
}
} else {
if (history.length > 8) {
result += 1;
}
if (voyage.length > 14) {
result -= 1;
}
}
return result;
}
函数名中出现“And”字样是一个很不好的味道,不过我会暂时容忍它,先聚焦子类化操作。
@Override
public int voyageAndHistoryLengthFactor() {
int result = 0;
result += 3;
if (this.getHistory().length > 10) {
result += 1;
}
if (this.getVoyage().length > 12) {
result += 1;
}
if (this.getVoyage().length > 18) {
result -= 1;
}
return result;
}
严格说,重构至此就结束了:我已经把变体行为分离到了子类中,超类的逻辑理解和维护起来更简单了,只有在进入子类代码时我才需要操心变体逻辑。子类的代码表述了它与超类的差异。
但至少应该谈谈如何处理这个丑陋的新函数。引入一个函数以便子类覆写,这在处理这种“基础和变体”的继承关系时是常见操作。但这样一个难看的函数只会妨碍而非帮助别人理解其中的逻辑。
函数名中的“And”字样说明其中包含了两件事,所以应该分开。我会用提炼函数(106)把“历史航行数”(history length)的相关逻辑提炼出来。这一步提炼在超类和子类中都要发生,我首先从超类开始。
public int voyageAndHistoryLengthFactor() {
int result = 0;
result += this.historyLengthFactor();
if (voyage.length > 14) {
result -= 1;
}
return result;
}
public int historyLengthFactor() {
return (this.getHistory().length > 8) ? 1 : 0;
}
@Override
public int voyageAndHistoryLengthFactor() {
int result = 0;
result += 3;
result += this.historyLengthFactor();
if (this.getVoyage().length > 12) {
result += 1;
}
if (this.getVoyage().length > 18) {
result -= 1;
}
return result;
}
@Override
public int historyLengthFactor() {
return (this.getHistory().length > 10) ? 1 : 0;
}
然后在超类中使用【搬移语句到调用者】,再用【函数改名】改掉这个难听名字,改为三元表达式,以简化voyageLengthFactor函数。
@Override
public int voyageLengthFactor() {
int result = 0;
result += 3;
if (this.getVoyage().length > 12) {
result += 1;
}
if (this.getVoyage().length > 18) {
result -= 1;
}
return result;
}
@Override
public int historyLengthFactor() {
return (this.getHistory().length > 10) ? 1 : 0;
}
在“航程数”(voyage length)因素上加3分,这个逻辑不合理,应该把这3分加在最终结果。
@Override
public int voyageProfitFactor(Voyage voyage, History history) {
return super.voyageProfitFactor(voyage, history) + 3;
}
至此,与“中国经验”相关的代码则清晰表述出在基本逻辑之上的一系列变体逻辑。
- 点赞
- 收藏
- 关注作者
评论(0)