如何在任意抽象层中使用泛型?

时间:2016-01-13 21:44:00

标签: swift generics protocols

Sample Project Here

抽象

我正在尝试理解如何重构代码和使用Swift泛型。我能够识别出几个概念上相似类型的通用实现,但是我遇到了困难,因为代码构建的层不是通用的。

背景

以下是我对一些我认为想要利用的概念的理解。

协议

用于声明接口并具有该接口的许多“具体”实现。这允许您在运行时交换实现。

泛型

当实现相同或相似但类型不同时有用。似乎规范示例是交换两个值或map

问题

我有一个客户端类(APIClient)负责创建URL请求。客户端只知道主要数据类型(整数,字符串,数组,JSON等)。客户端类上方有几个抽象域对象的类(FruitDownloaderTrafficDownloaderWildlifeDownloader)。此类知道如何从域类型解压缩主数据类型并调用正确的客户端方法。此类使用映射器类(FruitMapperTrafficMapperWildlifeMapper)从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。好吧,所以有一些直接的担忧:

  1. 每个下载程序类都使用特定于域的映射器。
  2. 每个下载程序都必须在Mappable中搜索特殊键。
  3. 客户端方法的参数稍有不同。
  4. 每个下载程序都会调用特定的客户端方法。
  5. 让我们依次处理这些:

    1:具有关联类型的协议

    使用特定的映射器而不是下载器,让我们使用类型为protocol MapperType { typealias ObjectType func object(objectNotation: ObjectNotation) throws -> ObjectType } 的东西。可映射协议需要对许多不同类型都是通用的,因此我们可以通过使用关联类型来实现此目的。

    Mappable

    2:对象表示法操作

    嗯,我真的不确定处理这个问题的好方法。但是,我可以声明另一个协议protocol Mappable { static func objectNotationRoot() -> String } 并让我的模型对象符合。

    Location

    3:下载器参数

    有些下载程序会使用SeasonLocation,而另一个下载只需Season。通用下载程序可以在不需要时同时删除T实例,但这感觉就像漏洞一样。

    4:根据类型

    调用不同的方法

    好的,现在我真的被卡住了。我如何打电话给客户?检查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}}

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。不清楚是否有意。如果是,那么你可能需要另外一层功能抛弃了过去的季节。但它不应该复杂。只要想想那些能够恢复功能的函数。)

All the code together as a gist.