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

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

ただいまの
回答率

90.45%

  • Go

    659questions

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

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 537

cloudspider

score 80

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

package main

import "fmt"

// 具象クラス Subject
// =======================

type Subject struct {
    name      string
    observers []Observer
}

func newSubject(name string) *Subject {
    return &Subject{name: name, observers: []Observer{}}
}

func (s *Subject) addObserver(o Observer) {
    s.observers = append(s.observers, o)
}

func (s *Subject) notifyObservers() {
    for _, observer := range s.observers {
        observer.notify()
    }
}

// 具象クラス Observer
// =======================

type Notify interface {
    notify()
}

type Observer struct {
    name string
}

func newObserver(name string) *Observer {
    return &Observer{name: name}
}

func (o *Observer) notify() {
    fmt.Printf("hello! i am %v\n", o.name)
}

// main
// =======================

func main() {
    s := newSubject("sms")
    mom := newObserver("mom")
    gf := newObserver("girlFriend")

    s.addObserver(*mom)
    s.addObserver(*gf)

    s.notifyObservers()
}

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

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

interface Observer {
  name: string;
  notify: () => string
}

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

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

type Observer struct {
    name string
    notify()
}

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

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

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

// 略

func (s Subject) notifyObservers() {
    for _, observer := range s.observers {
        nObserver(observer)
    }
}

func nObserver(n Notify) {
    n.notify()
}

// 具象クラス Observer
// =======================

type Notify interface {
    notify()
}

// 略

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

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

type Subject struct {
    name      string
    observers []Notify // ココ
}

func newSubject(name string) *Subject {
    return &Subject{name: name, observers: []Notify{}} // ココ
}

func (s *Subject) addObserver(o Notify) { // ココ
    s.observers = append(s.observers, o)
}

func (s Subject) notifyObservers() {
    for _, observer := range s.observers {
        observer.notify()
    }
}

// func nObserver(n Notify) {
//     n.notify()
// }

// 具象クラス Observer
// =======================

type Notify interface {
    notify()
}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

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

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

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

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

type Notifier interface {
    Notify()
}

type Observer struct{}

func (s *Observer) Notify() {
}

var _ Notifier = (*Observer)(nil) // ここでチェック


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

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/28 13:30

    回答ありがとうございます。

    var _ Notifier = (*Observer)(nil) // ここでチェック
    こんな書き方があるんですね。知りませんでした。

    お手数おかけして申し訳ないのですが、
    > Goのよくある実装では
    以下が言葉だけではよく理解できなかったので、簡単なコードを添えて頂けると嬉しいです。

    質問文のコードの場合は、「Notifyメソッドをもつオブジェクトを期待する側」というのはSubject構造体んの方になるのですよね?

    キャンセル

  • 2018/10/28 13:50

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

    キャンセル

  • 2018/10/28 14:02

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

    キャンセル

  • 2018/10/28 14:11

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

    質問文にさらに【追記の追記】に追記しました。

    キャンセル

  • 2018/10/28 16:12

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

    キャンセル

  • 2018/10/28 16:23

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

    キャンセル

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

  • ただいまの回答率 90.45%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Go

    659questions

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