我是Java开发人员,我试图用Swift编写与Java代码相同的解决方案。
是否可以在Swift上执行此操作?
Java示例:
public interface Converter<S,T> {
T convert(S in)
}
public class CarConverterToDTO implements Converter<Car, CarDTO> {
@Override
public CarDTO convert(Car in) {
.....
}
}
示例Swift:
protocol Converter {
func convert<IN, OUT>(in: IN) -> OUT
}
实施情况如何?
谢谢!
答案 0 :(得分:2)
看似简单的问题实际上是相当大且令人不快的冰山一角……
首先,我将为您提供解决问题的真正方法:
class Converter<Input, Output> {
func convert(_ input: Input) -> Output {
fatalError("subclass responsibility")
}
}
struct Car { }
struct CarDTO { }
class DTOCarConverter: Converter<Car, CarDTO> {
override func convert(_ input: Car) -> CarDTO {
return CarDTO()
}
}
上面,我已经将您的Java Converter
接口转换为Swift class
而不是Swift protocol
。那可能就是你想要的。
现在我将解释原因。
从Java到Swift的程序员可能会认为Swift协议等效于Java接口。所以你可以这样写:
protocol Converter {
associatedtype Input
associatedtype Output
func convert(_ input: Input) -> Output
}
struct Car { }
struct CarDTO { }
class /* or struct */ DTOCarConverter: Converter {
func convert(_ input: Car) -> CarDTO {
return CarDTO()
}
}
好的,现在您可以创建一个转换器并进行转换:
let converter = DTOCarConverter()
let car = Car()
let dto = converter.convert(car)
但是,当您要编写一个以Converter
作为参数的函数时,就会遇到问题:
func useConverter(_ converter: Converter) { }
// ^
// error: protocol 'Converter' can only be used as a generic constraint because it has Self or associated type requirements
“好吧,嗯,”您说,“您忘记了类型自变量!”但不,我没有。 Swift不允许在协议名称后使用显式类型参数:
func useConverter(_ converter: Converter<Car, CarDTO>) { }
// ^ ~~~~~~~~~~~~~
// error: cannot specialize non-generic type 'Converter'
我不想陷入为什么的境地。只需接受Swift协议与Java接口通常不等效即可。
没有关联类型且没有提及Self
的Swift协议通常等效于非通用Java接口。但是具有关联类型(或提及Self
)的Swift协议实际上并不等同于任何Java构造。
在讨论此问题时,我们经常使用首字母缩写词“ PAT”,代表“具有关联类型的协议”(并包括提及Self
的协议)。 PAT并未定义您可以用作函数参数,返回值或属性值的类型。 PAT可以做很多事情:
您可以定义一个子协议。例如,Equatable
是PAT,因为它定义了==
运算符以接受两个Self
类型的参数。 Hashable
是Equatable
的子协议。
您可以将PAT用作类型约束。例如,Set
是通用类型。 Set
的类型参数命名为Element
。 Set
将其Element
约束为Hashable
。
因此,您不能编写以纯Converter
作为参数的函数。但是您可以编写一个函数,该函数将Converter
的任何实现作为参数,方法是使该函数通用:
func useConverter<MyConverter: Converter>(_ converter: MyConverter)
where MyConverter.Input == Car, MyConverter.Output == CarDTO
{ }
这样编译就可以了。但是有时使您的函数通用是不便的。
还有另一个无法解决的问题。您可能需要一个容器,该容器可容纳Converter
至Car
之间的各种CarDTO
。也就是说,您可能想要这样的东西:
var converters: [Converter<Car, CarDTO>] = []
// ^ ~~~~~~~~~~~~~
// error: cannot specialize non-generic type 'Converter'
我们无法通过使converters
通用来解决此问题,就像我们对useConverter
函数所做的那样。
您最终需要的是“类型擦除的包装器”。请注意,此处的“类型擦除”与Java的“类型擦除”具有不同的含义。实际上,这几乎与Java的类型擦除相反。让我解释一下。
如果您查看Swift标准库,则会发现名称以Any
开头的类型,例如AnyCollection
。这些大多是PAT的“类型擦除包装”。 AnyCollection
符合Collection
(是PAT),并包装符合Collection
的任何类型。例如:
var carArray = Array<Car>()
let carDictionary = Dictionary<String, Car>()
let carValues = carDictionary.values
// carValues has type Dictionary<String, Car>.Values, which is not an array but conforms to Collection
// This doesn't compile:
carArray = carValues
// ^~~~~~~~~
// error: cannot assign value of type 'Dictionary<String, Car>.Values' to type '[Car]'
// But we can wrap both carArray and carValues in AnyCollection:
var anyCars: AnyCollection<Car> = AnyCollection(carArray)
anyCars = AnyCollection(carValues)
请注意,我们必须将其他集合显式包装在AnyCollection
中。包装不是自动的。
这就是为什么我说这几乎与Java的类型擦除相反:
Java保留通用类型,但删除type参数。源代码中的java.util.ArrayList<Car>
在运行时变成java.util.ArrayList<_>
,而java.util.ArrayList<Truck>
在运行时也变成java.util.ArrayList<_>
。在这两种情况下,我们都保留容器类型(ArrayList
),但删除元素类型(Car
或Truck
)。
Swift类型擦除包装器将擦除通用类型,但保留type参数。我们将Array<Car>
变成AnyCollection<Car>
。我们还将Dictionary<String, Car>.Values
变成AnyCollection<Car>
。在这两种情况下,我们都会丢失原始容器类型(Array
或Dictionary.Values
),但保留元素类型(Car
)。
因此,无论如何,对于您的Converter
类型,将Converter
存储在容器中的一种解决方案是编写一个AnyConverter
类型擦除的包装器。例如:
struct AnyConverter<Input, Output>: Converter {
init<Wrapped: Converter>(_ wrapped: Wrapped) where Wrapped.Input == Input, Wrapped.Output == Output {
self.convertFunction = { wrapped.convert($0) }
}
func convert(_ input: Input) -> Output { return convertFunction(input) }
private let convertFunction: (Input) -> Output
}
(实现类型擦除的包装器有多种方法。这只是一种方法。)
然后可以在属性类型和函数参数中使用AnyConverter
,如下所示:
var converters: [AnyConverter<Car, CarDTO>] = [AnyConverter(converter)]
func useConverters(_ converters: [AnyConverter<Car, CarDTO>]) {
let car = Car()
for c in converters {
print("dto = \(c.convert(car))")
}
}
但是现在您应该问:有什么意义?如果我将不得不使用类型擦除的包装器,为什么还要麻烦Converter
做一个协议呢?为什么不只使用基类来定义接口,而子类来实现它呢?还是在初始化时提供了一些闭包的结构(例如上面的AnyConverter
示例)?
有时候,没有充分的理由使用协议,而仅使用类层次结构或结构更为明智。因此,您应该仔细研究如何实现和使用Converter
类型,并查看非协议方法是否更简单。如果是这样,请尝试像我在该答案顶部所示的设计:定义接口的基类,以及实现该接口的子类。
答案 1 :(得分:0)
与Java代码等效的Swift如下所示。
protocol Converter {
associatedtype Input
associatedtype Output
func convert(input: Input) -> Output
}
class CarConverterToDTO: Converter {
typealias Input = Car
typealias Output = CarDTO
func convert(input: Car) -> CarDTO {
return CarDTO()
}
}
说明
等效于Swift中的通用Java接口,将是带有associatedtype
s的协议。
protocol Converter {
associatedtype Input
associatedtype Output
}
要创建该协议的实现,实现必须使用typealias
指定关联类型映射到的类型。
class CarConverterToDTO: Converter {
typealias Input = Car
typealias Output = CarDTO
}
擦除类型
如果尝试使用此方法,则可能会遇到尝试将通用协议的实例存储在变量或属性中的问题,在这种情况下,您会收到编译器错误:
协议“转换器”只能用作通用约束,因为它具有“自我”或相关联的类型要求
在Swift中解决此问题的方法是使用类型擦除,您可以在其中创建通用协议的新实现,该协议本身就是通用类型(结构或类),并使用接受与您的协议相匹配的通用参数的构造函数,如下所示:
struct AnyConverter<Input, Output>: Converter {
// We don't need to specify type aliases for associated types, when the type
// itself has generic parameters, whose name matches the associated types.
/// A reference to the `convert(input:)` method of a converter.
private let _convert: (Input) -> Output
init<C>(_ converter: C) where C: Converter, C.Input == Input, C.Output == Output {
self._convert = converter.convert(input:)
}
func convert(input: Input) -> Output {
return self._convert(input)
}
}
这通常伴随通用协议上的扩展功能,该功能通过使用AnyConverter<Input, Output>
创建self
的实例来执行类型擦除,如下所示:
extension Converter {
func asConverter() -> AnyConverter<Input, Output> {
return AnyConverter(self)
}
}
使用类型擦除,您现在可以创建接受通用Converter
的代码(通过使用AnyConverter<Input, Output>
),该代码将Car
映射到CarDTO
:
let car: Car = ...
let converter: AnyConverter<Car, CarDTO> = ...
let dto: CarDTO = converter.convert(input: car)