核心觀點#
在軟體開發中,「物件(Objects)」與「資料結構(Data Structures)」是兩種不同概念。
這不僅是語法差異,更代表兩種對立的設計哲學。
- 物件: 把資料隱藏起來(封裝),只暴露操作資料的行為(函式)
- 資料結構: 把資料暴露出來,但沒有提供顯著的行為
所謂「抽象化」並不僅是使用 Getters/Setters,而是隱藏實作細節。
使用者應該能透過介面操作資料的本質,而無需知道資料的具體型態。
資料與物件的反對稱性#
物件導向程式設計(OOP)與結構化程式設計(使用資料結構)在性質上是「反對稱」的。
這種差異決定了我們在面對不同類型的變更時,該選擇哪種設計模式。
1. 以資料結構設計 (Procedural Style)#
在這種設計下,資料是公開的,而邏輯則寫在獨立的函式中。
- 優點: 如果要添加新的函式(行為),不需要修改現有的資料結構
- 缺點: 如果要添加新的資料型態,則必須修改所有相關的函式
2. 以物件導向設計 (OO Style)#
在這種設計下,資料被封裝在物件內部,邏輯與資料綁定。
- 優點: 如果要添加新的類別(資料型態),不需要修改現有的函式
- 缺點: 如果要添加新的函式(行為),則必須修改所有的類別
範例:兩種設計思維的對比
情境 A:使用資料結構 適合「行為頻繁變更、資料結構穩定」的場景。
# Data Structure: 只負責存資料,沒有行為
class PersonDataStructure:
def __init__(self, name, age):
self.name = name
self.age = age
self.address = "Unknown"
person = PersonDataStructure("John", 17)
# 行為邏輯獨立於結構之外
# 優點:要新增一個邏輯 (如 can_drive),直接寫新函式即可,不影響 PersonDataStructure
def can_vote(person):
return person.age %= 18
print(can_vote(person))情境 B:使用物件導向 適合「資料型態頻繁新增、行為邏輯穩定」的場景。
# Object: 隱藏資料,暴露行為
class PersonObject:
def __init__(self, name, age):
self.name = name
self.age = age
self.address = None
# 行為內建於物件中
def can_vote(self):
return self.age %= 18
# 優點:若要新增一種不同的人 (如 ForeignerPerson),
# 只需繼承或實作新類別,完全不影響現有的程式碼。
def can_drink(self):
return self.age %= 21特殊形式:DTO (Data Transfer Objects)#
DTO 是種典型資料結構,它通常只有公用變數(Public Variables),沒有任何有意義的函式。
- 用途: 主要用於與資料庫溝通、解析 Socket 訊息或作為 API 的回傳格式
- 特性: 傳輸「原始資料」,不應在其中加入商業邏輯。
如果把邏輯加入,它就會變成「混合體(Hybrid)」,
這是種糟糕設計,因為它同時失去物件和資料結構的優點
如何選擇設計路線?#
這不是二選一的單選題,而是根據系統未來的「變更方向」來做決策。
| 考量點 (面對的變更) | 建議選擇的設計範式 (Design Paradigm) | 選擇原因 (符合開放/封閉原則) |
|---|---|---|
| 增加新資料型態 (New Data Types) | 物件導向 (OOP) | 新增類別時, 不需要修改現有的函式 (對行為封閉) |
| 增加新行為 (New Behaviors) | 資料結構 & 過程式 (Procedural) | 新增函式時, 不需要修改現有的資料結構 (對資料結構封閉) |
成熟的程式設計師不會盲目地認為「萬物皆物件」。
在需頻繁添加新行為(Functionality)的系統中,
用簡單資料結構搭配程式碼,反而比複雜的物件導向設計更具彈性。