 
                Swift 装饰模式讲解和代码示例
装饰是一种结构设计模式, 允许你通过将对象放入特殊封装对象中来为原对象增加新的行为。
由于目标对象和装饰器遵循同一接口, 因此你可用装饰来对对象进行无限次的封装。 结果对象将获得所有封装器叠加而来的行为。
复杂度:
流行度:
使用示例: 装饰在 Swift 代码中可谓是标准配置, 尤其是在与流式加载相关的代码中。
识别方法: 装饰可通过以当前类或对象为参数的创建方法或构造函数来识别。
                                                            以下示例可在
                                Swift Playgrounds
                                上使用。
                                                    
                        
                                                            感谢
                                Alejandro Mohamad 创建了Playground版本。
                                                    
                    概念示例
本例说明了装饰设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。
Example.swift: 概念示例
import XCTest
/// The base Component interface defines operations that can be altered by
/// decorators.
protocol Component {
    func operation() -> String
}
/// Concrete Components provide default implementations of the operations. There
/// might be several variations of these classes.
class ConcreteComponent: Component {
    func operation() -> String {
        return "ConcreteComponent"
    }
}
/// The base Decorator class follows the same interface as the other components.
/// The primary purpose of this class is to define the wrapping interface for
/// all concrete decorators. The default implementation of the wrapping code
/// might include a field for storing a wrapped component and the means to
/// initialize it.
class Decorator: Component {
    private var component: Component
    init(_ component: Component) {
        self.component = component
    }
    /// The Decorator delegates all work to the wrapped component.
    func operation() -> String {
        return component.operation()
    }
}
/// Concrete Decorators call the wrapped object and alter its result in some
/// way.
class ConcreteDecoratorA: Decorator {
    /// Decorators may call parent implementation of the operation, instead of
    /// calling the wrapped object directly. This approach simplifies extension
    /// of decorator classes.
    override func operation() -> String {
        return "ConcreteDecoratorA(" + super.operation() + ")"
    }
}
/// Decorators can execute their behavior either before or after the call to a
/// wrapped object.
class ConcreteDecoratorB: Decorator {
    override func operation() -> String {
        return "ConcreteDecoratorB(" + super.operation() + ")"
    }
}
/// The client code works with all objects using the Component interface. This
/// way it can stay independent of the concrete classes of components it works
/// with.
class Client {
    // ...
    static func someClientCode(component: Component) {
        print("Result: " + component.operation())
    }
    // ...
}
/// Let's see how it all works together.
class DecoratorConceptual: XCTestCase {
    func testDecoratorConceptual() {
        // This way the client code can support both simple components...
        print("Client: I've got a simple component")
        let simple = ConcreteComponent()
        Client.someClientCode(component: simple)
        // ...as well as decorated ones.
        //
        // Note how decorators can wrap not only simple components but the other
        // decorators as well.
        let decorator1 = ConcreteDecoratorA(simple)
        let decorator2 = ConcreteDecoratorB(decorator1)
        print("\nClient: Now I've got a decorated component")
        Client.someClientCode(component: decorator2)
    }
}
Output.txt: 执行结果
Client: I've got a simple component
Result: ConcreteComponent
Client: Now I've got a decorated component
Result: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent))
真实世界示例
Example.swift: 真实世界示例
import UIKit
import XCTest
protocol ImageEditor: CustomStringConvertible {
    func apply() -> UIImage
}
class ImageDecorator: ImageEditor {
    private var editor: ImageEditor
    required init(_ editor: ImageEditor) {
        self.editor = editor
    }
    func apply() -> UIImage {
        print(editor.description + " applies changes")
        return editor.apply()
    }
    var description: String {
        return "ImageDecorator"
    }
}
extension UIImage: ImageEditor {
    func apply() -> UIImage {
        return self
    }
    open override var description: String {
        return "Image"
    }
}
class BaseFilter: ImageDecorator {
    fileprivate var filter: CIFilter?
    init(editor: ImageEditor, filterName: String) {
        self.filter = CIFilter(name: filterName)
        super.init(editor)
    }
    required init(_ editor: ImageEditor) {
        super.init(editor)
    }
    override func apply() -> UIImage {
        let image = super.apply()
        let context = CIContext(options: nil)
        filter?.setValue(CIImage(image: image), forKey: kCIInputImageKey)
        guard let output = filter?.outputImage else { return image }
        guard let coreImage = context.createCGImage(output, from: output.extent) else {
            return image
        }
        return UIImage(cgImage: coreImage)
    }
    override var description: String {
        return "BaseFilter"
    }
}
class BlurFilter: BaseFilter {
    required init(_ editor: ImageEditor) {
        super.init(editor: editor, filterName: "CIGaussianBlur")
    }
    func update(radius: Double) {
        filter?.setValue(radius, forKey: "inputRadius")
    }
    override var description: String {
        return "BlurFilter"
    }
}
class ColorFilter: BaseFilter {
    required init(_ editor: ImageEditor) {
        super.init(editor: editor, filterName: "CIColorControls")
    }
    func update(saturation: Double) {
        filter?.setValue(saturation, forKey: "inputSaturation")
    }
    func update(brightness: Double) {
        filter?.setValue(brightness, forKey: "inputBrightness")
    }
    func update(contrast: Double) {
        filter?.setValue(contrast, forKey: "inputContrast")
    }
    override var description: String {
        return "ColorFilter"
    }
}
class Resizer: ImageDecorator {
    private var xScale: CGFloat = 0
    private var yScale: CGFloat = 0
    private var hasAlpha = false
    convenience init(_ editor: ImageEditor, xScale: CGFloat = 0, yScale: CGFloat = 0, hasAlpha: Bool = false) {
        self.init(editor)
        self.xScale = xScale
        self.yScale = yScale
        self.hasAlpha = hasAlpha
    }
    required init(_ editor: ImageEditor) {
        super.init(editor)
    }
    override func apply() -> UIImage {
        let image = super.apply()
        let size = image.size.applying(CGAffineTransform(scaleX: xScale, y: yScale))
        UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, UIScreen.main.scale)
        image.draw(in: CGRect(origin: .zero, size: size))
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return scaledImage ?? image
    }
    override var description: String {
        return "Resizer"
    }
}
class DecoratorRealWorld: XCTestCase {
    func testDecoratorRealWorld() {
        let image = loadImage()
        print("Client: set up an editors stack")
        let resizer = Resizer(image, xScale: 0.2, yScale: 0.2)
        let blurFilter = BlurFilter(resizer)
        blurFilter.update(radius: 2)
        let colorFilter = ColorFilter(blurFilter)
        colorFilter.update(contrast: 0.53)
        colorFilter.update(brightness: 0.12)
        colorFilter.update(saturation: 4)
        clientCode(editor: colorFilter)
    }
    func clientCode(editor: ImageEditor) {
        let image = editor.apply()
        /// Note. You can stop an execution in Xcode to see an image preview.
        print("Client: all changes have been applied for \(image)")
    }
}
private extension DecoratorRealWorld {
    func loadImage() -> UIImage {
        let urlString = "https:// refactoring.guru/images/content-public/logos/logo-new-3x.png"
        /// Note:
        /// Do not download images the following way in a production code.
        guard let url = URL(string: urlString) else {
            fatalError("Please enter a valid URL")
        }
        guard let data = try? Data(contentsOf: url) else {
            fatalError("Cannot load an image")
        }
        guard let image = UIImage(data: data) else {
            fatalError("Cannot create an image from data")
        }
        return image
    }
}
Output.txt: 执行结果
Client: set up an editors stack
BlurFilter applies changes
Resizer applies changes
Image applies changes
Client: all changes have been applied for Image