今天我們來介紹一個相當實用的 Pattern: Composite,這個 Pattern 在很多知名的框架都有被套用。它雖然看起來樸實,但是卻威力強大唷!話不多說,我們來了解 Composite 的內容吧!
在各類的繪圖軟體中,允許使用者可以畫單純的圖形外也能夠建立複雜的圖形,使用者也可透過把大量的單純圖性組成群組的方式來建造複雜的圖形。簡單要達成這樣的目的的實作就是定義這些圖形的物件,比如說:Line、Text 這樣的圖元物件,再加上一些物件像是這些圖元物件的容器。而這個時候就有一個問題產生了:程式碼必須要用不同的方式來對待對待圖元物件以及容器物件,而這樣就會讓整個應用程式的程式碼變得相當的複雜。Composite 的目的,就是要抽象化出一個抽象物件同時代表了圖元物件以及容器物件。
尚未套入 Composite 的繪圖軟體的 Class Diagram 會長這樣子:
若是使用 Compsite 的話,則會變成這樣子:
我們可以看到當使用了 Composite 之後,Client 就再也不需要去知道未來擴充的任何圖形物件的細節了!
Composite 讓 Client 可以用一致的方式看待個體和組成物件!
也就是說,Composite 這個 Pattern 的目的,就是讓 Client 不用在乎究竟所要處理的物件是一種個體物件或著是組成物件,針對其所定義的一致介面進行操作。
就用 PicCollage 中的一些角色來撰寫範例。我們有 Engneer, Designer, Coach(還有更多角色,但是為了保持範例簡單就先介紹這些)。Engineer 是工程師,Designer 是設計師,而 Coach 則負責為其所負責的某些人提供工作、職涯上的協助。而範例將用 Swift 來撰寫!不過因為 Swift 裡面並沒有 Abstract Class 的概念,我們可以用 Protocol 來實現 Composite。
protocol Employee: class {
var name: String { get }
func showInformation(indentCount: Int)
}
class Engineer: Employee {
private(set) var name: String
init(name: String) {
self.name = name
}
func showInformation(indentCount: Int) {
let indent = String(repeating: "\t", count: indentCount)
print("\(indent)Engineer: \(name)")
}
func develop() {
}
}
class Designer: Employee {
private(set) var name: String
init(name: String) {
self.name = name
}
func showInformation(indentCount: Int) {
let indent = String(repeating: "\t", count: indentCount)
print("\(indent)Designer: \(name)")
}
func design() {
}
}
class Coach: Employee {
var name: String
private var relations = [Employee]()
init(name: String) {
self.name = name
}
func showInformation(indentCount: Int) {
let indent = String(repeating: "\t", count: indentCount)
print("\(indent)Coach: \(name)")
print("\(indent)Coaching: [")
for relation in relations {
relation.showInformation(indentCount: indentCount + 1)
}
print("\(indent)]")
}
func add(relation: Employee) {
if !relations.contains(where: { $0 === relation}) {
relations.append(relation)
}
}
func remove(relation: Employee) {
if let index = relations.firstIndex(where: {$0 === relation}) {
relations.remove(at: index)
}
}
}
let russ = Coach(name: "Russ")
let chris = Coach(name: "Chris")
let ming = Designer(name: "Ming")
let yy = Coach(name: "YY")
let raymond = Engineer(name: "Raymond")
let simon = Engineer(name: "Simon")
russ.add(relation: yy)
yy.add(relation: raymond)
yy.add(relation: simon)
chris.add(relation: ming)
let employees: [Employee] = [russ, chris]
employees.forEach { (employee) in
employee.showInformation(indentCount: 0)
print("")
}
Output
Coach: Russ
Coaching: [
Coach: YY
Coaching: [
Engineer: Raymond
Engineer: Simon
]
]
Coach: Chris
Coaching: [
Designer: Ming
]
其實 Composite 是一個很好的 Pattern,使用上並沒有太大的副作用。這邊還是做一下簡單的優缺點比較:
作者:Charles