質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

Q&A

解決済

1回答

2006閲覧

Go言語でのオブジェクト指向プログラミングのinterfaceの使い方がわかりません

退会済みユーザー

退会済みユーザー

総合スコア0

Go

Go(golang)は、Googleで開発されたオープンソースのプログラミング言語です。

0グッド

1クリップ

投稿2018/10/27 17:18

編集2018/10/28 05:23

Go言語でシンプルなObserverパターンのコードを書いてみました。

go

1package main 2 3import "fmt" 4 5// 具象クラス Subject 6// ======================= 7 8type Subject struct { 9 name string 10 observers []Observer 11} 12 13func newSubject(name string) *Subject { 14 return &Subject{name: name, observers: []Observer{}} 15} 16 17func (s *Subject) addObserver(o Observer) { 18 s.observers = append(s.observers, o) 19} 20 21func (s *Subject) notifyObservers() { 22 for _, observer := range s.observers { 23 observer.notify() 24 } 25} 26 27// 具象クラス Observer 28// ======================= 29 30type Notify interface { 31 notify() 32} 33 34type Observer struct { 35 name string 36} 37 38func newObserver(name string) *Observer { 39 return &Observer{name: name} 40} 41 42func (o *Observer) notify() { 43 fmt.Printf("hello! i am %v\n", o.name) 44} 45 46// main 47// ======================= 48 49func main() { 50 s := newSubject("sms") 51 mom := newObserver("mom") 52 gf := newObserver("girlFriend") 53 54 s.addObserver(*mom) 55 s.addObserver(*gf) 56 57 s.notifyObservers() 58} 59

上のコードは一応動くのですが、一つinterfaceの実装の方法がわからないことによる問題が生じています。(上のコードでは宣言したNotifyはどこにも使われていません)
構造体SubjectのもつnotifyObservers()メソッドの中で、プロパティobserversが持つnotify()メソッドを実行している部分があります。

Goではない、例えばTypeScript等の場合は、Observerクラスを作るときに、「nameプロパティ」と「notifyメソッド」をもつinterfaceをimplementsすることで、Observerクラスのインスタンスはこれら2つを持っていることがわかります。

typescript

1interface Observer { 2 name: string; 3 notify: () => string 4}

ですが、Goの場合、struct内に定義できるのはプロパティのみなので、これがnotify()メソッドを持っていることは保証できません。

イメージ的には、上のTypeScriptと同じように以下のように書きたいのですが、文法上それはできません。

Go

1type Observer struct { 2 name string 3 notify() 4}

このように、あるクラスに対してこのメソッドを絶対持たせたいことを宣言するために、Goではどのように書くのでしょうか。

どなたかご存知の方がいらっしゃればご教示願えませんでしょうか。
よろしくおねがいします。

【追記】
少しひらめきました。
以下のように書くというのはどうでしょうか。
Notifyインターフェースのみを受け付けるnObserver関数を別に用意し、それをfor文の中で実行しています。

go

1// 略 2 3func (s Subject) notifyObservers() { 4 for _, observer := range s.observers { 5 nObserver(observer) 6 } 7} 8 9func nObserver(n Notify) { 10 n.notify() 11} 12 13// 具象クラス Observer 14// ======================= 15 16type Notify interface { 17 notify() 18} 19 20// 略

【追記の追記】
nobonoboさんのヒントを元に再度修正してみました。
(最初の質問時のコードと整合性を取るために付け足しコードになっており、一部命名がおかしいと感じながらもそのままにしている部分があります)

「Observerはnotify()メソッドを持ってさえいればいい」。
なので、Subject構造体のプロパティobserversの型は、Observer構造体ではなく、Notifyインターフェースであれば良いということでしょうか

go

1type Subject struct { 2 name string 3 observers []Notify // ココ 4} 5 6func newSubject(name string) *Subject { 7 return &Subject{name: name, observers: []Notify{}} // ココ 8} 9 10func (s *Subject) addObserver(o Notify) { // ココ 11 s.observers = append(s.observers, o) 12} 13 14func (s Subject) notifyObservers() { 15 for _, observer := range s.observers { 16 observer.notify() 17 } 18} 19 20// func nObserver(n Notify) { 21// n.notify() 22// } 23 24// 具象クラス Observer 25// ======================= 26 27type Notify interface { 28 notify() 29}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

TypeScriptとは違いメソッド名の先頭が小文字なのはパッケージを超えて参照できないので注意してください。(一般にGoでインターフェース定義を書くときは先頭を大文字にする)

Goでは構造体がメソッドを持つかどうかはメソッド定義があるかどうかでstruct宣言の中にメソッドに関する記述はできません。

また、インターフェースは逆でメソッドしか記述できずプロパティを扱うことはできません。
(もしプロパティを扱いたい場合はGetterやSetterを書きます)

下記のようにすれば「ある構造体」が特定のインターフェースを実装済みかどうかをチェックすることができます。

go

1type Notifier interface { 2 Notify() 3} 4 5type Observer struct{} 6 7func (s *Observer) Notify() { 8} 9 10var _ Notifier = (*Observer)(nil) // ここでチェック

上記では構造体ObserverがNotifyというメソッドを持つことをチェックします。

ただし、Goの実際のコードに上記のようなチェックをしているところは少ないです。
Goのよくある実装では

  • Notifyメソッドをもつオブジェクトを期待する側がNotifierインターフェース宣言をする
  • 期待する側ではNotifierインターフェース型を引数に持つのでそれを利用する実装を書いた時点でインターフェースを満たしたかどうかがコンパイル時にチェックされるのでチェックのためだけの記述が不要

投稿2018/10/27 23:32

編集2018/10/27 23:46
nobonobo

総合スコア3367

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/10/28 04:30

回答ありがとうございます。 var _ Notifier = (*Observer)(nil) // ここでチェック こんな書き方があるんですね。知りませんでした。 お手数おかけして申し訳ないのですが、 > Goのよくある実装では 以下が言葉だけではよく理解できなかったので、簡単なコードを添えて頂けると嬉しいです。 質問文のコードの場合は、「Notifyメソッドをもつオブジェクトを期待する側」というのはSubject構造体んの方になるのですよね?
退会済みユーザー

退会済みユーザー

2018/10/28 04:50

ちょっとひらめいたのですが、これはまた違いますか?(コードがあるので質問文に追記ししました)
nobonobo

2018/10/28 05:02

そうですね。そのひらめきは少しGoらしくなる方向です。もう一つ踏み込めばobserversの各要素に必要なのがnotifyメソッドさえあれば良いという事に気づけば?
退会済みユーザー

退会済みユーザー

2018/10/28 05:11

あ!!!!先日nobonoboさんに回答して頂いたコードを見て、さらにひらめきました(あちらは、少しレベルが高かったので段階を踏もうと思いまだベストアンサーをつけていません。すみません。) 質問文にさらに【追記の追記】に追記しました。
nobonobo

2018/10/28 07:12

素晴らしい。正しいダックタイプになりました。 そしてaddObserverなどでNotifyインターフェースを引数に持つ事でそこに渡される構造体がnotifyメソッドを持っていない場合はコンパイルエラーになります。つまりnotifyメソッドを持つことが保証されます。 インターフェースを満たすかどうかのチェックのみを別途する必要は無いのです(より緩いインターフェース型を経由しない場合に限り)
退会済みユーザー

退会済みユーザー

2018/10/28 07:23

大変ありがとうございました。 良い学びができました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問