swift - 有没有办法根据最终都可以由特定类型表示的参数类型来简化这个“重载矩阵”?
问题描述
我们正在尝试创建一个addQueryItem
最终在内部使用字符串和可选字符串的函数。
为了在 API 中获得更大的灵活性,而不是String
用于参数类型,我们改为使用CustomStringConvertible
(String 实现的),因此我们可以使用任何可以表示为字符串的东西。
此外,因此我们可以传递String
基于 it 的枚举,我们还希望它接受自身为 a 的RawRepresentable
类型。RawValue
CustomStringConvertible
然而,由于我们现在在技术上接受每个参数的两种不同类型的值,我们最终不得不为这两种类型的每种组合创建一个“重载矩阵”——总共四个。
我的第一个想法是通过扩展来使用面向协议的编程,RawRepresentable
因此CustomStringConvertible
如果它RawValue
也是一个CustomStringConvertible
. 然后我可以直接将它传递给接受两个CustomStringConvertible
参数并消除其他三个参数的版本。但是,编译器不喜欢它,因为我正在尝试扩展协议,而不是具体类型。
// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {
var description: String {
return self.rawValue
}
}
由于无法执行上述操作,如前所述,我必须具备以下所有四个:
func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){
if let valueAsString = value.flatMap({ String(describing:$0) }) {
queryItems.append(name: String(describing:name), value: valueAsString)
}
}
func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
addQueryItem(name: name.rawValue, value: value)
}
func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {
addQueryItem(name: name, value: value?.rawValue)
}
func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
TValue.RawValue:CustomStringConvertible
{
addQueryItem(name: name.rawValue, value: value?.rawValue)
}
那么,既然看起来不可能RawRepresentable
坚持CustomStringConvertible
,还有其他方法可以解决这个“重载矩阵”问题吗?
解决方案
为了扩展我的评论,我相信您正在与 Swift 类型系统作斗争。在 Swift 中,你通常不应该尝试自动转换类型。当调用者需要一个特性时,他们应该明确地符合他们的类型。因此,对于您的枚举示例Order
,我相信它应该以这种方式实现:
首先,有一个名称和值的协议:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
现在对于可转换字符串的枚举,不必自己实现它是件好事。
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
但是,为了类型安全,您需要明确遵守协议。这样,您就不会与不应该以这种方式使用的事物发生冲突。
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
现在,也许QueryItems
真的不得不接受。好的。
class QueryItems {
func append(name: String, value: String) {}
}
但是包装它的东西可以是类型安全的。这样Order.buy
就Purchase.buy
不会发生碰撞(因为它们不能同时通过):
class QueryBuilder<Name: QueryName, Value: QueryValue> {
var queryItems = QueryItems()
func addQueryItem(name: QueryName, value: QueryValue?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
您可以使用上面的方法来降低类型安全性(使用类似的东西StringCustomConvertible
并制作QueryBuilder
非泛型,我不建议这样做,但您可以这样做)。但我仍然强烈建议您让调用者明确标记他们计划以这种方式使用的类型,方法是明确标记(而不是其他)它们符合协议。
要显示不太安全的版本会是什么样子:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
extension QueryName where Self: CustomStringConvertible {
var queryName: String { return self.description }
}
extension QueryValue where Self: CustomStringConvertible {
var queryValue: String { return self.description }
}
class QueryItems {
func append(name: String, value: String) {}
}
class QueryBuilder {
var queryItems = QueryItems()
func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
推荐阅读
- sql - 在新单元格中返回 INT 的最后一个值
- scala - HList 元素匹配类型类类型参数
- c# - Solr - 无法解析双精度数的科学记数法
- javascript - JS 中 DOM 对象的 onclick 到底是什么?
- javascript - 如何在 React Native 中检查触摸事件的目标?
- php - php - 回显时不需要转义变量?
- android - 应用数据绑定适配器来包含标签
- neo4j - 使用继承存储和检索对象的推荐方法
- prolog - PROLOG 所有组合分为三个子集
- django - Celery 4 + Django 2.0 - CELERY_BROKER_TRANSPORT_OPTIONS 被忽略