持续创作,加速成长!这是我参与「掘金日新计划 · 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. 总结
组合模式的主要意图是让树状结构中每个节点具有相同的抽象接口
。这样整个结构可作为一个统一的抽象结构使用
,而不暴漏
其内部表示。对每个节点(叶节点或组合体)的任何操作,可以通过协议或抽象类中定义的相同接口
来进行。组合模式总是跟着迭代器
模式一起使用,以遍历
组合对象中的每一个项目。