Kent Beck Implementation Pattern Principles(4)-Logic And Date Together
這次要介紹的原則:Logic And Data Together,語意非常直白,就是程式碼的邏輯要跟處理的資料越近越好。
看看原文的解釋:
Put logic and the data it operates on near each other, in the same method if possible, or the same object, or at least the same package.
為什麼要Logic And Date Together呢?
看看原文的解釋:
To make a change, the logic and data are likely to have to change at the same time. If they are together, then the consequences of changing them will remain local.
概念很簡單,就是要達到Local Consequence的效果,讓我們的程式好修改。
各位應該都有為了增加一個功能而修改Class A,結果A使用到的另一個Class B也要跟著修改,這其實是一種Code Smell:Shoutgun surgery,大意就是修改時不能簡單修改一個模組,而是修改數個模組,像是散彈槍一樣,擴散開來。
常見的例子有:
- 修改資料庫影響商業邏輯,ex:專案建立一堆資料庫表格,透過這些表格去實作商業邏輯也沒有Domain Object(Anti-Pattern:Database Schema Driven),很容易產生這種問題。
- 修改商業邏輯影響使用者介面(UI),ex:沒有把UI Layer跟Domain Layer適當分割,全部寫在UI Layer(Anti-Pattern:Smart UI),很容易產生這種問題。
當我們沒有好好遵守Logic And Date Together時,代表物件聚合度很低,也代表封裝沒有做好(只能透過行為去對內部屬性做運算,而不是用getter把其他物件資料拿出來在做運算)。
那麼應該要怎麼重構程式碼,以達到Logic And Data Together呢?
其實很簡單,透過Extract Method、Move Method、Move Field,這些重構手段,把適當的邏輯和資料捆綁在一起。
範例程式碼如下:
public class Money {
private double price;
Money(final double price) {
this.price = price;
}
double getPrice() {
return this.price;
}
void setPrice(double newPrice) {
this.price = newPrice;
}
public static void main(String[] args) {
Money money = new Money(50);
double price = money.getPrice();
money.setPrice(price + 50);
}
}
上述程式碼可以看到Money物件,只有getter、setter用來操作物件屬性,而沒有適當邏輯。
在Main函數,我們獲得Money物件的價錢,在使用setter修改價錢使其增加50元。
這些做法有許多缺點:
- 物件變成Data Class,沒有任何行為,物件應該要提供Operation,而不是一堆getter、setter。
- 物件封裝沒有確實,外部可以隨意修改物件的內部屬性,會意外破壞物件invariant。
- 沒有把行為封裝在物件上,會導致一堆重複程式碼,違反Local Consequence原則。
- 變動價錢的邏輯(增加50元)和資料(member variable price)沒有在一起,變動時要修改Money和Main兩個地方。
解決辦法如下:
- 用Move Method把變動價錢的邏輯移到Money物件身上,
- Main使用Money物件提供的Operation去操作內部屬性,而不是用getter、setter把資料抓出來運算之後再塞回去。
public class Money {
private double price;
Money(final double price) {
this.price = price;
}
void addPrice(double newPrice) {
this.price += newPrice;
}
@Override
public String toString() {
return String.valueOf(this.price);
}
public static void main(String[] args) {
Money money = new Money(50);
money.addPrice(50);
System.out.println(money);
}
}
修改後的程式碼沒有了getter、setter,而Main使用Money物件的Operation:addPrice去修改金額。
修改完的好處:
- 當邏輯變動時,只需修改Money物件,其他地方不會受到影響。
- 使用Ubiquitous Language(addPrice),而不是getter、setter這些low level operation,因此可讀性、溝通性增加。
- Money物件維持自身的invariant。
結論:Logic And Data Together跟OOAD的information expert有異曲同工之妙,都是為了達到Local Consequence。