最近把《重构 第2版》第1章看了一遍,也跟书上的例子完成了示例程序的重构,完成时的代码记录如下:
invoices.json
[
{
"customer": "BigCo",
"performances": [
{
"playID": "hamlet",
"audience": 55
},
{
"playID": "as-like",
"audience": 35
},
{
"playID": "othello",
"audience":40
}
]
}
]
plays.json
{
"hamlet": {
"name": "Hamlet",
"type": "tragedy"
},
"as-like": {
"name": "As You Like It",
"type": "comedy"
},
"othello": {
"name": "Othello",
"type": "tragedy"
}
}
createStatementData.js
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 = new 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 = 4000;
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);
}
}
module.exports.createStatementData = createStatementData;
statement.js
const { createStatementData } = require('./createStatementData.js');
let pathPlays = "./plays.json";
let pathInvoices = "./invoices.json";
let fs = require('fs');
let plays = JSON.parse(fs.readFileSync(pathPlays));
let invoice = JSON.parse(fs.readFileSync(pathInvoices));
invoice = invoice[0];
function usd(aNumber) {
return new Intl.NumberFormat("en-US", { styyle: "currency", currency: "USD", minimumFractionDigits: 2 }).format(aNumber/100);
}
function renderPlainText(data) {
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)} (${perf.audience} seats)\n`;
}
result += `Amount owed is ${usd(data.totalAmount)}\n`;
result += `You earned ${data.totalVolumeCredits} credits\n`;
return result;
}
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.amout)}</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 statement(invoice, plays) {
return renderPlainText(createStatementData(invoice, plays));
}
let ret = statement(invoice, plays);
console.log(ret);