简体   繁体   English

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

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

I want to implement a system that allows me to generically process a type by registering processors for different key paths.我想实现一个系统,允许我通过为不同的键路径注册处理器来一般地处理一个类型。

One trait of the system should be composition , so every processor should extend one common generic protocol Processor .系统的一个特征应该是composition ,因此每个处理器都应该扩展一个通用的通用协议Processor

Example of usage:使用示例:

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

Instead of trying to erase property types, your can use inheritance of "processors", where subclasses are generic over root object and property types and have property key path.您可以使用 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)
        }
    }
}

I've created a playground showing how this can be solved with function composition and then using what we've learned there to recreate your example.我创建了一个游乐场,展示了如何使用 function 组合解决这个问题,然后使用我们在那里学到的知识重新创建您的示例。

Function composition allows you to create new functions by chaining together existing ones, as long as the types match up. 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)) }
}

The Processor can be translated to a function that takes an object O , transforms it in some way, and returns a new object. Creating the function could be done like this: 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)

Now we can compose those two functions.现在我们可以组合这两个函数了。 The resulting functions still keeps the signature `(Person) -> Person).结果函数仍然保持签名`(Person) -> Person)。 We can compose as many functions as we like, creating a processing pipeline.我们可以组合任意多的函数,创建一个处理管道。

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

Moving on to recreating your example.继续重新创建您的示例。 As this answer mentions, the type is generic over the root object. This is just like in the function composition example, and it allows us to group all the processors in an array for example.正如这个答案提到的,该类型在根 object 上是通用的。这就像在 function 组合示例中一样,它允许我们将所有处理器分组到一个数组中。

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

When erasing an object, it's important to keep a reference to the original, so that we can use it to implement the required functionality.在擦除 object 时,重要的是要保留对原始数据的引用,以便我们可以使用它来实现所需的功能。 In this case, we're keeping a reference to its process(:) method.在这种情况下,我们保留对其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)
    }
}

Here's two types implementing the Processor protocol.这是实现Processor协议的两种类型。 Notice the first one has two placeholder types.注意第一个有两种占位符类型。 The second placeholder will get erased.第二个占位符将被删除。

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
    }
}

Finally, the ObjectProcessor that uses object composition.最后,使用object组成的ObjectProcessor Notice the array holds object of the same type.请注意,该数组包含相同类型的 object。 An instance of this struct will only be able to process Person s for example.例如,此结构的实例将只能处理Person What each child processor does is hidden away in the implementation and the fact it may run on different kinds of data does not affect the ObjectProcessor .每个子处理器所做的事情都隐藏在实现中,并且它可能在不同类型的数据上运行这一事实不会影响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
    }
}

And here it is in action.它正在发挥作用。 Notice I add two processors for the same key.请注意,我为同一个密钥添加了两个处理器。

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)

Thank you for your answers.谢谢您的回答。 I've used your inputs and come up with the following solution:我已经使用了您的输入并提出了以下解决方案:

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.

相关问题 如何快速使用通用类型处理不同类型? - How can I handle different types using generic type in swift? 我如何实现一个通用结构来管理 Swift 中 UserDefaults 的键值对? - How can I implement a generic struct that manages key-value pairs for UserDefaults in Swift? 如何使用通用约束类型属性实现Swift协议? - How do I implement a Swift protocol with a generic constrained type property? 如何实现符合具有相关类型协议的泛型类? - How can I implement a generic class that conforms to a protocol with associated type? 如何键入检查通用然后在swift中转换为它? - How can I type check a generic then cast to it in swift? 如何将 nil 传递给 Swift 上的泛型类型参数? - How can I pass nil to generic type parameter on Swift? 如何实现使用NSKeyedArchiver在Swift中保存不同词典的通用方法? - How to implement a generic way of saving different Dictionaries in Swift using NSKeyedArchiver? 如何使通用协议在 Swift 中不通用? 所以我可以将它用作参数类型 - How to make generic protocol not generic in Swift? So I can use it as parameter type 如何在 Swift 中实现通用约束? - How to implement a generic constraint in Swift? 如何确定Swift中函数的泛型参数类型是什么? - How can I determine what is the type of generic parameter type in function in Swift?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM