💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
又到了观摩代码的时刻,让我们来看看,为计算器引入多态会对代码库有什么影响。 ##### createStatementData.js ``` export default function createStatementData(invoice, plays) { const result = {}; result.customer = invoice.customer; result.performances = invoice.performances.map(enrichPerformance); result.totalAmount = totalAmount(result); result.totalVolumeCredits = totalVolumeCredits(result); return result; function enrichPerformance(aPerformance) { const calculator = createPerformanceCalculator(aPerformance, playFor(aPerformance)); const result = Object.assign({}, aPerformance); result.play = calculator.play; result.amount = calculator.amount; result.volumeCredits = calculator.volumeCredits; return result; } function playFor(aPerformance) { return plays[aPerformance.playID] } function totalAmount(data) { return data.performances .reduce((total, p) => total + p.amount, 0); } function totalVolumeCredits(data) { return data.performances .reduce((total, p) => total + p.volumeCredits, 0); } } function createPerformanceCalculator(aPerformance, aPlay) { switch(aPlay.type) { case "tragedy": return new TragedyCalculator(aPerformance, aPlay); case "comedy" : return new ComedyCalculator(aPerformance, aPlay); default: throw new Error(`unknown type: ${aPlay.type}`); } } class PerformanceCalculator { constructor(aPerformance, aPlay) { this.performance = aPerformance; this.play = aPlay; } get amount() { throw new Error('subclass responsibility'); } get volumeCredits() { return Math.max(this.performance.audience - 30, 0); } } class TragedyCalculator extends PerformanceCalculator { get amount() { let result = 40000; if (this.performance.audience > 30) { result += 1000 * (this.performance.audience - 30); } return result; } } class ComedyCalculator extends PerformanceCalculator { get amount() { let result = 30000; if (this.performance.audience > 20) { result += 10000 + 500 * (this.performance.audience - 20); } result += 300 * this.performance.audience; return result; } get volumeCredits() { return super.volumeCredits + Math.floor(this.performance.audience / 5); } } ``` 代码量仍然有所增加,因为我再次整理了代码结构。新结构带来的好处是,不同戏剧种类的计算各自集中到了一处地方。如果大多数修改都涉及特定类型的计算,像这样按类型进行分离就很有意义。当添加新剧种时,只需要添加一个子类,并在创建函数中返回它。 这个示例还揭示了一些关于此类继承方案何时适用的洞见。上面我将条件分支的查找从两个不同的函数(`amountFor`和`volumeCreditsFor`)搬移到一个集中的构造函数`createPerformanceCalculator`中。有越多的函数依赖于同一套类型进行多态,这种继承方案就越有益处。 除了这样设计,还有另一种可能的方案,那就是让`createStatementData`返回计算器实例本身,而非自己拿到计算器来填充中转数据结构。JavaScript的类设计有不少好特性,例如,取值函数用起来就像普通的数据存取。我在考量是“直接返回实例本身”还是“返回计算好的中转数据”时,主要看数据的使用者是谁。在这个例子中,我更想通过中转数据结构来展示如何以此隐藏计算器背后的多态设计。