You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在视图与实现间构建协议/接口分离层?——基于SOLID与MVVM架构的多服务泛型协议问题求解

我太懂这种被泛型协议卡脖子的感觉了——带关联类型和Self约束的协议看起来完美契合依赖倒置的设计,但真要拿来当变量或者存进集合里,编译器分分钟给你甩错误。手动写每个服务对应的类型擦除类确实是个苦力活,尤其是服务多的时候。下面给你几个实用的思路,包括一个能简化类型擦除的通用方案,还有替代的架构方向。

一、通用型自动类型擦除方案

我们可以写一个万能的AnyService结构体,它能包装任何符合Service协议的实例,同时处理关联类型、Hashable和Codable这些带Self约束的要求。核心是把协议的关联类型转为通用参数,用闭包捕获原实例的行为:

struct AnyService<Config: ServerConfiguration>: Service, Hashable, Codable {
    private let wrappedConfiguration: Config
    private let performJobClosure: () -> Void
    private let hashClosure: (inout Hasher) -> Void
    private let encodeClosure: (Encoder) throws -> Void
    private let decodeFactory: (Config) throws -> any Service

    // 初始化方法:包装任意符合Service协议的实例
    init<Wrapped: Service>(_ wrapped: Wrapped, decodeFactory: @escaping (Config) throws -> Wrapped) 
    where Wrapped.Configuration == Config, Wrapped: Codable {
        self.wrappedConfiguration = wrapped.configuration
        self.performJobClosure = wrapped.performJob
        self.hashClosure = wrapped.hash(into:)
        self.encodeClosure = wrapped.encode(to:)
        self.decodeFactory = decodeFactory
    }

    // 实现Service协议要求
    var configuration: Config { wrappedConfiguration }

    init(using configuration: Config) {
        fatalError("请使用init(_ wrapped:, decodeFactory:)创建实例")
    }

    func performJob() {
        performJobClosure()
    }

    // 实现Hashable
    func hash(into hasher: inout Hasher) {
        hashClosure(&hasher)
    }

    static func == (lhs: AnyService<Config>, rhs: AnyService<Config>) -> Bool {
        lhs.wrappedConfiguration == rhs.wrappedConfiguration
    }

    // 实现Codable
    func encode(to encoder: Encoder) throws {
        try encodeClosure(encoder)
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        self.wrappedConfiguration = try container.decode(Config.self)
        self.performJobClosure = { try decodeFactory(self.wrappedConfiguration).performJob() }
        self.hashClosure = { hasher in try decodeFactory(self.wrappedConfiguration).hash(into: &hasher) }
        self.encodeClosure = { encoder in try decodeFactory(self.wrappedConfiguration).encode(to: encoder) }
        self.decodeFactory = { try self.decodeFactory($0) }
    }
}

使用的时候只需要一行代码包装你的服务:

let config = SomeServerConfiguration(baseURL: URL(string: "https://example.com")!)
let authService = SomeAuthenticationService(using: config)
let anyAuthService = AnyService(authService, decodeFactory: SomeAuthenticationService.init(using:))

这样你就可以用AnyService<SomeServerConfiguration>作为统一类型,存储不同的服务实例了。

二、用服务注册器统一管理实例

如果觉得类型擦除还是麻烦,你可以搞一个服务注册器,把服务的创建逻辑统一托管,完全避免直接引用泛型协议:

class ServiceRegistry {
    static let shared = ServiceRegistry()
    private var serviceFactories: [ObjectIdentifier: (any ServerConfiguration) -> any Service] = [:]

    // 注册服务工厂
    func register<ServiceType: Service>(factory: @escaping (ServiceType.Configuration) -> ServiceType) {
        let key = ObjectIdentifier(ServiceType.self)
        serviceFactories[key] = { config in
            guard let typedConfig = config as? ServiceType.Configuration else {
                fatalError("配置类型不匹配 \(ServiceType.self)")
            }
            return factory(typedConfig)
        }
    }

    // 获取服务实例
    func getService<ServiceType: Service>(with config: ServiceType.Configuration) -> ServiceType {
        let key = ObjectIdentifier(ServiceType.self)
        guard let factory = serviceFactories[key],
              let service = factory(config) as? ServiceType else {
            fatalError("未注册 \(ServiceType.self) 服务或配置无效")
        }
        return service
    }
}

使用流程很简单:先注册服务,再按需获取:

// 注册服务(建议在App启动时完成)
ServiceRegistry.shared.register { config in
    SomeAuthenticationService(using: config)
}

// 在需要的地方获取服务
let config = SomeServerConfiguration(baseURL: URL(string: "https://example.com")!)
let authService: SomeAuthenticationService = ServiceRegistry.shared.getService(with: config)

这个方案既保留了依赖倒置的优势,又不用处理泛型协议的类型问题,还能和依赖注入结合让代码更解耦。

三、调整协议设计,去掉关联类型

如果可以接受少量运行时类型检查,直接修改协议设计是最快捷的解决方式:把关联类型换成存在类型any,牺牲一点类型安全换编译器友好:

protocol Service: ObservableObject, Hashable, Codable {
    var configuration: any ServerConfiguration { get }
    init(using configuration: any ServerConfiguration)
    func performJob()
}

对应的实现类调整为:

class SomeAuthenticationService: AuthenticationService {
    var configuration: any ServerConfiguration

    required init(using configuration: any ServerConfiguration) {
        guard let typedConfig = configuration as? SomeServerConfiguration else {
            fatalError("配置类型不匹配")
        }
        self.configuration = typedConfig
    }

    func performJob() {}
}

这样你就可以直接用any Service作为类型来引用实例,完全不用处理类型擦除。

总结

  • 想要严格类型安全又不想重复写擦除代码:通用AnyService + 服务注册器的组合是最优解,注册器还能帮你统一管理解码逻辑。
  • 追求最快解决问题:调整协议去掉关联类型,虽然有运行时类型检查,但开发成本最低。
  • 服务注册器的方式还能很好地适配依赖注入,让你的架构扩展性更强。

内容的提问来源于stack exchange,提问作者zombie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 11:57:35