こんにちは。KOUKIです。
とある企業でWeb系のエンジニアをしています。開発言語はGo言語です。
以前、「[golang]Interfaceのオススメの使い方」について紹介しました。
この記事が結構好評だったので、第二弾を記事にしたいと思います。
Interfaceの基礎については、以下の記事を参考にしてください!
<目次>
Interfaceで密結合を避ける
今回、私がオススメするInterfaceの使い方として、「密結合を避ける」をご紹介したいと思います。
Interfaceを使わない場合
次のサンプルは、密結合が顕著なダサいコードです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package main import "log" // 3rdパーティー製のモジュールとする type DBInfo3rdParty struct { username string password string } // DBを操作するハンドラーを作成 type DBHandler struct { DBInfo3rdParty } func DBNormal(username, password string) { // インスタンス化 d := &DBHandler{ DBInfo3rdParty{ username: username, password: password, }, } // DBを操作する d.Get() d.Delete() } // データベースからデータを取り出す func (d *DBHandler) Get() { log.Println("Get Process") } // データベースからデータを削除する func (d *DBHandler) Delete() { log.Println("Delete Process") } func main() { // DBを操作するためusername,passwordを渡す DBNormal("user", "pass") } |
DBInfo3rdPartyは、3rdパーティー製のモジュールだと仮定してください。
このモジュールを取り込んだDBHandlerを定義しており、これをインスタンス化することで、DBの操作(Get, Delete)を可能にしています。
DBの操作メソッドを使うには、main関数内でDBNormal関数を呼び出してインスタンス化する必要があります。その際に、usernameとpasswordを渡しています。
このコードは、一見問題なく見えます。しかし、実はDBNormalにusernameとpasswordを渡してインスタンス化しているところが密結合になっています。
例えば、DBInfo3rdPartyモジュールに変更があった場合は、次の修正作業が発生します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
type DBInfo3rdParty struct { username string password string text string // textが追加される } // 修正1; textを引数に持たせないといけない func DBNormal(username, password, text string) { // インスタンス化 d := &DBHandler{ DBInfo3rdParty{ username: username, password: password, text: text, // 修正2; textを追加しなければならない }, } ... } func main() { DBNormal("user", "pass", "ok") // 修正3: textを追加しなければならない } |
今回の修正は「簡単」な内容でしたが、修正すべき箇所が3つも発生しました。このようにプログラムの実装が密結合になると修正箇所があちこちに発生します。
Interfaceを使った場合
Interfaceを賢く使うと先ほどの密結合を避けることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package main import "log" // インターフェースを定義 type DBInterface interface { Get() Delete() } type DBInfo3rdParty struct { username string password string } type DBHandler struct { DBInfo3rdParty } func DBUseInterface(d DBInterface) { // DBを操作する d.Get() d.Delete() } // データベースからデータを取り出す func (d *DBHandler) Get() { log.Println("Get Process") } // データベースからデータを削除する func (d *DBHandler) Delete() { log.Println("Delete Process") } func main() { db := &DBHandler{ DBInfo3rdParty{ username: "user", password: "pass", }, } DBUseInterface(db) } |
違いがお分かりでしょうか。
まず、DBInterfaceを定義しています。このInterfaceには、GetとDelete関数を定義しており、DBHandlerをインスタンス化することで暗黙的にこのInterfaceを実装することができます。なぜなら以下のメソッドを定義しているからです。
1 2 |
func (d *DBHandler) Get() {} func (d *DBHandler) Delete() {} |
Go言語では、Interfaceに定義した関数と同じシグネチャのメソッドを実装することでInterfaceを実装したことにしてくれます(そういう仕様です)。JavaやC#ではimplementsキーワードなどが必要ですよね。
main関数内でDBHandlerをインスタンス化して、DBUseInterface関数に渡しています。DBUseInterfaceの引数をよく見て欲しいのですが、DBInterface Type(Interface)が定義されています。
これが密結合を避ける秘訣です。
先ほどと同じようにDBInfo3rdPartyに修正を加えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
type DBInfo3rdParty struct { username string password string text string // textが追加される } ... func main() { db := &DBHandler{ DBInfo3rdParty{ username: "user", password: "pass", text: "ok", // 修正1: textを追加する }, } DBUseInterface(db) } |
いかがでしょうか?今度は、修正箇所はたった一箇所です。
Interfaceを引数に渡すメリットがお分りいただけたのではないでしょうか?
おわりに
今回のサンプルプログラムの作りはイマイチだったかもしれません。しかし、Interfaceを使って密結合を避けることの利便性はお分りいただけたのではないでしょうか?
これからもガンガンInterfaceを使っていきましょう!
それでは、また!
最近のコメント