戏剧演出团
客户 customer 会指定几出剧目
剧团根据观众 audience 人数及剧目类型来向观众收费
悲剧 tragedy 和 喜剧 comedy
客户账单 根据到场观众数量给出 观众量积分 volume credit 优惠
通过积分获得优惠
demo程序
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<script>
let plays =
{
"hamlet": {"name": "Hamlet", "type": "tragedy"},
"as-like": {"name": "As You Like It", "type": "comedy"},
"othello": {"name": "Othello", "type": "tragedy"}
};
let invoice =
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
};
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = 0;
switch (play.type) {
case "tragedy":
thisAmount = 40000;
if (perf.audience > 30) {
thisAmount += 1000 * (perf.audience - 30);
}
break;
case "comedy":
thisAmount = 30000;
if (perf.audience > 20) {
thisAmount += 10000 + 500 * (perf.audience - 20);
}
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${play.name}: ${format(thisAmount / 100)} (${perf.audience} seats)\n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
function myFunction() {
alert(statement(plays, invoice));
}
</script>
<body>
<button onclick="myFunction()">Clink</button>
</body>
</html>
如果你要给程序添加一个特性,但发现代码因缺乏良好的结构而不易于进行更改,那就先重构那个程序,使其比较容易添加该特性,然后再添加该特性。
我再强调一次,是需求的变化使重构变得必要。如果一段代码能正常工作,并且不会再被修改,那么完全可以不去重构它。能改进之当然很好,但若没人需要去理解它,它就不会真正妨碍什么。如果确实有人需要理解它的工作原理,并且觉得理解起来很费劲,那你就需要改进一下代码了。
重构的第一步
确保即将修改的代码,拥有一组可靠的测试。
在数字时代,软件的名字就是脆弱。
提炼 thisAmount
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format;
for (let perf of invoice.performances) {
const play = plays[perf.playID];
let thisAmount = amountFor(perf, play);
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${play.name}: ${format(thisAmount / 100)} (${perf.audience} seats)\n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
function amountFor(perf, play) {
let thisAmount = 0;
switch (play.type) {
case "tragedy":
thisAmount = 40000;
if (perf.audience > 30) {
thisAmount += 1000 * (perf.audience - 30);
}
break;
case "comedy":
thisAmount = 30000;
if (perf.audience > 20) {
thisAmount += 10000 + 500 * (perf.audience - 20);
}
thisAmount += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
return thisAmount;
}
给一些变量命名,使他们更简洁。例如将thisAmount重命名为result.
function amountFor(perf, play) {
let result = 0;
switch (play.type) {
case "tragedy":
result = 40000;
if (perf.audience > 30) {
result += 1000 * (perf.audience - 30);
}
break;
case "comedy":
result = 30000;
if (perf.audience > 20) {
result += 10000 + 500 * (perf.audience - 20);
}
result += 300 * perf.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
return result;
}
修改函数参数 perf -> aPerformance
function amountFor(aPerformance, play) {
let result = 0;
switch (play.type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
return result;
}
去除 const play = plays[perf.playID];
function amountFor(aPerformance, play) {
let result = 0;
switch (play.type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${play.type}`);
}
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format;
for (let perf of invoice.performances) {
let thisAmount = amountFor(perf, playFor(perf));
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === play.type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${play.name}: ${format(thisAmount / 100)} (${perf.audience} seats)\n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
去除函数 amountFor中的 play 参数
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format;
for (let perf of invoice.performances) {
let thisAmount = amountFor(perf);
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === playFor(perf).type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${playFor(perf).name}: ${format(thisAmount / 100)} (${perf.audience} seats)\n`;
totalAmount += thisAmount;
}
result += `Amount owed is ${format(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
去除 thisAmount
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format;
for (let perf of invoice.performances) {
// add volume credits
volumeCredits += Math.max(perf.audience - 30, 0);
// add extra credit for every ten comedy attendees
if ("comedy" === playFor(perf).type) volumeCredits += Math.floor(perf.audience / 5);
// print line for this order
result += ` ${playFor(perf).name}: ${format(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
result += `Amount owed is ${format(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
提炼 volumeCredits
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function volumeCreditsFor(perf) {
let volumeCredits = 0;
volumeCredits += Math.max(perf.audience - 30, 0);
if ("comedy" === playFor(perf).type) volumeCredits += Math.floor(perf.audience / 5);
return volumeCredits;
}
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format;
for (let perf of invoice.performances) {
volumeCredits = volumeCreditsFor(perf);
// print line for this order
result += ` ${playFor(perf).name}: ${format(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
result += `Amount owed is ${format(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
去除 format变量
function format(aNumber) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber);
}
修改 format 变量 函数名称为 usd
function usd(aNumber) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber / 100);
}
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
volumeCredits = volumeCreditsFor(perf);
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
result += `Amount owed is ${usd(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
好的命名十分重要,但往往并非唾手可得。只有恰如其分地命名,才能彰显出将大函数分解成小函数的价值。有了好的名称,我就不必通过阅读函数体来了解其行为。但要一次把名取好并不容易,因此我会使用当下能想到最好的那个。如果稍后想到更好的,我就会毫不犹豫地换掉它。通常你需要花几秒钟通读更多代码,才能发现最好的名称是什么。
重构 volumeCredits
function statement(plays, invoice) {
let totalAmount = 0;
let volumeCredits = 0;
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
result += `Amount owed is ${usd(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
把与更新 volumeCredits 变量相关的代码都集中到一起
function totalVolumeCredits() {
let volumeCredits = 0;
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
return volumeCredits;
}
function statement(plays, invoice) {
let totalAmount = 0;
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
let volumeCredits = totalVolumeCredits();
result += `Amount owed is ${usd(totalAmount / 100)}\n`;
result += `You earned ${volumeCredits} credits\n`;
return result;
}
内联
function totalVolumeCredits() {
let volumeCredits = 0;
for (let perf of invoice.performances) {
volumeCredits += volumeCreditsFor(perf);
}
return volumeCredits;
}
function statement(plays, invoice) {
let totalAmount = 0;
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
totalAmount += amountFor(perf);
}
result += `Amount owed is ${usd(totalAmount / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
因此对于重构过程的性能问题,我总体的建议是:大多数情况下可以忽略它。如果重构引入了性能损耗,先完成重构,再做性能优化。
以同样的方式 移除 totalAmount
function appleSauce() {
let totalAmount = 0;
for (let perf of invoice.performances) {
totalAmount += amountFor(perf);
}
return totalAmount;
}
function statement(plays, invoice) {
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
let totalAmount = appleSauce();
result += `Amount owed is ${usd(totalAmount / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
内联
function appleSauce() {
let totalAmount = 0;
for (let perf of invoice.performances) {
totalAmount += amountFor(perf);
}
return totalAmount;
}
function statement(plays, invoice) {
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(appleSauce() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
修改变量名
function totalAmount() {
let totalAmount = 0;
for (let perf of invoice.performances) {
totalAmount += amountFor(perf);
}
return totalAmount;
}
function statement(plays, invoice) {
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
修改函数内部变量名
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<script>
let plays =
{
"hamlet": {"name": "Hamlet", "type": "tragedy"},
"as-like": {"name": "As You Like It", "type": "comedy"},
"othello": {"name": "Othello", "type": "tragedy"}
};
let invoice =
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
};
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function volumeCreditsFor(perf) {
let result = 0;
result += Math.max(perf.audience - 30, 0);
if ("comedy" === playFor(perf).type) result += Math.floor(perf.audience / 5);
return result;
}
function usd(aNumber) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber / 100);
}
function totalVolumeCredits() {
let result = 0;
for (let perf of invoice.performances) {
result += volumeCreditsFor(perf);
}
return result;
}
function totalAmount() {
let result = 0;
for (let perf of invoice.performances) {
result += amountFor(perf);
}
return result;
}
function statement(plays, invoice) {
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
function myFunction() {
alert(statement(plays, invoice));
}
</script>
<body>
<button onclick="myFunction()">Clink</button>
</body>
</html>
statenebt 方法 抽取 函数 封装 renderPlainText
function renderPlainText(plays, invoice){
let result = `Statement for ${invoice.customer}\n`;
for (let perf of invoice.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
function statement(plays, invoice) {
return renderPlainText(plays, invoice);
}
移除 renderPlainText 的 invoice 参数
function renderPlainText(data, plays){
let result = `Statement for ${data.customer}\n`;
for (let perf of data.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
function statement(plays, invoice) {
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances;
return renderPlainText(statementData, plays);
}
增强中转数据 performances 。使用play中的数据 填充 aPerformance对象
function statement(plays, invoice) {
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances.map(enrichPerformance);
return renderPlainText(statementData, plays);
}
function enrichPerformance(aPerformance){
const result = Object.assign({}, aPerformance);
return result;
}
增强 enrichPerformance 中转函数 填充 play amount volumeCredits
function renderPlainText(data, plays){
let result = `Statement for ${data.customer}\n`;
for (let perf of data.performances) {
// print line for this order
result += ` ${playFor(perf).name}: ${usd(amountFor(perf) / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
function statement(plays, invoice) {
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances.map(enrichPerformance);
return renderPlainText(statementData, plays);
}
function enrichPerformance(aPerformance){
const result = Object.assign({}, aPerformance);
result.play = playFor(aPerformance);
result.amount = amountFor(aPerformance);
result.volumeCredits = volumeCreditsFor(aPerformance);
return result;
}
playFor(perf) 替换为 perf.play
amountFor(perf) 替换为 perf.amount
volumeCreditsFor(perf) 替换为 perf.volumeCredits
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<script>
let plays =
{
"hamlet": {"name": "Hamlet", "type": "tragedy"},
"as-like": {"name": "As You Like It", "type": "comedy"},
"othello": {"name": "Othello", "type": "tragedy"}
};
let invoice =
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience": 40
}
]
};
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function volumeCreditsFor(perf) {
let result = 0;
result += Math.max(perf.audience - 30, 0);
if ("comedy" === playFor(perf).type) result += Math.floor(perf.audience / 5);
return result;
}
function usd(aNumber) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber / 100);
}
function totalVolumeCredits() {
let result = 0;
for (let perf of invoice.performances) {
result += perf.volumeCredits;
}
return result;
}
function totalAmount() {
let result = 0;
for (let perf of invoice.performances) {
result += perf.amount;
}
return result;
}
function renderPlainText(data, plays){
let result = `Statement for ${data.customer}\n`;
for (let perf of data.performances) {
// print line for this order
result += ` ${perf.play.name}: ${usd(perf.amount / 100)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(totalAmount() / 100)}\n`;
result += `You earned ${totalVolumeCredits()} credits\n`;
return result;
}
function statement(plays, invoice) {
const statementData = {};
statementData.customer = invoice.customer;
statementData.performances = invoice.performances.map(enrichPerformance);
return renderPlainText(statementData, plays);
}
function enrichPerformance(aPerformance){
const result = Object.assign({}, aPerformance);
result.play = playFor(aPerformance);
result.amount = amountFor(aPerformance);
result.volumeCredits = volumeCreditsFor(aPerformance);
return result;
}
function myFunction() {
alert(statement(plays, invoice));
}
</script>
<body>
<button onclick="myFunction()">Clink</button>
</body>
</html>
====================================================================================
将所有的函数进行抽取 抽取出两个js
createStatementData.js
statement.js
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);
}
function enrichPerformance(aPerformance){
const result = Object.assign({}, aPerformance);
result.play = playFor(aPerformance);
result.amount = amountFor(aPerformance);
result.volumeCredits = volumeCreditsFor(aPerformance);
return result;
}
function playFor(aPerformance) {
return plays[aPerformance.playID];
}
function amountFor(aPerformance) {
let result = 0;
switch (playFor(aPerformance).type) {
case "tragedy":
result = 40000;
if (aPerformance.audience > 30) {
result += 1000 * (aPerformance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (aPerformance.audience > 20) {
result += 10000 + 500 * (aPerformance.audience - 20);
}
result += 300 * aPerformance.audience;
break;
default:
throw new Error(`unknown type: ${playFor(aPerformance).type}`);
}
return result;
}
function volumeCreditsFor(perf) {
let result = 0;
result += Math.max(perf.audience - 30, 0);
if ("comedy" === playFor(perf).type) result += Math.floor(perf.audience / 5);
return result;
}
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);
}
statement.js
import createStatementData from "./createStatementData";
function htmlStatement(invoice, plays) {
return renderHtml(createStatementData(invoice, plays));
}
function renderHtml(data) {
let result = `<h1>Statement for ${data.customer}</h1>\n`;
result += "<table>\n";
result += "<tr><th>play</th><th>seats</th><th>cost</th></tr>";
for (let perf of data.performances) {
result += ` <tr><td>${perf.play.name}</td><td>${perf.audience}</td>`;
result += `<td>${usd(perf.amount)}</td></tr>\n`;
}
result += "</table>\n";
result += `<p>Amount owed is <em>${usd(data.totalAmount)}</em></p>\n`;
result += `<p>You earned <em>${data.totalVolumeCredits}</em> credits</p>\n`;
return result;
}
function usd(aNumber) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber / 100);
}
createStatementData.js 搬移 amount 搬移 volumeCredits
class PerformanceCalculator {
constructor(aPerformance, aPlay, amount) {
this.performance = aPerformance;
this.play = aPlay;
}
get amount(){
let result = 0;
switch (this.performance.type) {
case "tragedy":
result = 40000;
if (this.performance.audience > 30) {
result += 1000 * (this.performance.audience - 30);
}
break;
case "comedy":
result = 30000;
if (this.performance.audience > 20) {
result += 10000 + 500 * (this.performance.audience - 20);
}
result += 300 * this.performance.audience;
break;
default:
throw new Error(`unknown type: ${this.play.type}`);
}
return result;
}
get volumeCredits(){
let result = 0;
result += Math.max(this.performance.audience - 30, 0);
if ("comedy" === this.play.type) result += Math.floor(perf.audience / 5);
return result;
}
}
function enrichPerformance(aPerformance){
const calculator = new PerformanceCalculator(aPerformance, playFor(aPerformance));
const result = Object.assign({}, aPerformance);
result.play = calculator.play;
result.amount = calculator.amount;
result.volumeCredits = calculator.volumeCredits;
return result;
}
将函数进行抽取 最终结果
statement.js
import createStatementData from "./createStatementData";
export function htmlStatement(invoice, plays) {
return renderHtml(createStatementData(invoice, plays));
}
function renderHtml(data) {
let result = `<h1>Statement for ${data.customer}</h1>\n`;
result += "<table>\n";
result += "<tr><th>play</th><th>seats</th><th>cost</th></tr>";
for (let perf of data.performances) {
result += ` <tr><td>${perf.play.name}</td><td>${perf.audience}</td>`;
result += `<td>${usd(perf.amount)}</td></tr>\n`;
}
result += "</table>\n";
result += `<p>Amount owed is <em>${usd(data.totalAmount)}</em></p>\n`;
result += `<p>You earned <em>${data.totalVolumeCredits}</em> credits</p>\n`;
return result;
}
function usd(aNumber) {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2
}).format(aNumber / 100);
}
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);
}
}
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);
}
}