【问题标题】:Using Swift Generics in Base class to easily return type class to subclass在基类中使用 Swift 泛型轻松将类型类返回到子类
【发布时间】:2021-04-12 13:04:48
【问题描述】:

我想在 BasePresenter 类中保留一个 Interactor 类,但在我的一生中,我无法弄清楚如何在 Swift 中轻松使用泛型。我想在基类中有一个函数(比如 BasePresenter interactorForType)。

public class InboxListPresenter: BasePresenter, ObservableObject {
        
    public init(interactor: InboxListInteractor) {
        
        super.init(router: InboxListRouter(viewData: viewData), interactor: interactor)
    }
    
    func findInboxNotifications() {
        
        interactorForType(type: InboxListInteractor.self).findInboxNotifications() { inboxNotifications, errorCode in
            
            // do something
        }
    }    
}

这是我想要保存通用对象的 BasePresenter。我正在尝试使用协议 (ILibertyInteractor)

open class BasePresenter {
    
    public let router:ILibertyRouter
    public let interactor:ILibertyInteractor
    
    public init(router:ILibertyRouter, interactor:ILibertyInteractor) {
        self.router = router
        self.interactor = interactor
    }
            
    func interactorForType<T>(type: T.Type) -> T {
        return interactor as! T
    }
    
    func routerForType<T>(type: T.Type) -> T {
        return router as! T
    }
}

上面的 interactorForType 对我想要避免的交互进行了强制解包。另外,我也不想传入对象。我想使用协议和泛型来拥有一个返回类型 T 的泛型函​​数。

下面是我试图允许在上面的演示者中返回类型的开始。

public protocol ILibertyPresenter {
    var router:ILibertyRouter { get }
}

public protocol ILibertyRouter {
    
}

public protocol ILibertyInteractor {
//    associatedtype T
    
//    func trueInteractor() -> T
}

【问题讨论】:

  • 这能满足您的需求吗? func interactorForType() -&gt; ILibertyInteractor { return interactor }
  • 你不能改变 Swift 子类中方法的返回类型(甚至不能让它更专业)。您在这里尝试做的事情是不可能的。首先摆脱类继承(即 BasePresenter)。将类继承与泛型和协议混合使用会导致类型混乱。编写一些具体的演示者、路由器和交互器。查看实际代码重复发生的位置。从中提取协议或泛型(我经常发现在实践中你需要的很少)。强烈避免创建类型只是为了填补架构槽。确保它对你有用。
  • 您的设计似乎自相矛盾。您对所有事情都使用协议,这意味着您不知道正在使用的具体类型,从而允许使用一种通用代码,因为当您调用协议上的方法时,它会动态地分派给适当的类。但另一方面,您希望使用真正的泛型,这取决于具体的类型信息,以便编译器可以静态调度方法。
  • 你有一个 InboxListPresenter 和一个 InboxListInteractor 和一个 InboxListRouter 的事实强烈暗示这些类型没有发挥它们的作用并且没有提供任何灵活性。如果 InboxListInteractor 不能与 InboxListPresenter (或 "Mock"InboxListPresenter) 以外的任何东西一起使用,那么您实际上并不是在编写通用代码。您正在以复杂的方式编写非常具体的代码。具体代码很好(它太棒了!),但没有理由不能让它变得简单。
  • 罗布是对的。不要将子类化与协议混合,您可以单独使用协议来完成所有操作。 InboxListPresenter 仍然需要是一个类,因为它需要是 ObserveableObject 才能与 SwiftUI 很好地配合。

标签: swift generics viper-architecture


【解决方案1】:

这里有两件事。您问的问题是“如何存储泛型?”,然后是您的示例代码似乎试图做的事情。

如何存储通用值

关于如何存储泛型的简短答案(假设通过 Any 是不可取的 - 也是不可取的)是将其存储在泛型中。

如果要将泛型类型作为其显式类型,则保持它的事物也必须是泛型的并且专门针对同一类型。这将要求您的 InboxListPresenter 也是通用的。所以你可以这样做:

open class BasePresenter <InteractorType: IteractorProtocol>{
    
    public let router:ILibertyRouter
    public let interactor: InteractorType // You can do this now
    
    public init(router:ILibertyRouter, interactor:ILibertyInteractor) {
        self.router = router
        self.interactor = interactor
    }
            
    /* you don't need this now, because InteractorType is part of the generic class definition 
    func interactorForType<T>(type: T.Type) -> T {
        return interactor as! T
    }
    */
    
    func routerForType<T>(type: T.Type) -> T {
        return router as! T
    }
}

在本例中,我指定 IteractorType 符合 InteractorProtocol

在某些情况下,您无需将周围的类型设为泛型,从而允许您这样做:

open class BasePresenter{
    
    public let router:ILibertyRouter
    public let interactor: InteractorProtocol
    
    public init(router:ILibertyRouter, interactor:InteractorProtocol) {
        self.router = router
        self.interactor = interactor
    }
            
    /* you don't need this now, because InteractorProtocol should define the interface for all Interactors 
    func interactorForType<T>(type: T.Type) -> T {
        return interactor as! T
    }
    */
    
    func routerForType<T>(type: T.Type) -> T {
        return router as! T
    }
}

具体来说,你可以这样做 if InteractorProtocol 没有,因为可怕的 Swift 编译器错误会说,“自我或相关的类型约束”。基本上如果您对协议或协议中的关联类型有where 子句或其他协议一致性约束,那么您只能使用协议来约束泛型参数。

如果您没有Self 或关联的类型约束,那么您可以将它当作普通类型使用,包括作为存储属性的类型。

代码似乎想要什么

您展示的代码基本上是经典 OOP 风格继承的典型代表。这不是解决问题的唯一方法,但是看着它,它需要一个定义接口的Interactor 基类,并需要具体的实现来完成实际工作。我不知道Interactor 必须做什么,但如果它足够复杂,您可以应用各种设计模式来避免必须拥有它的无数子类。您可以借鉴 Cocoa/CocoaTouch 声明委托协议的想法,这样您就可以通过这种方式修改行为。

无论如何,继承方法相当简单。

open class Iteractor {
    // Just replace the closure parameter types with whatever they're supposed to be
    func findInboxNotifications((Any, ErrorCode) -> Void) { fatalError("Implement in subclass!") }
}

然后您将其用作具体交互器类型的基类。

final class AConcreteInteractor: Iteractor {
    override func findInboxNotifications ((Any, ErrorCode) -> Void)  {
        /* Do actual work here */ 
    }
}

然后在你的BasePresenter:

open class BasePresenter {
    
    public let router:ILibertyRouter
    public let interactor: Iteractor
    
    public init(router:ILibertyRouter, interactor: Iteractor) {
        self.router = router
        self.interactor = interactor
    }
            
    /* This isn't needed because you'll use `interactor` polymorphically`
    func interactorForType<T>(type: T.Type) -> T {
        return interactor as! T
    }
    */

    ...
}

InboxListPresenter 看起来像这样

public class InboxListPresenter: BasePresenter, ObservableObject {
        
    public init(interactor: InboxListInteractor) {
        
        super.init(router: InboxListRouter(viewData: viewData), interactor: interactor)
    }
    
    func findInboxNotifications() {
        
        iteractor.findInboxNotifications() { inboxNotifications, errorCode in
            
            // do something
        }
    }    
}

使用协议而不是继承

我认为协议不适合这种情况,但在某些方面,协议可以替代基于类的继承;但是由于您必须存储交互器,因此您必须确保没有 Self 或相关的类型约束。

当使用这种方式时,协议优于继承的唯一优势是您可以获得更好的编译器检查 - 例如,它可以确保所有必需的实现方法实际上由符合协议的类型实现,而在类继承中,基类总是定义它们(因为与 Java 不同,Swift 不允许您定义纯粹的抽象基类,也不允许您像 C++ 那样指定“纯”虚拟方法)。您需要为这种好处付出代价,因为您可以更严格地定义它们,以便可以将它们用作存储的属性类型。

我会说您在问题中提出的代码想要为您的交互器使用基于类的继承。除非您有充分的理由不这样做,否则我会选择这样做,而不是试图将方钉强行插入圆孔。

【讨论】:

  • 感谢您的完整建议!是的,当涉及泛型时,当我从主要的 Java +10 年背景进入 Swift 时,我正在努力寻找一个简单的解决方案。更不用说拥有一个伪力架构以及如何构建 SwiftUI 应用程序。我会尝试这些选项,但最初我的想法是我很幸运能够通用地存储对象并且本机返回子类而无需执行一些样板代码。我可能只是走协议路线,为每个实现的具体类提供一致性,并为每个类添加额外的属性。
  • 哈哈。即使在 Swift 中的泛型示例上,我也遇到了很多困难。但是还没有找到好的解决方案。我认为您一针见血。“Swift 不允许您定义纯粹的抽象基类”。这就是我缺少一种将事物存储在开放类中并直接返回对象的好方法的地方。
  • 您仍然可以定义基类,只是不能阻止它们被实例化,并且除了必需的初始化程序之外,您不能强制子类将覆盖它们应该覆盖的方法。但是大部分 Cocoa/CocoaTouch 都是这样构建的,而且效果很好。您可以使用您想要的不依赖泛型的接口构建一个非泛型基类,然后定义该基类的泛型子类。
  • 所以class Base { }class Subclass&lt;T&gt;: Base { }
  • 您可以通过将其初始化程序设置为内部来防止基类被实例化,但是模块中的任何内容仍然可以创建一个,并且模块外部的任何人都试图在一个调用super.init子类会有问题。私有初始化器主要用于强制通过工厂方法创建对象。
猜你喜欢
  • 2022-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多