繁体   English   中英

在 Swift 中,如何通过为不同的键路径注册处理器来实现处理一个类型的通用系统?

[英]In Swift, how can I implement a generic system that processes a type by registering processors for different key paths?

我想实现一个系统,允许我通过为不同的键路径注册处理器来一般地处理一个类型。

系统的一个特征应该是composition ,因此每个处理器都应该扩展一个通用的通用协议Processor

使用示例:

struct Language {
    var name = "Swift"
    var version = 5.3
}

var processor = TypeProcessor<Language>()
processor.add(procesor: VersionProcessor(), keypath: \.version)
processor.add(procesor: NameProcessor(), keypath: \.name)

var input = Language()
processor.process(value: input)

// Languge version:     5.3
// Languge name:        Swift

您可以使用 inheritance 的“处理器”,而不是尝试擦除属性类型,其中子类在根 object 和属性类型上是通用的,并且具有属性键路径。

protocol Processor {
    associatedtype T
    func process(value: T)
}

class AnyProcessor<Value>: Processor {
    func process(value: Value) {
    }
}

final class KeyPathProcessor<Root, P: Processor>: AnyProcessor<Root> {
    typealias Value = P.T

    let keyPath: KeyPath<Root, Value>
    let processor: P

    init(keyPath: KeyPath<Root, Value>, processor: P) {
        self.keyPath = keyPath
        self.processor = processor
    }

    override func process(value: Root) {
        processor.process(value: value[keyPath: keyPath])
    }
}

struct ObjectProcessor<T>: Processor {
    var processors = [AnyKeyPath: AnyProcessor<T>]()

    mutating func add<P: Processor, V>(processor: P, keypath: KeyPath<T, V>) where P.T == V {
        self.processors[keypath] = KeyPathProcessor(keyPath: keypath, processor: processor)
    }

    func process(value: T) {
        for (_, processor) in processors {
            processor.process(value: value)
        }
    }
}

我创建了一个游乐场,展示了如何使用 function 组合解决这个问题,然后使用我们在那里学到的知识重新创建您的示例。

Function 组合允许您通过将现有函数链接在一起来创建新函数,只要类型匹配即可。

precedencegroup CompositionPrecedence {
    associativity: left
}

infix operator >>>: CompositionPrecedence

func >>> <T, U, V>(lhs: @escaping (T) -> U, rhs: @escaping (U) -> V) -> (T) -> V {
    return { rhs(lhs($0)) }
}

Processor可以转换为 function,它接受 object O ,以某种方式对其进行转换,并返回一个新的 object。创建 function 可以像这样完成:

func process<O, K>(keyPath: WritableKeyPath<O, K>, _ f: @escaping (K) -> K) -> (O) -> O {
    return { object in
        var writable = object
        writable[keyPath: keyPath] = f(object[keyPath: keyPath])
        return writable
    }
}

let reverseName = process(keyPath: \Person.name, reverse)
let halfAge = process(keyPath: \Person.age, half)

