理解 Associated Value Enums

为什么 Swift 中会有 associated value enums?我的意思是,为什么 Swift core team 会设计 associated value enum?

为了理解这点,首先,让我们看看下面这个 C 语言的例子:

struct Printer;

struct Scanner;

enum DeviceType {
    DeviceTypePrinter = 0,
    DeviceTypeScanner = 1,
};

struct Device {
    union {
        struct Printer * printer;
        struct Scanner * scanner;
    } pointer;
    enum DeviceType device_type;
};
复制代码

C struct Device 代表一个设备。其成员 pointer 是一个有 printerscanner 两个成员的匿名联结体,其中 printerscanner 可以帮助我们特化 pointer 这个指针的类型;其成员 device_type 表示这个 struct 储存着哪种类型的设备指针。

然后我们可以用下列 C 代码来获取储存在这个结构体种的指针:

struct Device device;

Printer * printerPtr = NULL;
Scanner * scannerPtr = NULL;
    
switch (device.device_type) {
    case DeviceTypePrinter:
        printerPtr = device.pointer.printer;
        break;
    case DeviceTypeScanner:
        scannerPtr = device.pointer.scanner;
        break;
}
复制代码

在我描述了上面这个 C struct 的意图之后,你应该可以快速地反应过来,你可以将其表述为下列 Swift 代码:

enum Device {
    printer(Printer)
    scanner(Scanner)
}
复制代码

然后我们可以用下列 Swift 代码来获取这个 enum 中储存的值:

let device: Device = .printer(Printer());

var printer: Printer!
var scanner: Scanner!

switch device {
case let .printer(p):
    printer = p
case let .scanner(s):
    scanner = s
}
复制代码

是的。Associated value enum 的设计意图就是简化类似之前 Device 这样的 C struct 的数据结构的表述和使用的。像 Device 这样的 C struct 的数据结构在操作系统的代码仓库和其他用 C 写的基础设施中无所不在。而像这样的数据结构也是另一种套用 多态 的途径。

我说多态。是的,多态。

多态不仅仅是一个存在于物件导向(陆译:面向对象)世界中的概念。一旦一个实体拥有多种可能的形态,那么我们就可以说这个实体在套用多态。

由于 associated value enum 为简化 plain-old data 的多态表述而设计,我们也可以将 associated value enum 以其他形式的多态来表述——比如说物件导向(陆译:面向对象)。

在物件导向(陆译:面向对象)世界中表述 Associated Value Enum

为了在物件导向(陆译:面向对象)世界中表述 Associated Value Enum,我们能第一个想起的模式是抽象工厂。

是的。实际上,我总是用抽象工厂模式在物件导向(陆译:面向对象)世界中描述 associated value enum。

对于之前 Swift 的例子中的 Device struct 而言,我们可以写出等效的物件导向(陆译:面向对象)Swift 代码:

enum DeviceType {
    printer
    scanner
}

class DeviceObject {
    var type: DeviceType { preconditionFailure("Abstract") }

    var deviceValue: Device { preconditionFailure("Abstract") } 

    static func make(_ device: Device) -> DeviceObject {
        switch device {
            case let .printer(printer): return makePrinter(printer)
            case let .scanner(scanner): return makeScanner(scanner)
        }
    }

    static func makePrinter(_ printer: Printer) -> DeviceObject {
        return PrinterObject(printer: printer)
    }

    static func makeScanner(_ scanner: Scanner) -> DeviceObject {
        return ScannerObject(scanner: scanner)
    }
}

class PrinterObject: DeviceObject {
    override var type: DeviceType { return .printer }

    override var deviceValue: Device { return .printer(printer) }

    let printer: Printer

    init(printer: Printer) {
        self.printer = printer
    }
}

class ScannerObject: DeviceObject {
    override var type: DeviceType { return .scanner }

    override var deviceValue: Device { return .scanner(scanner) }

    let scanner: Scanner

    init(scanner: Scanner) {
        self.scanner = scanner
    }
}
复制代码

于是我们已经以物件导向(陆译:面向对象)的途径表述除了一个 associated value enum。

遵从 Codable 协议

现在有了 DeviceObject 这个 class,遵从 Codable 协议已经变得非常简单了。首先我们要让 DeviceObject 遵从 Codeable 协议。

class DeviceObject: Codable {

    ...

    required init(from decoder: Decoder) throws {
        
    }
    
    func encode(to encoder: Encoder) throws {
        
    }
}

class PrinterObject {

    ...

    private enum _CodingKeys: CodingKey {
        case printer
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: _CodingKeys.self)
        printer = try container.decode(Printer.self, forKey: .printer)
        super.init(from: decoder)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: _CodingKeys.self)
        try encoder.encode(printer, forKey: .printer)
        super.encode(to: encoder)
    }
}

class ScannerObject {

    ...

    private enum _CodingKeys: CodingKey {
        case scanner
    }
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: _CodingKeys.self)
        scanner = try container.decode(Scanner.self, forKey: .scanner)
        super.init(from: decoder)
    }
    
    override func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: _CodingKeys.self)
        try encoder.encode(scanner, forKey: .scanner)
        super.encode(to: encoder)
    }
}
复制代码

然后现在我们可以让 Device 服从 Codable 协议了。

enum Device: Codable {

    ...

    required init(from decoder: Decoder) throws {
        let deviceObject = try DeviceObject(from: decoder)
        self = deviceObject.deviceValue
    }
    
    func encode(to encoder: Encoder) throws {
        try DeviceObject.make(device: self).encode(to: encoder)
    }
}
复制代码

最后,收工。


  • 因为我觉得 Object-Oriented 这个术语台湾翻译得比较准确,所以使用了台译。

GankRobot转载声明

原文出处:掘金iOS

原文作者:WeZZard

原文地址:https://juejin.im/post/5d7a578de51d4561db5e3ae5