iOS设计模式之组合模式

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情

  • 本文主要介绍iOS设计模式中的组合模式,组合模式是一种部分-整体的层次结构,可以看成一种抽象集合

1. 什么事组合模式?

你有两类对象:  产品和 盒子 。 一个盒子中可以包含多个 产品或者几个较小的 盒子 。 这些小 盒子中同样可以包含一些 产品或更小的 盒子 , 以此类推。
假设你希望在这些类的基础上开发一个定购系统。 订单中可以包含无包装的简单产品, 也可以包含装满产品的盒子……以及其他盒子。 此时你会如何计算每张订单的总价格呢?
你可以尝试直接计算: 打开所有盒子, 找到每件产品, 然后计算总价。 这在真实世界中或许可行, 但在程序中, 你并不能简单地使用循环语句来完成该工作。 你必须事先知道所有 产品和 盒子的类别, 所有盒子的嵌套层数以及其他繁杂的细节信息。 因此, 直接计算极不方便, 甚至完全不可行。 组合模式建议使用一个通用接口来与 产品和 盒子进行交互, 并且在该接口中声明一个计算总价的方法。
那么方法该如何设计呢? 对于一个产品, 该方法直接返回其价格; 对于一个盒子, 该方法遍历盒子中的所有项目, 询问每个项目的价格, 然后返回该盒子的总价格。 如果其中某个项目是小一号的盒子, 那么当前盒子也会遍历其中的所有项目, 以此类推, 直到计算出所有内部组成部分的价格。 你甚至可以在盒子的最终价格中增加额外费用, 作为该盒子的包装费用。
该方式的最大优点在于你无需了解构成树状结构的对象的具体类。 你也无需了解对象是简单的产品还是复杂的盒子。 你只需调用通用接口以相同的方式对其进行处理即可。 当你调用该方法后, 对象会将请求沿着树结构传递下去。
我么的会员体系同样是一种树状机构,我们会员会有多个下线会员,同时也会有上线会员,也就是父节点和子节点。同时我们定义一些行为比如卖货提成给父节点,通过迭代器计算任意一个会员以及其下线会员的提成收入。
在面向对象软件设计中我们借用类似的思想。组合结构可以非常复杂,而且其内部不应该暴漏给客户端。我们需要通过统一的接口把整个复杂结构作为一个整体来使用,所以客户端不必知道某个节点事什么就能够使用它。

组合模式: 将对象组合成树形结构表示“部分-整体”的层次结构。组合使得用户对单个对象和组合的使用具有一致性。

2. 什么时候使用组合模式

在以下情形,自然会想到使用这一模式:

  • 想获得对象抽象树形表示(部分-整体层次结构);
  • 想让客户端统一处理组合结构中的所有对象。

3. Cocoa Touch框架中使用组合模式

在Cocoa Touch框架中,UIView被组织成一个组合结构。每个UIView的实例可以包含UIView的其他实例,形成统一的树状结构,让客户端对单个UIView对象UIView的组合统一对待。
视图组合结构参与绘图事件处理。当请求超视图为显示进行渲染时,消息会先在超视图被处理,然后传给其子视图,消息会传播到遍及整个树的其他子视图。因为它们都是相同类型UIView,可以被统一处理,而且UIView层次结构的一个分支也可以同样当做一个视图来处理。
统一的视图也作为一个响应者链,用于事件处理和消息处理。

4. 代码展示

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
    }
}

/// Leaf 类代表结束对象的组成。叶子不能有任何子类。
/// 通常,实际工作的是 Leaf 对象,而 Composite 对象只委托给它们的子组件。
class Leaf: Component {

    var parent: Component?

    func operation() -> String {
        return "Leaf"
    }
}

/// Composite 类表示可能有
/// 子级的复杂组件。通常,复合对象将实际工作委托给它们的
/// 子对象,然后“汇总”结果。
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
    }

    /// Composite 以特定方式执行其主要逻辑。它
    /// 递归遍历其所有子节点,收集和汇总
    /// 它们的结果。由于复合体的子代将这些调用传递给它们的
    /// 子代等等,因此会遍历整个对象树。
    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())
    }
}
复制代码

执行结果

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)
复制代码

5. 总结

组合模式的主要意图是让树状结构中每个节点具有相同的抽象接口。这样整个结构可作为一个统一的抽象结构使用,而不暴漏其内部表示。对每个节点(叶节点或组合体)的任何操作,可以通过协议或抽象类中定义的相同接口来进行。组合模式总是跟着迭代器模式一起使用,以遍历组合对象中的每一个项目。

猜你喜欢

转载自juejin.im/post/7107243291459977229
今日推荐