本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。
复合模式(The Composite Pattern)
复合模式将对象组合成树形结构以表示“部分-整体”的层次结构,它使得客户对单个对象和复合对象的使用具有一致性。
示例工程
OS X Command Line Tool工程:
CarParts.swift
class Part {
let name:String
let price:Float
init(name:String, price:Float) {
self.name = name
self.price = price
}
}
class CompositePart {
let name:String
let parts:[Part]
init(name:String, parts:Part...) {
self.name = name
self.parts = parts;
}
}
我们定义了两个汽车零件类用来修理汽车。Part类代表独立的汽车零件,例如轮胎。 CompositePart 代表一些列组合而成的零件,例如包含轮胎的车轮。 CompositePart 包含了一个Part元素构成的数组,用来代表构成复合零件的各个独立零件。
Orders.swift
import Foundation
class CustomerOrder {
let customer:String
let parts:[Part]
let compositeParts:[CompositePart]
init(customer:String, parts:[Part], composites:[CompositePart]) {
self.customer = customer
self.parts = parts
self.compositeParts = composites
}
var totalPrice:Float {
let total = parts.reduce(0){
subtotal,part -> Float in
return subtotal + part.price
}
return compositeParts.reduce(total){
subtotal,cpart -> Float in
return cpart.parts.reduce(subtotal){
tmptotal,tmpart -> Float in
return tmptotal + tmpart.price
}
}
}
func printDetails() {
print("Order for \(customer): Cost: \(formatCurrencyString(totalPrice))")
}
func formatCurrencyString(number:Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return formatter.stringFromNumber(number) ?? ""
}
}
CustomerOrder 类代表Part和CompositePart对象的一个订单。
main.swift
let doorWindow = CompositePart(name: "DoorWindow", parts:
Part(name: "Window", price: 100.50),
Part(name: "Window Switch", price: 12))
let door = CompositePart(name: "Door", parts:
Part(name: "Window", price: 100.50),
Part(name: "Door Loom", price: 80),
Part(name: "Window Switch", price: 12),
Part(name: "Door Handles", price: 43.40))
let hood = Part(name: "Hood", price: 320)
let order = CustomerOrder(customer: "Bob", parts: [hood],
composites: [door, doorWindow])
order.printDetails()
运行程序,得到下面输出:
Order for Bob: Cost: ¥ 668.40
理解复合模式解决的问题
示例工程显示了两个不同的问题:
第一个问题是我们限制了零件的层级。当我们创建一个CompositePart来代表车门时,不得不去创建Part对象代表车窗和车窗开关。同时当我们创建车门窗时又创建了相同的对象,虽然创建车门时已经创建了。
第二个问题是操作零件的类需要知道CompositePart和Part的详细构成,这点我们可以从求总价的计算中看出:
......
var totalPrice:Float {
let total = parts.reduce(0){
subtotal,part -> Float in
return subtotal + part.price
}
return compositeParts.reduce(total){
subtotal,cpart -> Float in
return cpart.parts.reduce(subtotal){
tmptotal,tmpart -> Float in
return tmptotal + tmpart.price
}
}
}
......
理解复合模式
复合模式通过树型方式和定义允许独立和复合对象一致的协议来解决上述问题。
实现复合模式
首先是定义协议,协议是复合模式的心脏。
(1) 树角色(Component):代表复合对象和单个对象共有特征的抽象,该角色是一个抽象类,它有一个对象列表,定义了一些增删对象的操作。
(2) 子树角色(Composite):代表复合对象。树上有很多子树,子树也是树的一种。
(3) 树叶角色(Leaf):独立对象,也就是Component中的操作的单个对象。树叶是只一个结点的树,也是特殊的一种树。
CarParts.swift
protocol CarPart {
var name:String { get }
var price:Float { get }
}
class Part : CarPart {
let name:String
let price:Float
init(name:String, price:Float) {
self.name = name
self.price = price
}
}
class CompositePart : CarPart {
let name:String
let parts:[CarPart]
init(name:String, parts:CarPart...) {
self.name = name
self.parts = parts
}
var price:Float {
return parts.reduce(0){
total,part -> Float in
return total + part.price
}
}
}
接着我们应用复合模式:
Orders.swift
import Foundation
class CustomerOrder {
let customer:String
let parts:[CarPart]
init(customer:String, parts:[CarPart]) {
self.customer = customer
self.parts = parts
}
var totalPrice:Float {
return parts.reduce(0){
total,part -> Float in
return total + part.price
}
}
func printDetails() {
print("Order for \(customer): Cost: \(formatCurrencyString(totalPrice))");
}
func formatCurrencyString(number:Float) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
return formatter.stringFromNumber(number) ?? ""
}
}
最后,修改main.swift:
let doorWindow = CompositePart(name: "DoorWindow", parts:
Part(name: "Window", price: 100.50),
Part(name: "Window Switch", price: 12))
let door = CompositePart(name: "Door", parts:
doorWindow,
Part(name: "Door Loom", price: 80),
Part(name: "Door Handles", price: 43.40))
let hood = Part(name: "Hood", price: 320)
let order = CustomerOrder(customer: "Bob", parts: [hood, door, doorWindow])
order.printDetails()
可以看出我们上面创建CompositePart时直接用了doorWindow对象。运行程序,输出一下内容:
Order for Bob: Cost: ¥ 668.40