作者在書中提醒我們這件事,我們使用私有 (private) 變數的理由是「不希望任何人依賴這些變數」,但是有許多工程師會自動為它們加上 getter 與 setter,讓私有變數如同公用 (public) 變數一般。
延伸閱讀: 🔗 Why do we need private variables?
資料結構 (Data Structure) 與物件 (Object) 是反對稱性的存在:
- 資料結構
- 將資料直接暴露在外,可以直接對資料讀取與寫入。例如:list、dict、set 等。
- 結構化(Procedural)的程式碼(使用資料結構的程式碼)容易添加新的function,而不需要變動已有的資料結構
- 物件
- 將資料隱藏起來,提供可以操作這些資料的函式在外面。例如: 🕸 Crawler、🕸 CrawlerRunner、 🕸 CrawlerProcess。
- 物件導向的程式碼容易添加新的class,而不用變動已有的function
- 結構化的程式碼難以添加新的資料結構,因為必須改變所有的function。而物件導向的程式碼難以添加新的function,因為必須改變所有的class
將資料實現的過程隱藏起來,就是一種抽象化的過程。
例子: 圖型
-
Procedural:
public class Square { public Point topLeft; public double side; } public class Rectangle { public Point topLeft; public double height; public double width; } public class Circle { public Point center; public double radius; } public class Geometry { public final double PI = 3.141592653589793; public double area(Object shape) throws NoSuchShapeException { if (shape instanceof Square) { Square s = (Square)shape; return s.side * s.side; } else if (shape instanceof Rectangle) { Rectangle r = (Rectangle)shape; return r.height * r.width; } else if (shape instanceof Circle) { Circle c = (Circle)shape; return PI * c.radius * c.radius; } throw new NoSuchShapeException(); } }
-
OOP
public class Square implements Shape { private Point topLeft; private double side; public double area() { return side*side; } } public class Rectangle implements Shape { private Point topLeft; private double height; private double width; public double area() { return height * width; } } public class Circle implements Shape { private Point center; private double radius; public final double PI = 3.141592653589793; public double area() { return PI * radius * radius; } }
- 德摩特爾法則又可以稱作「最少知識原則」
- 符合 LoD 的函式要求在類別C 中的函式 f 只能呼叫以下幾種類型的函式:
- C 本身
- 任何由f所產生的物件
- 任何當作參數傳遞給f的物件
- C類別裡實體變數所持有的物件
LoD 範例使用 Python:
e = E()
class O:
def __init__(self):
self.a = A()
def m(self, b):
result = self.f() # O 本身的函式
b.execute() # m 的參數
d = D() # 在 m 中建立的物件
d.join()
self.a.run() # 宣告在 O 中的物件
e.jump() # 被 O 存取的全域變數,並且在 m 的 scope 中
def f(self):
return 1+1
如果你在網路上搜尋,也許會看到有人說:「LoD 禁止在一行程式碼中使用多個 dot」,這句話是一半對一半錯,使用多個 dot 的前題是「你知道它們是資料結構還是物件」。
如果是物件,則破壞了物件封裝;而如果是資料結構,資料結構本來就暴露了資料在外面,所以透過巢狀存取資料是沒有問題的。
// 破壞封裝,違反 LoD
class A {
fun(b) {
b.getOption().getSelection()
}
}
// 存取資料結構,沒有違反 LoD
data = {
a: {
b: 2
}
}
console.log(data.a.b)
你不應該讓一個函式知道太多事情,否則會破壞物件封裝原本的意義。
- 只是一個帶有資料的物件,沒有任何的操作行為
- 常用於和資料庫溝通
- 例子
public class Address {
private String street;
private String streetExtra;
private String city;
private String state;
private String zip;
public Address(String street, String streetExtra,
String city, String state, String zip) {
this.street = street;
this.streetExtra = streetExtra;
this.city = city;
this.state = state;
this.zip = zip;
}
public String getStreet() {
return street;
}
public String getStreetExtra() {
return streetExtra;
}
public String getCity() {
return city;
}
public String getState() {
return state;
}
public String getZip() {
return zip;
}
}
在物件與資料結構這個章節,我們首先談到物件的私有變數不應該隨意將它們暴露在外,物件即是要隱藏其內容,只讓外部的人看見它們想公開的行為;因此,開發時必須小心謹慎管理私有變數,否則私有變數將如同公有變數影響整個系統。
此外,我們談到資料結構與物件的不同之處,你經常會在寫程式時用到這兩種技巧,但是它們的意義是不一樣的,為了方便管理與維護,必須時時刻刻在意究竟現在寫的是資料結構還是物件。
最後,德摩特爾法則是一種撰寫程式的規範,我們不應該知道太多物件內的事情,否則就會破壞物件的封裝,讓被使用的物件難以被維護。