3

Swift 很了不起,但还不成熟,所以有一些编译器限制,其中包括泛型协议。由于类型安全考虑,泛型协议不能用作常规类型注释。我在 Hector Matos 的帖子中找到了解决方法。通用协议及其缺点

主要思想是使用类型擦除将泛型协议转换为泛型类,这很酷。但是在将这项技术应用于更复杂的场景时,我陷入了困境。

假设有一个抽象的Source,它产生数据,一个抽象的Procedure,它处理该数据,还有一个管道,它结合了一个源和一个数据类型匹配的过程。

protocol Source {
    associatedtype DataType
    func newData() -> DataType
}
protocol Procedure {
    associatedtype DataType
    func process(data: DataType)
}
protocol Pipeline {
    func exec() // The execution may differ
}

我希望客户端代码简单如下:

class Client {
    private let pipeline: Pipeline
    init(pipeline: Pipeline) {
        self.pipeline = pipeline
    }
    func work() {
        pipeline.exec()
    }
}

// Assume there are two implementation of Source and Procedure,
// SourceImpl and ProcedureImpl, whose DataType are identical.
// And also an implementation of Pipeline -- PipelineImpl

Client(pipeline: PipelineImpl(source: SourceImpl(), procedure: ProcedureImpl())).work()

实现 Source 和 Procedure 很简单,因为它们位于依赖关系的底部:

class SourceImpl: Source {
    func newData() -> Int { return 1 }
}

class ProcedureImpl: Procedure {
    func process(data: Int) { print(data) }
}

执行 Pipeline 时出现 PITA

// A concrete Pipeline need to store the Source and Procedure, and they're generic protocols, so a type erasure is needed
class AnySource<T>: Source {
    private let _newData: () -> T
    required init<S: Source>(_ source: S) where S.DataType == T {
        _newData = source.newData
    } 
    func newData() -> T { return _newData() }
}
class AnyProcedure<T>: Procedure {
    // Similar to above.
}

class PipelineImpl<T>: Pipeline {
    private let source: AnySource<T>
    private let procedure: AnySource<T>
    required init<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == T, P.DataType == T {
         self.source = AnySource(source)
         self.procedure = AnyProcedure(procedure)
    }
    func exec() {
         procedure.process(data: source.newData())
    }
}

呃,其实这个有效!我在开玩笑吗?不。

我对这个不满意,因为initializerofPipelineImpl非常通用,所以我希望它出现在协议中(我对这种痴迷有错吗?)。这导致了两个目的:

  1. 该协议Pipeline将是通用的。initializer包含一个 where 子句引用placeholder T,所以我需要将intoplaceholder T协议作为associated type. 然后协议变成一个通用协议,这意味着我不能直接在我的客户端代码中使用它——可能需要另一种类型擦除。

    虽然我可以承受为协议编写另一种类型擦除的麻烦Pipeline,但我不知道如何处理,initializer function因为AnyPipeline<T>该类必须实现与协议有关的初始化程序,但实际上它只是一个thunk 类,不应该实现任何初始化器本身。

  2. 保持协议Pipeline非通用。用写之initializer类的

    init<S: Source, P: Procedure>(source: S, procedure: P) 
    where S.DataType == P.DataType
    

    我可以防止协议是通用的。这意味着该协议仅声明“源和过程必须具有相同的数据类型,我不在乎它是什么”。这更有意义,但我未能实现确认此协议的具体类

    class PipelineImpl<T>: Protocol {
        private let source: AnySource<T>
        private let procedure: AnyProcedure<T>
        init<S: Source, P: Procedure>(source: S, procedure: P) 
        where S.DataType == P.DataType {
            self.source = AnySource(source) // doesn't compile, because S is nothing to do with T
            self.procedure = AnyProcedure(procedure) // doesn't compile as well
        }
        // If I add S.DataType == T, P.DataType == T condition to where clasue, 
        // the initializer won't confirm to the protocol and the compiler will complain as well
    }
    

那么,我该如何处理呢?

感谢您阅读allll本文。

4

2 回答 2

1

我认为你有点过于复杂了(除非我遗漏了什么)——你PipelineImpl似乎只是一个从 a 获取数据Source并将其传递给 a的函数的包装器Procedure

因此,它不需要是通用的,因为外部世界不需要知道被传递的数据类型——它只需要知道它可以调用exec(). 因此,这也意味着(至少现在)您不需要AnySourceorAnyProcedure类型的擦除。

这个包装器的一个简单实现是:

struct PipelineImpl : Pipeline {

    private let _exec : () -> Void

    init<S : Source, P : Procedure>(source: S, procedure: P) where S.DataType == P.DataType {
        _exec = { procedure.process(data: source.newData()) }
    }

    func exec() {
        // do pre-work here (if any)
        _exec()
        // do post-work here (if any)
    }
}

这使您可以自由地将初始化程序添加到您的Pipeline协议中,因为它不需要关心实际DataType是什么 - 只需源和过程必须具有相同的DataType

protocol Pipeline {
    init<S : Source, P : Procedure>(source: S, procedure: P) where S.DataType == P.DataType
    func exec() // The execution may differ
}
于 2016-12-29T12:30:37.047 回答
0

@Hamish 指出了一个很好的解决方案。

发布此问题后,我进行了一些测试,并找到了解决方法

class PipelineImpl<T>: Pipeline {
    required init<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == T, P.DataType == T {
        // This initializer does the real jobs.
        self.source = AnySource(source)
        self.procedure = AnyProcedure(procedure)
    }
    required convenience init<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == P.DataType {
        // This initializer confirms to the protocol and forwards the work to the initializer above
        self.init(source: source, procedure: procedure)
    }
}
于 2016-12-30T04:40:12.300 回答