假设存在一个抽象的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时,可能会出现让人头疼的问题。
// 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())
}
}
嗨,实际上这个是可行的!我在开玩笑吗?没有。
我对这个不太满意,因为PipelineImpl
的initializer
非常通用,所以我希望它在协议中(这种追求有问题吗?)。这导致了两个结果:
The protocol
Pipeline
will be generic. Theinitializer
contains a where clause which refers toplaceholder T
, so I need to move theplaceholder T
into protocol as anassociated type
. Then the protocol turns into a generic one, which means I can't use it directly in my client code -- may need another type erasure.Although I can bear the troublesome of writing another type erasure for the
Pipeline
protocol, I don't know how to deal with theinitializer function
because theAnyPipeline<T>
class must implement the initializer regarding to the protocol but it's only a thunk class actually, which shouldn't implement any initializer itself.Keep the protocol
Pipeline
non-generic. With writing theinitializer
likeinit<S: Source, P: Procedure>(source: S, procedure: P) where S.DataType == P.DataType
I can prevent the protocol being generic. This means the protocol only states that "Source and Procedure must have same DataType and I don't care what it is". This makes more sense but I failed to implement a concrete class confirming this protocol
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 }