/ 设计模式 / 装饰模式 / Swift Swift 装饰模式讲解和代码示例 装饰是一种结构设计模式, 允许你通过将对象放入特殊封装对象中来为原对象增加新的行为。 由于目标对象和装饰器遵循同一接口, 因此你可用装饰来对对象进行无限次的封装。 结果对象将获得所有封装器叠加而来的行为。 进一步了解装饰模式 导航 简介 概念示例 Example Output 真实世界示例 Example Output 复杂度: 流行度: 使用示例: 装饰在 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 概念示例 真实世界示例