/ 设计模式 / 组合模式 / Swift Swift 组合模式讲解和代码示例 组合是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 对于绝大多数需要生成树状结构的问题来说, 组合都是非常受欢迎的解决方案。 组合最主要的功能是在整个树状结构上递归调用方法并对结果进行汇总。 进一步了解组合模式 导航 简介 概念示例 Example Output 真实世界示例 Example Output 复杂度: 流行度: 使用示例: 组合模式在 Swift 代码中很常见, 常用于表示与图形打交道的用户界面组件或代码的层次结构。 识别方法: 组合可以通过将同一抽象或接口类型的实例放入树状结构的行为方法来轻松识别。 以下示例可在 Swift Playgrounds 上使用。 感谢 Alejandro Mohamad 创建了Playground版本。 概念示例 真实世界示例 概念示例 本例说明了组合设计模式的结构并重点回答了下面的问题: 它由哪些类组成? 这些类扮演了哪些角色? 模式中的各个元素会以何种方式相互关联? 了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。 Example.swift: 概念示例 import XCTest /// The base Component class declares common operations for both simple and /// complex objects of a composition. protocol Component { /// The base Component may optionally declare methods for setting and /// accessing a parent of the component in a tree structure. It can also /// provide some default implementation for these methods. var parent: Component? { get set } /// In some cases, it would be beneficial to define the child-management /// operations right in the base Component class. This way, you won't need /// to expose any concrete component classes to the client code, even during /// the object tree assembly. The downside is that these methods will be /// empty for the leaf-level components. func add(component: Component) func remove(component: Component) /// You can provide a method that lets the client code figure out whether a /// component can bear children. func isComposite() -> Bool /// The base Component may implement some default behavior or leave it to /// concrete classes. func operation() -> String } extension Component { func add(component: Component) {} func remove(component: Component) {} func isComposite() -> Bool { return false } } /// The Leaf class represents the end objects of a composition. A leaf can't /// have any children. /// /// Usually, it's the Leaf objects that do the actual work, whereas Composite /// objects only delegate to their sub-components. class Leaf: Component { var parent: Component? func operation() -> String { return "Leaf" } } /// The Composite class represents the complex components that may have /// children. Usually, the Composite objects delegate the actual work to their /// children and then "sum-up" the result. class Composite: Component { var parent: Component? /// This fields contains the conponent subtree. private var children = [Component]() /// A composite object can add or remove other components (both simple or /// complex) to or from its child list. func add(component: Component) { var item = component item.parent = self children.append(item) } func remove(component: Component) { // ... } func isComposite() -> Bool { return true } /// The Composite executes its primary logic in a particular way. It /// traverses recursively through all its children, collecting and summing /// their results. Since the composite's children pass these calls to their /// children and so forth, the whole object tree is traversed as a result. func operation() -> String { let result = children.map({ $0.operation() }) return "Branch(" + result.joined(separator: " ") + ")" } } class Client { /// The client code works with all of the components via the base interface. static func someClientCode(component: Component) { print("Result: " + component.operation()) } /// Thanks to the fact that the child-management operations are also /// declared in the base Component class, the client code can work with both /// simple or complex components. static func moreComplexClientCode(leftComponent: Component, rightComponent: Component) { if leftComponent.isComposite() { leftComponent.add(component: rightComponent) } print("Result: " + leftComponent.operation()) } } /// Let's see how it all comes together. class CompositeConceptual: XCTestCase { func testCompositeConceptual() { /// This way the client code can support the simple leaf components... print("Client: I've got a simple component:") Client.someClientCode(component: Leaf()) /// ...as well as the complex composites. let tree = Composite() let branch1 = Composite() branch1.add(component: Leaf()) branch1.add(component: Leaf()) let branch2 = Composite() branch2.add(component: Leaf()) branch2.add(component: Leaf()) tree.add(component: branch1) tree.add(component: branch2) print("\nClient: Now I've got a composite tree:") Client.someClientCode(component: tree) print("\nClient: I don't need to check the components classes even when managing the tree:") Client.moreComplexClientCode(leftComponent: tree, rightComponent: Leaf()) } } Output.txt: 执行结果 Client: I've got a simple component: Result: Leaf Client: Now I've got a composite tree: Result: Branch(Branch(Leaf Leaf) Branch(Leaf Leaf)) Client: I don't need to check the components classes even when managing the tree: Result: Branch(Branch(Leaf Leaf) Branch(Leaf Leaf) Leaf) 真实世界示例 Example.swift: 真实世界示例 import UIKit import XCTest protocol Component { func accept<T: Theme>(theme: T) } extension Component where Self: UIViewController { func accept<T: Theme>(theme: T) { view.accept(theme: theme) view.subviews.forEach({ $0.accept(theme: theme) }) } } extension UIView: Component {} extension UIViewController: Component {} extension Component where Self: UIView { func accept<T: Theme>(theme: T) { print("\t\(description): has applied \(theme.description)") backgroundColor = theme.backgroundColor } } extension Component where Self: UILabel { func accept<T: LabelTheme>(theme: T) { print("\t\(description): has applied \(theme.description)") backgroundColor = theme.backgroundColor textColor = theme.textColor } } extension Component where Self: UIButton { func accept<T: ButtonTheme>(theme: T) { print("\t\(description): has applied \(theme.description)") backgroundColor = theme.backgroundColor setTitleColor(theme.textColor, for: .normal) setTitleColor(theme.highlightedColor, for: .highlighted) } } protocol Theme: CustomStringConvertible { var backgroundColor: UIColor { get } } protocol ButtonTheme: Theme { var textColor: UIColor { get } var highlightedColor: UIColor { get } /// other properties } protocol LabelTheme: Theme { var textColor: UIColor { get } /// other properties } /// Button Themes struct DefaultButtonTheme: ButtonTheme { var textColor = UIColor.red var highlightedColor = UIColor.white var backgroundColor = UIColor.orange var description: String { return "Default Buttom Theme" } } struct NightButtonTheme: ButtonTheme { var textColor = UIColor.white var highlightedColor = UIColor.red var backgroundColor = UIColor.black var description: String { return "Night Buttom Theme" } } /// Label Themes struct DefaultLabelTheme: LabelTheme { var textColor = UIColor.red var backgroundColor = UIColor.black var description: String { return "Default Label Theme" } } struct NightLabelTheme: LabelTheme { var textColor = UIColor.white var backgroundColor = UIColor.black var description: String { return "Night Label Theme" } } class CompositeRealWorld: XCTestCase { func testCompositeRealWorld() { print("\nClient: Applying 'default' theme for 'UIButton'") apply(theme: DefaultButtonTheme(), for: UIButton()) print("\nClient: Applying 'night' theme for 'UIButton'") apply(theme: NightButtonTheme(), for: UIButton()) print("\nClient: Let's use View Controller as a composite!") /// Night theme print("\nClient: Applying 'night button' theme for 'WelcomeViewController'...") apply(theme: NightButtonTheme(), for: WelcomeViewController()) print() print("\nClient: Applying 'night label' theme for 'WelcomeViewController'...") apply(theme: NightLabelTheme(), for: WelcomeViewController()) print() /// Default Theme print("\nClient: Applying 'default button' theme for 'WelcomeViewController'...") apply(theme: DefaultButtonTheme(), for: WelcomeViewController()) print() print("\nClient: Applying 'default label' theme for 'WelcomeViewController'...") apply(theme: DefaultLabelTheme(), for: WelcomeViewController()) print() } func apply<T: Theme>(theme: T, for component: Component) { component.accept(theme: theme) } } class WelcomeViewController: UIViewController { class ContentView: UIView { var titleLabel = UILabel() var actionButton = UIButton() override init(frame: CGRect) { super.init(frame: frame) setup() } required init?(coder decoder: NSCoder) { super.init(coder: decoder) setup() } func setup() { addSubview(titleLabel) addSubview(actionButton) } } override func loadView() { view = ContentView() } } /// Let's override a description property for the better output extension WelcomeViewController { open override var description: String { return "WelcomeViewController" } } extension WelcomeViewController.ContentView { override var description: String { return "ContentView" } } extension UIButton { open override var description: String { return "UIButton" } } extension UILabel { open override var description: String { return "UILabel" } } Output.txt: 执行结果 Client: Applying 'default' theme for 'UIButton' UIButton: has applied Default Buttom Theme Client: Applying 'night' theme for 'UIButton' UIButton: has applied Night Buttom Theme Client: Let's use View Controller as a composite! Client: Applying 'night button' theme for 'WelcomeViewController'... ContentView: has applied Night Buttom Theme UILabel: has applied Night Buttom Theme UIButton: has applied Night Buttom Theme 概念示例 真实世界示例