Kekeの日記

エンジニア、読書なんでも

DIパターンをInterfaceを使って抽象化して実装する

https://fabianlee.org/wp-content/uploads/2017/05/golang-color-icon2.png

DIパターン,疎結合とは

DIパターンとは、疎結合な状態のコードを実現するものです。

たとえば以下のような構造体があったとします。

type Team struct{
   Members []Member
}

type Member struct {
   Name string
}

この時にTeamを初期化するような関数を作ったとします。

func NewTeam(names []string) *Team {
   members := make(Member, len(names))
   for i, name := range names {
          member := &Member{
                  Name: name,
          }
          members[i] = member
   }
   return &Team{
        Member: members,
        }

しかし、この関数には大きな問題があります。

それはTeam構造体を初期化したいのに、Member構造体について事前知って、メンバをはじめとするロジックが含まれたコードになっているのです。

このような状態だと、仮にMember構造体を何かしらリファクタリングすると、この関数自体にも影響が出るかもしれません。

このような時に依存関係の解決をします。

以下のようにします。

func main() {
   members := make(Member, len(names))
   for i, name := range names {
          member := &Member{
                  Name: name,
          }
          members[i] = member
   }

   team := NewTeam(members)
}

func NewTeam(members) []Member) *Team {
   return &Team{
        Member: members,
   }

のようにできます。また、membersを作るコードもNewMembersなど関数定義をすれば関数ごとに責務をもたらすことができます。

このような状態のことを疎結合の状態と呼びます。

また、今回は初期化のときにMemberを渡していました。 このようなDIの方法をコンストラクタインジェクションとよび、あとからフィールドをセットする方法をプロパティインジェクションと呼ぶ。

たとえば、以下のようなコードでそれは実現できます。

func (t Team) SetMembers(m []Member) {
     team.Member = m
}

interfaceを使って抽象化してみる

今回は別のケースです。

データベースにドメインを保存するCreateDomain関数があるとします。

一般的には構造体ごとにRubyのActiveRecordsのようなsave関数などを入れると思います。

しかし、そのような「データベースに永続化できる」という性質に着目すればもっとクリーンにかけます。

「データベースに永続化できる」 = storableという性質を定義します。

type Storable interface{
   save()
}

そして、このStorableの性質ももつものを永続化するリポジトリ関数を定義します。

func Create(d Storable) ... {
   ...
}

これによって、このインターフェースを実装しているものなら実行できる抽象的な関数ができました。

特にDIパターンは外部からオブジェクトを渡すので、たくさんあると同じ作業を繰り返してしまいます。

抽象化によってクリーンに書けているのではないかと思います。

テストがしやすい

データベースに保存するようなコードのテストはモックテストで済ませます。

実際にinterfaceを使うことで、モックテストをかけるようになっています。