持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第14天,点击查看活动详情
- 本文主要介绍iOS设计模式中的
装饰模式,该模式是一种行为拓展的方式,装饰模式相比生成子类更为灵活。
1. 什么是装饰模式
通常,我们拍照的时候,不会想去如何装饰它。拍照只是我们捕捉那个瞬间,但是后面我们要进行修图,比如说添加模糊效果,或者是改变大小,改变颜色等,虽然对它进行了装饰,但是照片底片还是那个照片,没有受到影响,我们只是在p图的照片中添加了东西,并没有改变它。
穿衣服是使用装饰的一个例子。 觉得冷时, 你可以穿一件毛衣。 如果穿毛衣还觉得冷, 你可以再套上一件夹克。 如果遇到下雨, 你还可以再穿一件雨衣。 所有这些衣物都 “扩展” 了你的基本行为, 但它们并不是你的一部分, 如果你不再需要某件衣物, 可以方便地随时脱掉。
这种装饰的行为可以看成俄罗斯套娃,不断队对象进行封装装饰,但是并没有改变实际上的对象。
装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
在面向对象软件中,我么借用了类似的思想,向对象添加“东西”(行为),而不破坏原有风格,因此增强了的对象是同一个类的加强版。任何“增强”均可动态添加和删除。我们把这种设计模式叫做“装饰”,装饰对象可以附加到另一个装饰对象,也可以附加到原始对象,对其功能进行拓展,同时保留原始行为不受影响。
装饰模式:动态地给一个对象添加一些额外的职责。就拓展功能来说,装饰模式相比成为子类更加灵活。
2. 什么时候使用装饰模式
下面的情况,可以考虑使用这一模式
- 想要在
不影响其对象的情况下,以动态,透明的方式给单个对象添加职责。 - 想要
拓展一个类的行为,却做不到。类定义可能被隐藏,无法进行子类化,比如你要拓展一些三方的对象的行为;或者,对类的每个行为的拓展,为支持每种功能组合,将产生大量的子类。 - 对类的职责的拓展是
可选的。
3.代码展示
我们通过对UIImage创建图像滤镜。装饰模式是向对象添加新的行为与职责的一种方式,它不改变现有行为与接口。比如说有个图像对象,它只包含让客户端管理其属性的接口,仅此而起。我们想向它添加点花里胡哨的东西,比如变换滤镜,但是我们又不想修改现有的接口,我们能做的是,再定义一个跟这个图像对象一样的类,但它包含对另一个图像对象的引用,增加这个图像对象的行为。
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
}
}
输出结果
Client: set up an editors stack
BlurFilter applies changes
Resizer applies changes
Image applies changes
Client: all changes have been applied for Image
这里是通过真正的子类实现装饰,我们实际开发也可以通过拓展分类category的形式进行装饰,通过对UIImage分类的行为进行拓展,可以像单独UIImage类那样使用。
4. 总结
装饰模式是对一些复杂的行为进行拓展,真正子类方式的实现使用一种较为结构化的方式连接各种装饰器。分类category的方式更为简单和轻量,适用于现有类只需要少量装饰器的应用程序。