Go言語 暗黙的なinterfaceのメリット

Go言語の何がいいかって、色々な部分で実践的な感じがする（唐突）
その代表例として今回はGo言語の暗黙的なインターフェースの何がいいかコードを例に説明したい

まず初めにGoのインターフェースの例を示します

```go
type Writer interface {
	Write(data string) error
}

type File struct {
	Name string
}

func (f *File) Write(data string) error {
	file, _ := os.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE, 0644)
	defer file.Close()
	file.WriteString(data)
	return nil
}
```

これで`File`構造体（クラスみたいなイメージ）は`Writer`インターフェースを実装していることになります
JavaやTypeScriptと違って明示的にimplementsする必要はないんですね
これの何がいいんかって話です
## Javaでの実装
今回は`File`クラスを例に考えていきます
まず一般的な静的型付け言語の代表としてJavaから
`File`のインターフェースを定義します

```java
interface FileInterface {
    void write(String data) throws IOException;
    String read() throws IOException;
}
```

このインターフェースを実装した`File`クラスを作ります

```java
class File implements FileInterface {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void write(String data) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(this.name)) {
            fos.write(data.getBytes());
        }
    }

    @Override
    public String read() throws IOException {
        try (FileInputStream fis = new FileInputStream(this.name)) {
            byte[] buf = new byte[1024];
            int n = fis.read(buf);
            return new String(buf, 0, n);
        }
    }
}
```

ファイルクラスはインターフェースを実装しているので、使用者側から具体的な実装を隠蔽することができます
実際に↓で使用してみます

```java
public class Main {
    public static void main(String[] args) {
        try {
            FileInterface file = new File("test.txt");
            
            file.write("Hello World");
            String data = file.read();
            System.out.println(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```
`FileInterface`を実装しているので`File`クラスの実装を追わなくても`file`がString型を引数とする`write`メソッドを持っていることがわかります
ここまでは普通のクラスとインターフェースの使い方だと思いますし何の問題もありません
## インターフェースの分離原則
唐突ですがここで`Write`メソッドだけ使いたい人が現れます

```java
public class Main {
    public void saveTask(String task) {
        try {
            FileInterface file = new File("store.txt");
            file.write("todo: " + task);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

これも問題なく使えるはずです
しかし、要件が変わり新しい`CSVFile`クラスを`saveTask`で用いることになりました

```java
class CSVFile implements FileInterface {
    private String name;

    public CSVFile(String name) {
        this.name = name;
    }

    @Override
    public void write(String data) throws IOException {
        // CSVファイルに書き込み
    }
    
    // readメソッドは使わないから実装したくない…
}
```

CSVFileはsaveTaskなどの書き込み専用メソッドでしか呼ばれない予定です
なので↑のようにwriteメソッドだけ実装したいのですがFileInterfaceに依存しているため、使わないのにreadメソッドも定義しなければなりません

こんなことが起きてしまうのはそもそも`FileInterface`が**インターフェースの分離原則**（Interface Segregation Principle, ISP）に違反しているからです
インターフェースも単一責任であるべきでした

では上記を防ぐためにFileInterfaceとFileクラスを定義しなおしましょう

```java
interface Reader {
    String read() throws IOException;
}

interface Writer {
    void write(String data) throws IOException;
}

class File implements Reader, Writer {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void write(String data) throws IOException {
        try (FileOutputStream fos = new FileOutputStream(this.name)) {
            fos.write(data.getBytes());
        }
    }

    @Override
    public String read() throws IOException {
        try (FileInputStream fis = new FileInputStream(this.name)) {
            byte[] buf = new byte[1024];
            int n = fis.read(buf);
            return new String(buf, 0, n);
        }
    }
}
```

↑のようにすることで書き込み専用のメソッドがあったとしてもreadメソッドに依存しなくすることができました

実際に使用してみます

```java
// Writerインターフェースを実装しているクラスをDI
class WriterService {
    private Writer writer;

    public WriterService(Writer writer) {
        this.writer = writer;
    }

    public void write(String data) throws IOException {
        writer.write(data);
    }
}

public class Main {
    public static void main(String[] args) {
        File file = new File("example.txt");

        WriterService writerService = new WriterService(file);
        try {
            writerService.write("Hello, World!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

これにより、新しく`File`クラスを書き込み専用の`CSVFile`クラスに変更することになっても`CSVFile` クラスは`Writer`インターフェースを実装するだけで済みます（readメソッドを実装しなくていい）

これで一件落着に見えますが、新たな問題があります
`File`クラスが実装するインターフェースが二つに増えてしまっています…😭

今はまだ2つなのでさほど問題に思えないかもしれませんが、もし今後`Closer`, `Seeker`, `Flusher` など大量に出てくると管理が大変になってきます
このようにJavaやTSなどの明示的なインターフェースだとクラスもインターフェースの情報を持つ必要があります（これが悪いことと言いたいわけではないです）

## Goでの実装

ではいよいよGo言語で上記を実装してみましょう
まずはインターフェースから

```go
type Writer interface {
	Write(data string) error
}

type Reader interface {
	Read() (string, error)
}
```

次に`File` 構造体

```go
type File struct {
	Name string
}

func (f *File) Write(data string) error {
	file, _ := os.OpenFile(f.Name, os.O_WRONLY|os.O_CREATE, 0644)
	defer file.Close()
	file.WriteString(data)
	return nil
}

func (f *File) Read() (string, error) {
	file, _ := os.OpenFile(f.Name, os.O_RDONLY, 0644)
	defer file.Close()
	buf := make([]byte, 1024)
	n, _ := file.Read(buf)
	return string(buf[:n]), nil
}
```

これで`File` 構造体は`Writer` ,`Reader` インターフェースを満たすようになりました
※Go言語では構造体に紐づくメソッドは`func (h *Hoge) someMethod() any {}` のように書きます

実際に使用してみます

```go
type WriterService struct {
	Writer Writer
}

func (w *WriterService) Write(data string) error {
	return w.Writer.Write(data)
}

type ReaderService struct {
	Reader Reader
}

func (r *ReaderService) Read() (string, error) {
	return r.Reader.Read()
}

func main() {
	file := &File{Name: "test.txt"}
	writer := &WriterService{Writer: file}
	writer.Write("Hello World")

	reader := &ReaderService{Reader: file}
	data, _ := reader.Read()
	println(data)
}
```

先ほどのJavaで実装したときの問題が改善されました
暗黙的にインターフェースを実装しているおかげで`File` 構造体は何個インターフェースを満たしていようと気にする必要はないのです

このおかげでGoでは自由に柔軟なDIがフレームワークに頼らずに実現できます

## まとめ
明示的なインターフェースにも明確さがあるし、[ダックタイピング](https://qiita.com/shimgo/items/9d9fbab1e3a7c4343f7b)にも柔軟さがあるしとどれが良いと決めるのは難しいかもしれません
けれどGoが謳う実践的な設計はこういった部分にあって、そこがとても良いと私は感じましたのでその紹介でした
そんなことねーだろという反論や意見はどしどし募集中です
おしまい