【发布时间】:2016-01-13 21:43:59
【问题描述】:
摘要
我正在尝试了解如何重构代码和使用 Swift 泛型。我能够跨几个概念上相似的类型确定一个通用实现,但我遇到了困难,因为代码构建的层不是通用的。
背景
这是我对一些我想利用的概念的理解。
协议
对于声明一个接口并拥有该接口的许多“具体”实现很有用。这允许您在运行时换出实现。
泛型
当实现相同或相似但类型不同时很有用。似乎典型的例子是交换两个值或map。
问题
我有一个负责创建 URL 请求的客户端类 (APIClient)。客户端只知道主要数据类型(整数、字符串、数组、JSON 等)。在客户端类之上有几个抽象领域对象的类(FruitDownloader、TrafficDownloader 和WildlifeDownloader)。此类知道如何从域类型中解压缩主要数据类型并调用正确的客户端方法。该类使用映射器类(FruitMapper、TrafficMapper 和 WildlifeMapper)从 JSON 构造域对象。
...Dowloader 类的实现非常相似:
class FruitDownloader {
init(client: APIClient, mapper: FruitMapper)
{
self.client = client
self.mapper = mapper
}
func downloadFruit(location: Location, season: Season, successHandler: (([ Fruit ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client.getFruitsForRegionIdentifier(location.identifier,
seasonIdentifier: season.identifier,
successHandler: { objectNotation in
if let instances = objectNotation["fruit_data"] as? [ ObjectNotation ] {
do {
let fruits = try instances.map(self.mapper.fruit)
successHandler?(fruits)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.fruit.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: FruitMapper
}
class TrafficDownloader {
init(client: APIClient, mapper: TrafficMapper)
{
self.client = client
self.mapper = mapper
}
func downloadTraffic(location: Location, season: Season, successHandler: (([ Traffic ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client.getHistoricalTrafficReportForRegionIdentifier(location.identifier,
seasonIdentifier: season.identifier,
successHandler: { objectNotation in
if let instances = objectNotation["data"] as? [ ObjectNotation ] {
do {
let trafficReport = try instances.map(self.mapper.traffic)
successHandler?(trafficReport)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.traffic.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: TrafficMapper
}
class WildlifeDownloader {
init(client: APIClient, mapper: WildlifeMapper)
{
self.client = client
self.mapper = mapper
}
func downloadWildlife(location: Location, successHandler: (([ Wildlife ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client.getWildlifeForRegionIdentifier(location.identifier,
successHandler: { objectNotation in
if let instances = objectNotation["content"] as? [ ObjectNotation ] {
do {
let wildlife = try instances.map(self.mapper.wildlife)
successHandler?(wildlife)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.wildlife.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: WildlifeMapper
}
好的,这对于泛型于T 的Downloader 类来说已经成熟了。好的,现在有一些紧迫的问题:
- 每个下载器类都使用一个特定于域的映射器。
- 每个下载者都必须通过
ObjectNotation摸索一个特殊的密钥。 - 客户端方法的参数略有不同。
- 每个下载器调用一个特定的客户端方法。
让我们依次处理:
1:具有关联类型的协议
让我们让它使用Mappable 类型的东西,而不是使用特定映射器的下载器。可映射协议需要对许多不同类型是通用的,因此我们可以通过使用关联类型来实现这一点。
protocol MapperType {
typealias ObjectType
func object(objectNotation: ObjectNotation) throws -> ObjectType
}
2:对象符号操作
嗯,我不太确定有什么好的方法来处理这个问题。但是,我可以声明另一个协议 Mappable 并让我的模型对象符合。
protocol Mappable {
static func objectNotationRoot() -> String
}
3:下载器参数
一些下载者使用Location 和Season,另一些下载者只使用Location。通用下载器可以同时获取并在不需要 Season 实例时丢弃它,但这感觉就像是泄漏抽象。
4:根据类型调用不同的方法
好的,现在我真的被困住了。我如何调用客户端?检查T 的类型并以此为基础做出决定感觉真的很恶心。类型信息在下载器中,客户端不获取或使用此信息。我是否应该认为我需要在这些层之间插入一些东西?
这是我半生不熟的通用下载器的样子:
class Downloader<T: Mappable, U: MapperType where U.ObjectType == T> {
init(client: APIClient, mapper: U)
{
self.client = client
self.mapper = mapper
}
func download(location: Location, season: Season, successHandler: (([ T ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
{
client._________(location.identifier,
seasonIdentifier: season.identifier,
successHandler: { objectNotation in
if let instances = objectNotation[T.objectNotationRoot()] as? [ ObjectNotation ] {
do {
let objects = try instances.map(self.mapper.object)
successHandler?(objects)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.fruit.downloader", code: 1000, userInfo: nil))
}
},
failureHandler: failureHandler)
}
private let client: APIClient
private let mapper: U
}
【问题讨论】: