Decorator em Swift
O Decorator é um padrão estrutural que permite adicionar novos comportamentos aos objetos dinamicamente, colocando-os dentro de objetos wrapper especiais.
Usando decoradores, você pode agrupar objetos inúmeras vezes, pois os objetos de destino e os decoradores seguem a mesma interface. O objeto resultante terá um comportamento de empilhamento de todos os wrappers.
Complexidade:
Popularidade:
Exemplos de uso: O Decorator é bastante padrão no código Swift, especialmente nos códigos relacionados a fluxos.
Identificação: O Decorator pode ser reconhecido por métodos de criação ou construtores que aceitam objetos da mesma classe ou interface que uma classe atual.
Exemplo conceitual
Este exemplo ilustra a estrutura do padrão de projeto Decorator. Ele se concentra em responder a estas perguntas:
- De quais classes ele consiste?
- Quais papéis essas classes desempenham?
- De que maneira os elementos do padrão estão relacionados?
Depois de aprender sobre a estrutura do padrão, será mais fácil entender o exemplo a seguir, com base em um caso de uso Swift do mundo real.
Example.swift: Exemplo conceitual
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: Resultados da execução
Client: I've got a simple component Result: ConcreteComponent Client: Now I've got a decorated component Result: ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent)) Exemplo do mundo real
Example.swift: Exemplo do mundo real
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: Resultados da execução
Client: set up an editors stack BlurFilter applies changes Resizer applies changes Image applies changes Client: all changes have been applied for Image