Есть ли способ упростить эту "матрицу перегрузок", основанную на типах аргументов, которые в конечном итоге представляются определенным типом?
Мы пытаемся создать функцию addQueryItem
который в конечном итоге использует строку и необязательную строку внутри.
Для большей гибкости в API, чем использовать String
для типов аргументов мы вместо этого используем CustomStringConvertible
(который реализует String), поэтому мы можем использовать все, что может быть представлено в виде строки.
Кроме того, чтобы мы могли передать его String
на основе перечислений, мы также хотим принять 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
Есть ли другой способ решить эту проблему "матрицы перегрузок"?
2 ответа
Чтобы расширить мои комментарии, я считаю, что вы боретесь с системой типов Swift. В Swift вы, как правило, не должны пытаться автоматически конвертировать типы. Вызывающие должны явно соответствовать своим типам, когда им нужна особенность. Так что к вашему примеру Order
enum, я считаю, что это должно быть реализовано следующим образом:
Во-первых, есть протокол для имен и значений:
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
}
Нет, вы не можете согласовать протокол с другим протоколом через расширение. Язык не поддерживает это.