0

Swift でコンパス可能なオブジェクトを構築する方法を作成しようとしています。私は自分が持っているものでほとんどそこにいるように感じますが、それでも100%正しいわけではありません.

私が目指しているのは、FlowControllerオブジェクトを作成UIViewControllersして必要な依存関係を与えることができるオブジェクトを持つことです。

私がやりたいことは、この作業をできるだけゆるくすることです。

ここに、機能するが理想的ではない小さな例があります。説明します...

コンポーネントとして使用できる 2 つのオブジェクトを次に示します...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!"

ご覧のとおり、ここで機能します。ユーザー オブジェクトが追加されます。

ただし、ご覧のとおり。プロトコルで vars を宣言したので、composableオブジェクトもwalletコンポーネントへの参照を持っています (ただし、常に nil になります)。

composable.wallet  // nil

私はこれで約 95% の道のりを進んでいるように感じますが、私ができるようにしたいのは、プロパティの宣言方法を改善することです。私が望むのは、その最後の行が...composable.walletコンパイルエラーになることです。

プロパティの宣言をプロトコルの外に移動することでこれを行うことができますが、プロトコルに準拠するオブジェクトにプロパティを追加できないという問題がありComposableます。

ファクトリ オブジェクトが宣言に依存せずにプロパティを追加できることは素晴らしいことです。または、「このオブジェクトにユーザーを呼び出すプロパティがある場合、それにユーザーコンポーネントを追加する」というある種のガードがあります。またはそのようなもの。

この作業の残りの 5% を取得する方法を誰かが知っていれば、それは素晴らしいことです。私が言ったように、これはうまくいきますが、理想的な方法ではありません.

ありがとう

ハッキー編集

うーん... ばかげた、恐ろしい、「誰もこれをすべきではない」編集として。プロトコル拡張を次のように変更しました...

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

少なくとも、定義していない変数にアクセスしようとすると、プログラムがクラッシュします。しかし、それはまだ理想的ではありません。

ダニエルのブログを読んだ後に編集

OK、やりたいことはできたと思います。それが正確に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などの多くの小さなプロトコルを使用します。次に、これらのさまざまな特性を構成したい具象型は、準拠するプロトコルとして「requiredComponents」をリストするだけです。UserComposingWalletComposing

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 }
}

次に、プロトコル拡張を使用して、実際のコンポーザブル トレイトを計算されたプロパティとして追加します。これにより、準拠するすべてのクラスでこれらのプロパティを作成する必要があるボイラープレートが削減されます。このようにして、どのクラスも、それぞれに特定の 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 に答える