0

我正在尝试创建一种在 Swift 中构建可比较对象的方法。我觉得我几乎已经有了我所拥有的,但它仍然不是 100% 正确的。

我的目标是拥有一个FlowController可以创建我们的对象,UIViewControllers然后为他们提供他们需要的任何依赖项。

我还想做的是使这项工作尽可能松散。

我在这里有一个小例子,但并不理想。我会解释...

这里有两个可以用作组件的对象......WalletUser

class Wallet {
    func topUp(amount: Int) {
        print("Top up wallet with £\(amount)")
    }
}

class User {
    func sayHello() {
        Print("Hello, world!")
    }
}

然后我们定义一个Component枚举,其中包含每个案例......

enum Component {
    case Wallet
    case User
}

...以及定义requiresComponents返回数组的方法的协议Components

这就是问题出现的地方。为了让“工厂对象”将组件放入Composable对象中,我们还需要在协议中定义userwallet属性。

protocol Composable {
    var user: User? {get set}
    var wallet: Wallet? {get set}
    
    func requiresComponents() -> [Component]
}

为了使这些属性“可选”(不是可选的),我定义了Composable协议的扩展,将这些变量定义为 nil。

extension Composable {
    var user: User? {
        get {return nil}
        set {}
    }
    var wallet: Wallet? {
        get {return nil}
        set {}
    }
}

现在我声明我要创建的类Composable。如您所见,它需要User组件并声明变量。

class SomeComposableClass: Composable {
    var user: User?
    
    func requiresComponents() -> [Component] {
        return [.User]
    }
}

现在FlowController将创建这些并将组件添加到它们。您可以在这里看到我必须获取对象,创建它的本地var版本,然后返回更新的对象。我认为这是因为它不知道将符合协议的对象的类型,因此不能改变参数。

class FlowController {
    func addComponents<T: Composable>(toComposableObject object: T) -> T {
        var localObject = object
        
        for component in object.requiresComponents() {
            switch component {
            case .Wallet:
                localObject.wallet = Wallet()
                print("Wallet")
            case .User:
                localObject.user = User()
                print("User")
            }
        }
        
        return localObject
    }
}

我在这里创建对象。

let flowController = FlowController()
let composable = SomeComposableClass()

在这里我添加了组件。在生产中,这将在FlowController.

flowController.addComponents(toComposableObject: composable)  // prints "User" when adding the user component
compassable.user?.sayHello()  // prints "Hello, world!"

如您所见,它在这里工作。用户对象被添加。

但是,您也可以看到。因为我已经在协议中声明了变量,所以该composable对象还具有对wallet组件的引用(尽管它总是为零)。

composable.wallet  // nil

我觉得我已经完成了 95% 的工作,但我想做的是改进属性的声明方式。我想要的是最后一行......composable.wallet成为编译错误。

我可以通过将属性声明移出协议来做到这一点,但是我遇到了无法将属性添加到任何符合Composable协议的对象的问题。

令人敬畏的是工厂对象能够在不依赖声明的情况下添加属性。或者甚至有某种守卫说“如果这个对象有一个属性调用用户,那么将用户组件添加到它”。或类似的东西。

如果有人知道我怎样才能让这剩下的 5% 工作,那就太棒了。就像我说的,这行得通,只是不是以理想的方式。

感谢:D

哈基编辑

嗯......作为一个快速俗气,可怕的,“没有人应该这样做”的编辑。我已将我的协议扩展更改为这样......

extension Composable {
    var user: User? {
        get {fatalError("Access user")}
        set {fatalError("Set user")}
    }
    var wallet: Wallet? {
        get {fatalError("Access wallet")}
        set {fatalError("Set waller")}
    }
}

现在,如果我尝试访问尚未定义的变量,至少程序会崩溃。但它仍然不理想。

阅读丹尼尔的博客后编辑

好吧,我想我已经做到了我想要的。只是不确定它是否完全是 Swifty。虽然,我也觉得有可能。寻求第二意见:)

所以,我的组件和协议变成了这个……

// these are unchanged
class Wallet {
    func topUp(amount: Int) {
        print("Top up wallet with £\(amount)")
    }
}

// each component gets a protocol    
protocol WalletComposing {
    var wallet: Wallet? {get set}
}

class User {
    func sayHello() {
        print("Hello, world!")
    }
}

protocol UserComposing {
    var user: User? {get set}
}

现在工厂方法已经改变了......

// this is the bit I'm unsure about.
// I now have to check for conformance to each protocol
// and add the components accordingly.
// does this look OK?
func addComponents(toComposableObject object: AnyObject) {
    if var localObject = object as? UserComposing {
        localObject.user = User()
        print("User")
    }
    
    if var localObject = object as? WalletComposing {
        localObject.wallet = Wallet()
        print("Wallet")
    }
}

这让我可以做到这一点......

class SomeComposableClass: UserComposing {
    var user: User?
}

class OtherClass: UserComposing, WalletComposing {
    var user: User?
    var wallet: Wallet?
}

let flowController = FlowController()

let composable = SomeComposableClass()
flowController.addComponents(toComposableObject: composable)
composable.user?.sayHello()
composable.wallet?.topUp(amount: 20) // this is now a compile time error which is what I wanted :D

let other = OtherClass()
flowController.addComponents(toComposableObject: other)
other.user?.sayHello()
other.wallet?.topUp(amount: 10)
4

1 回答 1

1

这似乎是应用接口隔离原则的好案例

具体来说,与其拥有主Composable协议,不如拥有许多较小的协议,例如UserComposingWalletComposing。然后,您希望构成这些不同特征的具体类型只需将它们的“requiredComponents”列为它们符合的协议,即:

class FlowController : UserComposing, WalletComposing 

实际上,我写了一篇博文,更广泛地讨论了这个问题,并在http://www.danielhall.io/a-swift-y-approach-to-dependency-injection提供了更详细的示例


更新:

查看更新的问题和示例代码,我只建议进行以下改进:

回到你的原始设计,定义一个Composing需要任何符合的类来为组合特征创建存储作为字典的基本协议可能是有意义的。像这样的东西:

protocol Composing : class {
    var traitDictionary:[String:Any] { get, set }
}

然后,使用协议扩展将实际的可组合特征添加为计算属性,这减少了必须在每个符合类中创建这些属性的样板。这样,任何类都可以符合任意数量的 trait 协议,而不必为每个协议声明一个特定的 var。这是一个更完整的示例实现:

class FlowController {
    static func userFor(instance:UserComposing) -> User {
        return User()
    }
    static func walletFor(instance:WalletComposing) -> Wallet {
        return Wallet()
    }
}

protocol Composing : class {
    var traitDictionary:[String:Any] { get, set }
}

protocol UserComposing : Composing {}
extension UserComposing {
    var user:User {
        get {
            if let user = traitDictionary["user"] as? User {
                return user
            }
            else {
                let user = FlowController.userFor(self)
                traitDictionary["user"] = user
                return user
            }
        }
    }
}

protocol WalletComposing {}
extension WalletComposing {
    var wallet:Wallet {
        get {
            if let wallet = traitDictionary["wallet"] as? Wallet {
                return wallet
            }
            else {
                let wallet = FlowController.walletFor(self)
                traitDictionary["wallet"] = wallet
                return wallet
            }
        }
    }
}

class AbstractComposing {
    var traitDictionary = [String:Any]()
}

这不仅摆脱了您必须在任何地方解包的那些讨厌的选项,而且还使用户和钱包的注入变得隐式和自动。这意味着您的类即使在它们自己的初始化程序中也已经具有这些特征的正确值,无需每次都显式地将每个新实例传递给 FlowController 的实例。

例如,您的最后一个代码片段现在将变得简单:

class SomeComposableClass: AbstractComposing, UserComposing {} // no need to declare var anymore

class OtherClass: AbstractComposing, UserComposing, WalletComposing {} //no vars here either!

let composable = SomeComposableClass() // No need to instantiate FlowController and pass in this instance
composable.user.sayHello() // No unwrapping the optional, this is guaranteed
composable.wallet.topUp(amount: 20) // this is still a compile time error which is what you wanted :D

let other = OtherClass() // No need to instantiate FlowController and pass in this instance
other.user.sayHello()
other.wallet.topUp(amount: 10) // It all "just works"  ;)
于 2016-06-29T21:13:58.280 回答