在Swift上使用DI时避免使用繁琐的初始化程序

时间:2018-12-17 19:28:19

标签: swift dependency-injection protocols

我试图在Swift中理解DI

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

以下面的代码为例

我的ProfileService的初始化方法只会继续增长,并且随着此 pseudo 服务的扩展越来越胖,并且由于项目包含多个类,因此将重复相同的模式很多很多次。

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

我正在考虑也许使用ProtocolStruct来包含依赖项并将其注入,但是无法理解如何最好地实现它。

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()

2 个答案:

答案 0 :(得分:0)

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

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,服务,数据库等):

  • 放入单个对象
  • 全球可用
  • 可以将其值替换为模拟实现

答案 1 :(得分:0)

使用DI时,胖的初始值设定项可能是不良设计的标志,并表明您的组件并非简单的任务。因此,您应该尝试使其断开并解耦。