现在我们可以组合这两个函数了。 结果函数仍然保持签名`(Person) -> Person)。 我们可以组合任意多的函数,创建一个处理管道。

let benjaminButton = reverseName >>> halfAge
let youngBradPitt = benjaminButton(bradPitt)

继续重新创建您的示例。 正如这个答案提到的,该类型在根 object 上是通用的。这就像在 function 组合示例中一样,它允许我们将所有处理器分组到一个数组中。

protocol Processor {
    associatedtype T
    func process(object: T) -> T
}

在擦除 object 时,重要的是要保留对原始数据的引用,以便我们可以使用它来实现所需的功能。 在这种情况下,我们保留对其process(:)方法的引用。

extension Processor {
    func erased()-> AnyProcessor<T> {
        AnyProcessor(base: self)
    }
}

struct AnyProcessor<T>: Processor {
    private var _process: (T) -> T
    
    init<Base: Processor>(base: Base) where Base.T == T {
        _process = base.process
    }
    
    func process(object: T) -> T {
        _process(object)
    }
}

这是实现Processor协议的两种类型。 注意第一个有两种占位符类型。 第二个占位符将被删除。

struct AgeMultiplier<T, K: Numeric>: Processor {
    let multiplier: K
    let keyPath: WritableKeyPath<T, K>
    
    private func f(_ value: K) -> K {
        value * multiplier
    }
    
    func process(object: T) -> T {
        var writable = object
        writable[keyPath: keyPath] = f(object[keyPath: keyPath])
        return writable
    }
}

struct NameUppercaser<T>: Processor {
    let keyPath: WritableKeyPath<T, String>
    
    private func f(_ value: String) -> String {
        value.uppercased()
    }
    
    func process(object: T) -> T {
        var writable = object
        writable[keyPath: keyPath] = f(object[keyPath: keyPath])
        return writable
    }
}

最后,使用object组成的ObjectProcessor 请注意,该数组包含相同类型的 object。 例如,此结构的实例将只能处理Person 每个子处理器所做的事情都隐藏在实现中,并且它可能在不同类型的数据上运行这一事实不会影响ObjectProcessor

struct ObjectProcessor<T>: Processor {
    private var processers = [AnyProcessor<T>]()
    
    mutating func add(processor: AnyProcessor<T>) {
        processers.append(processor)
    }
    
    func process(object: T) -> T {
        var object = object
        
        for processor in processers {
            object = processor.process(object: object)
        }
        
        return object
    }
}

它正在发挥作用。 请注意,我为同一个密钥添加了两个处理器。

var holyGrail = ObjectProcessor<Person>()
holyGrail.add(processor: NameUppercaser(keyPath: \Person.name).erased())
holyGrail.add(processor: AgeMultiplier(multiplier: 2, keyPath: \Person.age).erased())
holyGrail.add(processor: AgeMultiplier(multiplier: 3, keyPath: \Person.age).erased())

let bradPitt = Person(name: "Brad Pitt", age: 57)
let immortalBradPitt = holyGrail.process(object: bradPitt)

谢谢您的回答。 我已经使用了您的输入并提出了以下解决方案:

protocol Processor {
    
    associatedtype T
    
    func process(value: T)
}

extension Processor {
    
    func erase()-> AnyProcessor<T> {
        return AnyProcessor<T>(self)
    }
}


struct AnyProcessor<T>: Processor {
    
    let process: (T) -> Void
    
    init<P: Processor>(_ processor: P) where P.T == T {
        self.process = processor.process
    }
    
    func process(value: T) {
        self.process(value)
    }
}

struct KeyPathProcessor<T, V>: Processor {
    
    private var keyPath: KeyPath<T,V>
    private var processor: AnyProcessor<V>
    
    init<P: Processor>(_ processor: P, for keyPath: KeyPath<T, V>) where P.T == V {
        self.processor = processor.erase()
        self.keyPath = keyPath
    }
    
    func process (value: T) {
        let input = value[keyPath: keyPath]
        processor.process(value: input)
    }
}

struct VersionProcessor: Processor {
    
    func process(value: Double) {
        print("Languge version:     \(value)")
    }
}

struct NameProcessor: Processor {
    
    func process(value: String) {
        print("Languge name:        \(value)")
    }
}

struct TypeProcessor<T>: Processor {
    
    var processors = [AnyProcessor<T>]()
    
    mutating func add<P: Processor, V>(procesor: P, keypath: KeyPath<T, V>) where P.T == V {
        let p = KeyPathProcessor(procesor, for: keypath).erase()
        self.processors.append(p)
    }
    
    func process(value: T) {
        for processor in processors {
            processor.process(value: value)
        }
    }
}

struct Language {
    var name = "Swift"
    var version = 5.3
}

var processor = TypeProcessor<Language>()
processor.add(procesor: VersionProcessor(), keypath: \.version)
processor.add(procesor: NameProcessor(), keypath: \.name)

var input = Language()
processor.process(value: input)

// Languge version:     5.3
// Languge name:        Swift

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM