Skip to content

Instantly share code, notes, and snippets.

@takasek
Created April 4, 2018 15:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save takasek/2cbb7be66fe6d91c412985b0456cbc6c to your computer and use it in GitHub Desktop.
Save takasek/2cbb7be66fe6d91c412985b0456cbc6c to your computer and use it in GitHub Desktop.
依存グラフの解決を行うオブジェクトが必要な理由 #CodePiece
do {
struct A {}
struct B { let a: A }
struct C { let b: B }
// 何もしないと、「AをinjectしたC」を作りたいときこうなる
let c = C(b: B(a: A()))
// 「CがBに依存している」という知識は、本来Cを使う場合は不要なはず
// そのため、依存グラフを解決する Resolver を考える
struct Resolver {
func resolveC(a: A) -> C {
return C(b: B(a: a))
}
}
let c2 = Resolver().resolveC(a: A())
// これで、依存グラフについての知識をこのコードから排除できた
}
do {
struct A {}
struct X { let a: A } // 中間層 X が生まれたとする
struct B { let x: X }
struct C { let b: B }
// 「AをinjectしたC」を作るコードは以下のように変更しなければならない
//let c = C(b: B(a: A()))
let c = C(b: B(x: X(a: A())))
// これは、「CがBやXに依存している」という知識をこのコードが持ってしまっているから。
// 依存解決の責務を Resolver に逃がしていれば、コードに変更は起こらない。
// 変更の影響範囲は Resolver 内に留められる。
let c2 = Resolver().resolveC(a: A())
}
@Kuniwak
Copy link

Kuniwak commented Apr 9, 2018

この例ですが、これが単体テストなら 私は以下のようにします:

do {
    struct A {}
    prototype B {}
    struct BImpl: B { let a: A }
    struct BStub: B {}
    struct C { let b: B }

    // BImpl が壊れた時に、C のテストが落ちるのは望ましくないので、
    // BImpl はテストダブル BStub へと置き換える方が望ましい(このとき、
    // BImpl の設計がよければテストダブルへの置き換えは苦にならない)
    let c = C(b: BStub())

    // これで、依存グラフについての知識をこのコードから排除できた
}

なお、B をテストダブルへと置き換えられない理由があるならば、この例は使えません。
ただし、私の経験則では、単体テストであるオブジェクトをテストダブルへと置き換えられない場合、設計を間違っていることが多いです。
間違っている原因は責務過多/密接合/CQSができてないのいずれかです。

また、単体テストではなく結合テストをしたいのならば、下のように書きますね:

let c = C(b: B(a: A())

これは組み合わせのテストですから、どのような組み合わせでテストしたいのかが明示されるべきだからです。

@Kuniwak
Copy link

Kuniwak commented Apr 9, 2018

補足ですが、もし、どうしても C と A の結合を残したいのならば、次のように C を設計します:

do {
    struct A {}
    struct B {}
    struct C {
        let b: B
        init(a: A) { self.b = B(a: a) }
    }

    let c = C(a: A())
}

最初に提示していただいたケースでは B に別のものを使わないことが前提となっていますから、この書き方でも問題ないはずです。

@takasek
Copy link
Author

takasek commented Apr 9, 2018

となると、「CとAの結合は残したい」場合は「Bを除いたCの単体テストは行わない」ということでしょうか?
……と思いましたが、イニシャライザを複数用意すればいいだけですね。なるほど。

do {
    struct A {}
    protocol B {}
    struct BImpl: B { ... }
    struct BStub: B { ... }
    struct C {
        let b: B
        init(a: A) { self.b = BImpl(a: a) }
        init(b: B) { self.b = b }
    }

    let c = C(a: A())
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment