swift - 在 Swift 中使用 DI 时避免胖初始化器

标签 swift dependency-injection protocols

我试图理解 Swift 中的 DI

我知道有 Swinject 等框架可以帮助 DI 但是我很想自己理解这一点,而不是使用太多框架的魔力。

以下面的代码为例

我的 ProfileService 的初始化程序只会随着这个服务的扩展而继续增长并变得越来越胖,并且由于项目包含多个类,所以相同的模式会重复很多次。

如何避免这种情况?我希望找到一种方法来支持轻松维护,同时仍然获得直接和简单代码注入(inject)的所有好处。

我在想也许可以使用 ProtocolStruct 来包含依赖项并将其注入(inject),但是无法理解如何最好地实现它。

import UIKit

class UserService {
    func currentUser() -> String {
        return "some username"
    }
}

class AvatarService {
    func currentUserAvatarUrl() -> String {
        return "https://foo.bar/image.png"
    }
}

class MessageService {
    func currentInbox() -> [String:String] {
        return [
            "9279n1n2283":"something something",
            "m2j292i2m2n":"something something something"
        ]
    }
}

class ProfileService {
    private let userService: UserService
    private let avatarService: AvatarService
    private let messageService: MessageService

    init(userService: UserService, avatarService: AvatarService, messageService: MessageService) {
        self.userService = userService
        self.avatarService = avatarService
        self.messageService = messageService
    }

    func getLoggedInUser() -> String {
        return userService.currentUser()
    }

    func getUserAvatar() -> String {
        return avatarService.currentUserAvatarUrl()
    }

    func getInboxMessages() -> [String:String] {
        return messageService.currentInbox()
    }
}

let userService = UserService()
let avatarService = AvatarService()
let messageService = MessageService()

let profileService = ProfileService(userService: userService, avatarService: avatarService, messageService: messageService)

profileService.getLoggedInUser()
profileService.getUserAvatar()
profileService.getInboxMessages()

最佳答案

我的建议是根本不要直接对这些类进行依赖注入(inject)。我会做这样的事情:

import Foundation

// MARK: Protocols

protocol UserService {
    var username: String { get }
}

protocol AvatarService {
    var userAvatarURL: URL { get }
}

protocol MessageService {
    var inbox: [String: String] { get }
}

protocol GlobalServiceContext {
    var userService: UserService { get }
    var avatarService: AvatarService { get }
    var messageService: MessageService { get }
}

// MARK: Mock Implementations

class MockUserService: UserService {
    let username = "test_user"
}

class MockAvatarService: AvatarService {
    let userAvatarURL = URL(string: "https://en.wikipedia.org/static/images/project-logos/enwiki.png")!
}

class MockMessageService: MessageService {
    let inbox = [
        "test subject 1": "test message 1",
        "test subject 2": "test message 2",
        "test subject 3": "test message 3",
    ]
}

class MockServiceContext: GlobalServiceContext {
    let userService: UserService = MockUserService()
    let avatarService: AvatarService = MockAvatarService()
    let messageService: MessageService = MockMessageService()
}

// MARK: Mock Implementations

class ProdUserService: UserService {
    // TODO: Substitute real implementation here
    let username = "prod_user"
}

class ProdAvatarService: AvatarService {
    // TODO: Substitute real implementation here
    let userAvatarURL = URL(string: "https://en.wikipedia.org/static/images/project-logos/enwiki.png")!
}

class ProdMessageService: MessageService {
    let inbox = [ // TODO: Substitute real implementation here
        "Prod subject 1": "Prod message 1",
        "Prod subject 2": "Prod message 2",
        "Prod subject 3": "Prod message 3",
    ]
}

class ProdServiceContext: GlobalServiceContext {
    let userService: UserService = ProdUserService()
    let avatarService: AvatarService = ProdAvatarService()
    let messageService: MessageService  = ProdMessageService()
}

// MARK: Usage

let ServiceContext: GlobalServiceContext = MockServiceContext()

class ProfileService {
    var username: String { return ServiceContext.userService.username }
    var userAvatarURL: URL { return ServiceContext.avatarService.userAvatarURL }
    var inbox: [String:String] { return ServiceContext.messageService.inbox }
}

let profileService = ProfileService()
print(profileService.username)
print(profileService.userAvatarURL)
print(profileService.inbox)

它包含您所有的全局状态(您的 API、服务、数据库等):

  • 成为一个单一的对象
  • 全局可用
  • 其值可以替代模拟实现

关于swift - 在 Swift 中使用 DI 时避免胖初始化器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53821836/

相关文章:

unit-testing - Diffie-Hellman 检验向量

python - 当 tkinter 窗口获得焦点时如何处理

仅由特定类实现的 Swift 协议(protocol)

ios - Swift:变量不变(可能是全局性问题)

ios - 帧更改的 AVPlayer 回调(观察者)

ios - 如何访问DJIGO App飞行计划日志文件

javascript - 依赖注入(inject)库 - 重命名注入(inject)值

ios - 如何使用 Alamofire 将数据发布到服务器 API/注册用户?

laravel中的sql注入(inject)预防

c++ - 如何将shared_ptr<void>转换为另一种类型?