Skip to content

Instantly share code, notes, and snippets.

@kazuhiro4949
Last active April 26, 2017 04:57
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 kazuhiro4949/754d24f02e34152c8d81f78255c7dbbe to your computer and use it in GitHub Desktop.
Save kazuhiro4949/754d24f02e34152c8d81f78255c7dbbe to your computer and use it in GitHub Desktop.
//: Playground - noun: a place where people can play
import UIKit
// Cake PatternもどきをSwiftで実装したもの。要するにProtocolのコンポジションによって依存の定義、解決をしていく
protocol Animal {}
struct Cat: Animal {}
protocol AnimalComponent {
var animal: Animal { get }
}
protocol Gender {}
struct Man: Gender {}
protocol GenderComponent {
var gender: Gender { get }
}
// 上位レイヤー
protocol Owner: AnimalComponent, GenderComponent {
func exec()
}
extension Owner {
func exec() {
print("\(type(of: gender)) has \(type(of: animal))")
}
}
class Taro: Owner {
let animal: Animal = Cat()
let gender: Gender = Man()
}
protocol OwnerComponent {
var owner: Owner { get }
}
// #############################################################################
// Minimal Cake patternによるDI。mix-inをしていきながら依存を解決していく
// 4つの役割をプロトコルやクラス(値型)を作る。
// 1. 依存対象のプロトコルとその実装クラス (それ自体に依存するものがあれば、下位レイヤの2を持っている)
// 2. 依存するということを定義したプロトコル
// 3. 依存をまとめた(mix-inした)上位レイヤプロトコルと、プロトコルエクステンションによる依存を使った実装
// 4. 依存解決に特化したクラス
// これらをさらに上位のレイヤーでまとめ上げ、そのレイヤー内で同じものを作り、もう一つ上位のレイヤーでまとめ上げ...というのを繰り返す。
// #############################################################################
protocol Dao2 {
}
class ConcreteDao2: Dao2 {
}
protocol UsesDao2 {
var dao2: Dao2 { get }
}
protocol Dao {
init(str: String)
}
class ConcreteDao: Dao {
required init(str: String) {
}
}
class MockDao: Dao {
required init(str: String) {
}
}
protocol UsesDao {
var dao: Dao { get }
}
// 上位レイヤー
protocol Service: UsesDao, UsesDao2 {
func exec()
}
extension Service {
func exec() {
print(dao)
print(dao2)
}
}
class ConcreteService: Service {
var dao: Dao
var dao2: Dao2
init(str: String) {
dao = ConcreteDao(str: str)
dao2 = ConcreteDao2()
}
}
protocol UsesService {
var service: Service { get }
}
// 上位レイヤー
protocol Application: UsesService {
func main()
}
extension Application {
func main() {
}
}
class ConcreteApplication: Application {
var service: Service
init(str: String) {
service = ConcreteService(str: str)
}
}
// Applicationを実際に使う
ConcreteApplication(str: "aaa").main()
// Serviceのユニットテストを実装する
class TestService: Service {
var dao: Dao
var dao2: Dao2
init(str: String) {
dao2 = ConcreteDao2()
dao = MockDao(str: str)
}
}
// 感想
// - 依存をプロトコルとして定義してそれらを上位のプロトコルにまとめていく方法
// - 単なるConstract DIと違って初期化で依存を渡す必要がなくなる
// - DIコンテナと違って、コードを書いている最中やコンパイル時に依存解決のエラーが発見できる
// - DIコンテナと違い、外部ライブラリへ依存させる必要がなくなる
// - プロトコルが多くなってしまう
// 実装はプロトコルエクステンションで定義された依存のみを使って行うため、複雑なロジックを実装する時に苦労しないか心配
protocol DaoComponent {
var dao: Dao { get }
static func create(str: String) -> Dao
}
protocol Dao2Component {
var dao2: Dao2 { get }
static func create() -> Dao2
}
protocol ConcreteDaoComponent: DaoComponent {}
extension ConcreteDaoComponent {
static func create(str: String) -> Dao {
return ConcreteDao(str: str)
}
}
protocol ConcreteDao2Component: Dao2Component {}
extension ConcreteDao2Component {
static func create() -> Dao2 {
return ConcreteDao2()
}
}
class ApplicationContext: ConcreteDaoComponent, ConcreteDao2Component {
var dao: Dao
var dao2: Dao2
init(str: String) {
dao = ApplicationContext.create(str: str)
dao2 = ApplicationContext.create()
}
}
typealias Context = protocol<DaoComponent, Dao2Component>
struct AnyContext<T: Context> {
let context: T
var dao: Dao {
return context.dao
}
var dao2: Dao2 {
return context.dao2
}
init(_ context: T) {
self.context = context
}
}
let context = AnyContext(ApplicationContext(str: "hoge"))
//########################################################################################
protocol DaoComponent2 {
associatedtype DaoType: Dao
func create(str: String) -> DaoType
}
extension DaoComponent2 {
func create(str: String) -> DaoType {
return DaoType(str: str)
}
}
protocol Service2: DaoComponent2 {
func fetch(str: String) -> Dao
}
extension Service2 {
func fetch(str: String) -> Dao {
return create(str: str)
}
}
class Application2<T: Dao>: Service2 {
typealias DaoType = T
}
let a = Application2<MockDao>().fetch(str: "")
let b = Application2<ConcreteDao>().fetch(str: "")
//###########################################################
// http://merowing.info/2017/04/using-protocol-compositon-for-dependency-injection/
// 依存をprotocolで定義する部分のみを扱っている
// 基本的に構造は上と同じだが、ここでは依存はプロトコルに対してではなくクラスや値に対して行っている。
// 場合によってはそれで十分なのかも。(そして普段自分もそのレベルでコードを書いている)
// このやり方の場合、必要になったらプロトコルへ抽象してCake Patternへ徐々に持っていけそう
protocol HasDaoDependency {
var dao: Dao { get }
}
protocol HasDao2Dependency {
var dao2: Dao2 { get }
}
class Coordinator<Dependencies: HasDaoDependency & HasDao2Dependency> {
let dependencies: Dependencies
init(dependencies: Dependencies) {
self.dependencies = dependencies
}
}
struct ConcreteDependency: HasDaoDependency, HasDao2Dependency {
let dao2: Dao2
let dao: Dao
init(str: String) {
dao = ConcreteDao(str: str)
dao2 = ConcreteDao2()
}
}
let corrdinator = Coordinator(dependencies: ConcreteDependency(str: ""))
// 要はやっていることとしては、ブログのタイトルにもなっているけれど、Protocolを使って依存をコンポジションしようということ。
// Protocol Extensionによって、プロトコルに依存するプロトコルが定義された上でそれらを使った実装を実現できる。
// それだけで全ての需要が満たせるか・実際のアプリ開発へ無理なく導入できるかはちょっと自信持てない。
// こちらの記事にもあるが、プロトコルへの依存するようにするとプロトコル数が一気に増えてしまうので、必要になった時に都度プロトコルへ抽象して、依存を外に出すという判断するのが良さそう
// そうやって段階的にDI化としていく時にCake Pattern的なものは使えそう。
// http://milanista224.blogspot.jp/2016/05/repo-160427011.html
// 自分個人の目的としては、動的に初期化を行うDIコンテナが辛いので何かしら静的なしくみに置き換えていきたいと思っている。ただしいきなり全部置き換えたり全てその仕組で実装していくのではなく、やる価値があるところから少しづつ始めていけるようなやり方にしたい。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment