用多态处理变体逻辑

举报
JavaEdge 发表于 2022/04/11 14:43:21 2022/04/11
【摘要】 鸟类型体系是一个清晰的泛化体系:父类是抽象的“鸟”,子类是各种具体鸟。这是教科书中经常讨论的继承和多态,但并非实践中使用继承的唯一方式。而且这种方式很可能不是最常用或最好。另一种使用继承:我想表达某个对象与另一个对象大体类似,但又有一些不同之处。有一家评级机构,要对远洋航船的航行进行投资评级。这家评级机构会给出“A”或“B”两种评级,取决于多种风险和盈利潜力的因素。在评估风险时,既要考虑航程...

鸟类型体系是一个清晰的泛化体系:父类是抽象的“鸟”,子类是各种具体鸟。这是教科书中经常讨论的继承和多态,但并非实践中使用继承的唯一方式。而且这种方式很可能不是最常用或最好。

另一种使用继承:我想表达某个对象与另一个对象大体类似,但又有一些不同之处。

有一家评级机构,要对远洋航船的航行进行投资评级。这家评级机构会给出“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;
    }

至此,与“中国经验”相关的代码则清晰表述出在基本逻辑之上的一系列变体逻辑。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。