Java改寫重構第2版第一個示例
阿新 • • 發佈:2020-10-18
# 寫在前面
**《重構:改善既有程式碼的設計》**是一本經典的軟體工程必讀書籍。作者馬丁·福勒強調**重構技術是以微小的步伐修改程式**。
**但是**,從國內的情況來而論,“重構”的概念表裡分離。大家往往喜歡打著“重構”的名號,實際上卻乾的是“刀劈斧砍”的勾當。產生這種現象的原因,一方面是程式設計師希望寫出可維護,可複用,可拓展,靈活性好的程式碼,使系統具長期生命力;另一方面,重構的紮實功夫要學起來、做起來,頗不是一件輕鬆的事,且不說詳盡到近乎瑣碎的重構手法,光是單元測試一事,怕是已有九成同行無法企及。所以,重構變質為重寫,研發團隊拿著公司的經費,幹著“重複造輪子”的事兒,最終“重構”後的軟體仍然不能使人滿意,反倒是一堆問題,使用者不願意買單,程式設計師不願意繼續維護,管理人員也擔著巨大的壓力。痛苦的滋味在心底蔓延。
轉頭來看,Martin Fowler 時隔 20 年後的第 2 版,沒有照搬第一版,而是把工夫做得更加紮實了,我有幸發現這本書,解我之惑,實屬幸事一件。由於第 2 版中使用 javascript 作為展現重構手法的語言,可是本人慣用的語言卻是 Java,因此本著 “實踐出真知” 的原則,我想嘗試用 Java 語言來對示例進行改寫,在分享思路的同時,也希望能夠有人與我討論,甚至指出我的錯誤,在此深表感謝。
> 廢話不多說了,我們趕緊開始
# 專案地址
[Gitee 專案地址](https://gitee.com/kendoziyu/code-refactoring-example)
```bash
git clone https://gitee.com/kendoziyu/code-refactoring-example.git
```
# 起點
> 有些看到文章的小夥伴,可能還沒拿到這本《重構2》,所以我先把原文需求貼出來,另外在改寫時,我會參考並結合《重構》第 1 版中的程式碼。
設想有一個戲劇演出團,演員們經常要去各種場合表演戲劇。通常客戶(customer)會指定幾齣劇目,而劇團則根據觀眾(audience)人數及劇目型別向客戶收費。該團目前出演兩種戲劇:悲劇(tragedy)和喜劇(comedy)。給客戶發出賬單時,劇團還根據到場觀眾的數量給出“觀眾量積分”(volume credit)優惠,下次客戶再請劇團表演時,可以使用積分獲得折扣————你可以把它看作一種提升客戶忠誠度的方式。
該劇團將 **劇目** 的資料儲存在一個簡單的 JSON 檔案中。
**_plays.json..._**
```java
{
"hamlet":{"name":"Hamlet", "type":"tragedy"},
"as-like":{"name":"As You Like It", "type":"comedy"},
"othello":{"name":"Othello", "type":"tragedy"}
}
```
他們開出的 **賬單** 也儲存在一個 JSON 檔案裡。
**_invoices.json..._**
```java
{
"customer":"BigCo",
"performances":[
{
"playId":"hamlet",
"audience":55
},
{
"playId":"as-like",
"audience":35
},
{
"playId":"othello",
"audience":40
}
]
}
```
等下我要來解析這兩組 JSON 物件,不妨先來分析一下實體類之間的關係:
![](https://img2020.cnblogs.com/blog/1730512/202010/1730512-20201016231652659-1425591927.jpg)
發票(Invoice)
接著,書中直接就給出了 **列印賬單資訊** 的函式 **function statement(invoice, plays) {}**。注意,《重構2》書中有提到,
> 當我在程式碼塊上方使用了斜體(中文對應楷體)標記的題頭 _“function xxx”_ 時,表明該程式碼位於題頭所在函式、檔案或類的作用域內。
所以,結合《重構(第 1 版)》中的 Java 示例,我對第二版的示例做了一些改造:
_Statement.java..._
```java
public class Statement {
private Invoice invoice;
private Map plays;
public Statement(Invoice invoice, Map plays) {
this.invoice = invoice;
this.plays = plays;
}
public String show() {
int totalAmount = 0;
int volumeCredits = 0;
String result = String.format("Statement for %s\n", invoice.getCustomer());
StringBuilder stringBuilder = new StringBuilder(result);
Locale locale = new Locale("en", "US");
NumberFormat format = NumberFormat.getCurrencyInstance(locale);
for (Performance performance : invoice.getPerformances()) {
Play play = plays.get(performance.getPlayId());
int thisAmount = 0;
switch (play.getType()) {
case "tragedy":
thisAmount = 40000;
if (performance.getAudience() > 30) {
thisAmount += 1000 * (performance.getAudience() - 30);
}
break;
case "comedy":
thisAmount = 30000;
if (performance.getAudience() > 20) {
thisAmount += 10000 + 500 *(performance.getAudience() - 20);
}
thisAmount += 300 * performance.getAudience();
break;
default:
throw new RuntimeException("unknown type:" + play.getType());
}
volumeCredits += Math.max(performance.getAudience() - 30, 0);
if ("comedy".equals(play.getType())) {
volumeCredits += Math.floor(performance.getAudience() / 5);
}
stringBuilder.append(String.format(" %s: %s (%d seats)\n", play.getName(), format.format(thisAmount/100), performance.getAudience()));
totalAmount += thisAmount;
}
stringBuilder.append(String.format("Amount owed is %s\n", format.format(totalAmount/100)));
stringBuilder.append(String.format("You earned %s credits\n", volumeCredits));
return stringBuilder.toString();
}
}
```
值得一提的有:
1. 從 Java 1.7 開始,**switch** 開始支援字串了
2. **_NumberFormat.getCurrencyInstance_** 這個 API,可以為我們列印貨幣資訊
_Main.java..._
```java
public class Main {
static final String plays = "{" +
"\"hamlet\":{\"name\":\"Hamlet\",\"type\":\"tragedy\"}," +
"\"as-like\":{\"name\":\"As You Like It\",\"type\":\"comedy\"}," +
"\"othello\":{\"name\":\"Othello\",\"type\":\"tragedy\"}" +
"}";
static final String invoices = "[{" +
"\"customer\":\"BigCo\",\"performances\":[" +
"{\"playId\":\"hamlet\",\"audience\":55}" +
"{\"playId\":\"as-like\",\"audience\":35}" +
"{\"playId\":\"othello\",\"audience\":40}" +
"]" +
"}]";
public static void main(String[] args) {
TypeReference
發票(Invoice)
public class Invoice { private String customer; private List<Performance> performances; public String getCustomer() { return customer; } public void setCustomer(String customer) { this.customer = customer; } public List
getPerformances() { return performances; } public void setPerformances(List performances) { this.performances = performances; } }
表演(Performance)
public class Performance { private String playId; private int audience; public String getPlayId() { return playId; } public void setPlayId(String playId) { this.playId = playId; } public int getAudience() { return audience; } public void setAudience(int audience) { this.audience = audience; } }
劇目(Play)
public class Play {
private String name;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}