我正在尝试理解如何重构代码和使用Swift泛型。我能够识别出几个概念上相似类型的通用实现,但是我遇到了困难,因为代码构建的层不是通用的。
以下是我对一些我认为想要利用的概念的理解。
协议
用于声明接口并具有该接口的许多“具体”实现。这允许您在运行时交换实现。
泛型
当实现相同或相似但类型不同时有用。似乎规范示例是交换两个值或map
。
我有一个客户端类(APIClient
)负责创建URL请求。客户端只知道主要数据类型(整数,字符串,数组,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
}
好的,对Downloader
类的T
类来说,这似乎已经成熟了ObjectNotation
。好吧,所以有一些直接的担忧:
Mappable
中搜索特殊键。让我们依次处理这些:
使用特定的映射器而不是下载器,让我们使用类型为protocol MapperType {
typealias ObjectType
func object(objectNotation: ObjectNotation) throws -> ObjectType
}
的东西。可映射协议需要对许多不同类型都是通用的,因此我们可以通过使用关联类型来实现此目的。
Mappable
protocol Mappable {
static func objectNotationRoot() -> String
}
并让我的模型对象符合。
Location
有些下载程序会使用Season
和Location
,而另一个下载只需Season
。通用下载程序可以在不需要时同时删除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
}
的类型并在此基础上作出决定是非常严重的。类型信息在下载程序中,客户端不会使用或使用此信息。我是否应该认为我需要在这些层之间楔入某些东西?
这是我半生不熟的通用下载器的样子:
{{1}}
答案 0 :(得分:2)
你可以这样做,但你真的不需要所有的协议。单个结构很多,还有一些功能和一个奇怪的技巧。&#34;
&#34;一个奇怪的伎俩&#34;是方法是真正的curried函数,它们将对象作为第一个参数。所以这个:
struct Foo {
func bar() {}
}
有一个函数Foo.bar(self: Foo)
,它返回一个函数() -> Void
。这可能还不是很完美,但我们稍后会使用它。
首先,我们的Downloader
(按照目前的设计)需要四件事:客户端,获取内容的方法,查找树顶部的方法,以及将找到的内容映射到对象的方法。所以我们做到了:
struct Downloader<ObjectType> {
let client: APIClient
let fetcher: Fetcher
let rootKey: String
let mapper: (ObjectNotation) throws -> ObjectType
}
为方便起见,我创建了以下类型,因为它非常庞大:
typealias Fetcher = (APIClient) -> (locationIdentifier: String, seasonIdentifier: Int, successHandler: ((ObjectNotation) -> Void)?, failureHandler: ((NSError) -> Void)?) -> NSURL
那种类型的功能是什么?好吧,APIClient.getFruitsForRegionIdentifier
有那种类型(我从中复制了它)。因此,我们可以将其作为提取者传递。
对于那些download
非常简单:
func download(location: Location, season: Season, successHandler: (([ ObjectType ]) -> Void)?, failureHandler: ((NSError) -> Void)?) {
fetcher(client)(
locationIdentifier: location.identifier,
seasonIdentifier: season.identifier,
successHandler: self.successWrapper(successHandler: successHandler, failureHandler: failureHandler),
failureHandler: failureHandler)
}
为了让它更容易阅读,我拖出了成功阻止。它需要一个successHandler和一个failureHandler并返回一个新的成功处理程序。
func successWrapper(successHandler successHandler: (([ ObjectType ]) -> Void)?, failureHandler: ((NSError) -> Void)?)
-> ((objectNotation: ObjectNotation) -> Void) {
return { objectNotation in
if let instances = objectNotation[self.rootKey] as? [ ObjectNotation ] {
do {
let objects = try instances.map(self.mapper)
successHandler?(objects)
}
catch let error as NSError {
failureHandler?(error)
}
}
else {
failureHandler?(NSError(domain: "com.fruit.downloader", code: 1000, userInfo: nil))
}
}
}
好的,那里有很多东西。我们如何使用它?好吧,我们可以创建一个像这样的Fruit下载器:
let fruitDownloader = Downloader<Fruit>(
client: APIClient(baseURL: NSURL()),
fetcher: APIClient.getFruitsForRegionIdentifier, // <- That curried function we talked about
rootKey: "fruit_data",
mapper: simpleMapper("fruit")
)
其中simppleMapper
只是一个帮手:
extension String: ErrorType {} // For sloppy errors
func simpleMapper<T>(key: String) -> (ObjectNotation) throws -> T {
return { objectNotation in
guard let value = objectNotation[key] as? T else {
throw "Could not!" // FIXME
}
return value
}
}
我们已经完成了很多工作。这里的关键是高阶函数。接受函数并返回新函数的函数。
使用高阶函数,您可以简化更多这方面的工作。看APIClient
,我们并不清楚为什么我们真的需要所有这些不同的方法。它看起来像唯一不同的是URL模板。也许我们只需要一个函数并将其附加到Downloader。
您可能会注意到我们甚至不需要Downloader
结构。它只包含一个方法,甚至没有任何状态(这就是为什么它当然不应该是一个类)。我们可以将它构建为一个返回下载功能的函数(如simpleMapper
那样)。结构有一些好处(例如,你可以在调试器中打印出它们的属性),所以我并不是说你应该把它们全部删掉,但要记住你有选择是件好事。
(我注意到Wildlife有一个不同的功能签名,缺少Season
。不清楚是否有意。如果是,那么你可能需要另外一层功能抛弃了过去的季节。但它不应该复杂。只要想想那些能够恢复功能的函数。)