Slide 1

Slide 1 text

明示と暗黙 ー PHPとGoの インターフェイスの違いを知る 2025/06/28 PHP Conference Japan 2025 しまぶ@shimabox

Slide 2

Slide 2 text

NAME: "しまぶ" SNS: "@shimabox" TAMASHII: "沖縄" COMPANY: "カオナビ" SKILL: - "PHP" - "Go" KAMATA: "はじめて喋る" whoami.yml 2 自己紹介

Slide 3

Slide 3 text

3 📖 共著で本を書きました 📖

Slide 4

Slide 4 text

4 📖 共著で本を書きました 📖

Slide 5

Slide 5 text

5 📖 共著で本を書きました 📖 心はPHPer、身体はGopher

Slide 6

Slide 6 text

6 私がGoを書き始めて最も衝撃を受けたこと そんな私がGoを書き始めて 最も衝撃を受けたのが

Slide 7

Slide 7 text

7 私がGoを書き始めて最も衝撃を受けたこと 暗黙のインターフェイス

Slide 8

Slide 8 text

// カスタムエラー型を定義 type ValidationError struct { Field string Message string } // Error() メソッドを実装するだけで... func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // errorインターフェースとして使える!※errorインターフェースは、Goの組み込み型で定義されている func validateAge(age int) error { if age < 0 { return ValidationError{ Field: "age", Message: "年齢は0以上である必要があります", } } return nil } 8 私がGoを書き始めて最も衝撃を受けたこと

Slide 9

Slide 9 text

// カスタムエラー型を定義 type ValidationError struct { Field string Message string } // Error() メソッドを実装するだけで... func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // errorインターフェースとして使える!※errorインターフェースは、Goの組み込み型(built-in type)で定義 func validateAge(age int) error { if age < 0 { return ValidationError{ Field: "age", Message: "年齢は0以上である必要があります", } } return nil } 9 私がGoを書き始めて最も衝撃を受けたこと type error interface { Error() string }

Slide 10

Slide 10 text

// カスタムエラー型を定義 type ValidationError struct { Field string Message string } // Error() メソッドを実装するだけで... func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // errorインターフェースとして使える!※errorインターフェースは、Goの組み込み型(built-in type)で定義 func validateAge(age int) error { if age < 0 { return ValidationError{ Field: "age", Message: "年齢は0以上である必要があります", } } return nil } 10 私がGoを書き始めて最も衝撃を受けたこと 「implementsって 書いてないのに、 なぜerrorとして 扱える...?」

Slide 11

Slide 11 text

// カスタムエラー型を定義 type ValidationError struct { Field string Message string } // Error() メソッドを実装するだけで... func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // errorインターフェースとして使える!※errorインターフェースは、Goの組み込み型(built-in type)で定義 func validateAge(age int) error { if age < 0 { return ValidationError{ Field: "age", Message: "年齢は0以上である必要があります", } } return nil } 11 私がGoを書き始めて最も衝撃を受けたこと 今回はこの 「暗黙のインターフェイス」 について主に見ていきます ⚠⚠⚠Goのコード多めです⚠⚠⚠

Slide 12

Slide 12 text

1. PHPとGoのインターフェイス 2. Goのインターフェイスについて 3. サンプルを交えてGoのインターフェイスを知る 4. アプリの実装を見て深ぼる 5. まとめ 12 アジェンダ

Slide 13

Slide 13 text

13 アジェンダ 1. PHPとGoのインターフェイス 2. Goのインターフェイスについて 3. サンプルを交えてGoのインターフェイスを知る 4. アプリの実装を見て深ぼる 5. まとめ

Slide 14

Slide 14 text

interface LoggerInterface { public function log(string $message): void; } class FileLogger implements LoggerInterface { public function log(string $message): void { echo 'Logging to file: ' . $message; } } function logMessage(LoggerInterface $logger) { $logger->log('Hello, PHP!'); } logMessage(new FileLogger()); 14 まず、PHPのインターフェイス

Slide 15

Slide 15 text

interface LoggerInterface { public function log(string $message): void; } class FileLogger implements LoggerInterface { public function log(string $message): void { echo 'Logging to file: ' . $message; } } function logMessage(LoggerInterface $logger) { $logger->log('Hello, PHP!'); } logMessage(new FileLogger()); 15 まず、PHPのインターフェイス インターフェイスの 定義

Slide 16

Slide 16 text

interface LoggerInterface { public function log(string $message): void; } class FileLogger implements LoggerInterface { public function log(string $message): void { echo 'Logging to file: ' . $message; } } function logMessage(LoggerInterface $logger) { $logger->log('Hello, PHP!'); } logMessage(new FileLogger()); 16 まず、PHPのインターフェイス インターフェイスの 実装

Slide 17

Slide 17 text

interface LoggerInterface { public function log(string $message): void; } class FileLogger implements LoggerInterface { public function log(string $message): void { echo 'Logging to file: ' . $message; } } function logMessage(LoggerInterface $logger) { $logger->log('Hello, PHP!'); } logMessage(new FileLogger()); 17 まず、PHPのインターフェイス インターフェイスに 依存

Slide 18

Slide 18 text

interface LoggerInterface { public function log(string $message): void; } class FileLogger implements LoggerInterface { public function log(string $message): void { echo 'Logging to file: ' . $message; } } function logMessage(LoggerInterface $logger) { $logger->log('Hello, PHP!'); } logMessage(new FileLogger()); 18 まず、PHPのインターフェイス LoggerInterfaceを 実装しているもので あれば注入できる

Slide 19

Slide 19 text

明示的な実装 ● `implements` で明示的にインターフェイスを実装 ● どのクラスがどのインターフェイスを実装している か明確 19 まず、PHPのインターフェイス

Slide 20

Slide 20 text

type Logger interface { Log(message string) } type FileLogger struct{} func (f FileLogger) Log(message string) { fmt.Println("Logging to file:", message) } func logMessage(logger Logger) { logger.Log("Hello, Go!") } func main() { logMessage(FileLogger{}) } 20 つづいて、Goのインターフェイス

Slide 21

Slide 21 text

type Logger interface { Log(message string) } type FileLogger struct{} func (f FileLogger) Log(message string) { fmt.Println("Logging to file:", message) } func logMessage(logger Logger) { logger.Log("Hello, Go!") } func main() { logMessage(FileLogger{}) } 21 つづいて、Goのインターフェイス インターフェイスの 定義

Slide 22

Slide 22 text

type Logger interface { Log(message string) } type FileLogger struct{} func (f FileLogger) Log(message string) { fmt.Println("Logging to file:", message) } func logMessage(logger Logger) { logger.Log("Hello, Go!") } func main() { logMessage(FileLogger{}) } 22 つづいて、Goのインターフェイス FileLogger構造体

Slide 23

Slide 23 text

type Logger interface { Log(message string) } type FileLogger struct{} func (f FileLogger) Log(message string) { fmt.Println("Logging to file:", message) } func logMessage(logger Logger) { logger.Log("Hello, Go!") } func main() { logMessage(FileLogger{}) } 23 つづいて、Goのインターフェイス FileLoggerは インターフェイスを 満たしている

Slide 24

Slide 24 text

type Logger interface { Log(message string) } type FileLogger struct{} func (f FileLogger) Log(message string) { fmt.Println("Logging to file:", message) } func logMessage(logger Logger) { logger.Log("Hello, Go!") } func main() { logMessage(FileLogger{}) } 24 つづいて、Goのインターフェイス インターフェイスに 依存

Slide 25

Slide 25 text

type Logger interface { Log(message string) } type FileLogger struct{} func (f FileLogger) Log(message string) { fmt.Println("Logging to file:", message) } func logMessage(logger Logger) { logger.Log("Hello, Go!") } func main() { logMessage(FileLogger{}) } 25 つづいて、Goのインターフェイス Logger(Interface) を満たしているもの であれば注入できる

Slide 26

Slide 26 text

暗黙的な実装 ● 構造体(型)がインターフェイスで宣言されたメソッドを 持っていれば、インターフェイスを「暗黙的に」実装し ているとみなされる ● インターフェイスで宣言されたメソッドを持っている 26 つづいて、Goのインターフェイス

Slide 27

Slide 27 text

暗黙的な実装 ● 構造体(型)がインターフェイスで宣言されたメソッドを 持っていれば、インターフェイスを「暗黙的に」実装し ているとみなされる ● インターフェイスで宣言されたメソッドを持っている ↓ インターフェイスを満たす 27 つづいて、Goのインターフェイス

Slide 28

Slide 28 text

暗黙的な実装 ● 構造体(型)がインターフェイスで宣言されたメソッドを 持っていれば、インターフェイスを「暗黙的に」実装し ているとみなされる ● インターフェイスで宣言されたメソッドを持っている ↓ インターフェイスを満たす 28 つづいて、Goのインターフェイス インターフェイスを 満たすとは???

Slide 29

Slide 29 text

29 アジェンダ 1. PHPとGoのインターフェイス 2. Goのインターフェイスについて 3. サンプルを交えてGoのインターフェイスを知る 4. アプリの実装を見て深ぼる 5. まとめ

Slide 30

Slide 30 text

30 Goのインターフェイス ● 明示的に`implements`を宣言しない ● 必要なメソッドを持っていればインターフェイスを実装し ているとみなされる ● 「アヒルのように歩き、アヒルのように鳴くものは、アヒ ルだろう」→ ダックタイピング 🦆 ※ Goは静的型付け言語なので、厳密には 「構造的型付け(Structural Typing)」です Goのインターフェイスはダックタイピング

Slide 31

Slide 31 text

31 Goのインターフェイス Goのインターフェイスはダックタイピング 型そのものがインターフェイスを実装してい ることを明示しなくても良い設計を採用して いる ● Go言語プログラミングエッセンス ○ https://gihyo.jp/book/2023/978-4-297-13419-8 ○ P47, P68 あたり

Slide 32

Slide 32 text

32 Goのインターフェイス インターフェイスを満たすとは??? 型がインターフェイスで要求されている メソッドをすべて実装していること

Slide 33

Slide 33 text

// カスタムエラー型を定義 type ValidationError struct { Field string Message string } // Error() メソッドを実装するだけで... func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // errorインターフェースとして使える!※errorインターフェースは、Goの組み込み型(built-in type)で定義 func validateAge(age int) error { if age < 0 { return ValidationError{ Field: "age", Message: "年齢は0以上である必要があります", } } return nil } 33 【再掲】私がGoを書き始めて最も衝撃を受けたこと 「implementsって 書いてないのに、 なぜerrorとして 扱える...?」

Slide 34

Slide 34 text

// カスタムエラー型を定義 type ValidationError struct { Field string Message string } // Error() メソッドを実装するだけで... func (e ValidationError) Error() string { return fmt.Sprintf("%s: %s", e.Field, e.Message) } // errorインターフェースとして使える!※errorインターフェースは、Goの組み込み型(built-in type)で定義 func validateAge(age int) error { if age < 0 { return ValidationError{ Field: "age", Message: "年齢は0以上である必要があります", } } return nil } 34 【再掲】私がGoを書き始めて最も衝撃を受けたこと type error interface { Error() string } を実装しているので、 errorインターフェース として扱える

Slide 35

Slide 35 text

35 アジェンダ 1. PHPとGoのインターフェイス 2. Goのインターフェイスについて 3. サンプルを交えてGoのインターフェイスを知る 4. アプリの実装を見て深ぼる 5. まとめ

Slide 36

Slide 36 text

type Speaker interface { Speak() } // Dog構造体 type Dog struct{} func (d Dog) Speak() { fmt.Println("I am a dog.") } // Robot構造体 type Robot struct{} func (r Robot) Speak() { fmt.Println("I am a robot.") } func introduce(s Speaker) { s.Speak() } func main() { // Dog, Robot はSpeakerインターフェイスを満たす d := Dog{} r := Robot{} // 振る舞いを切り替えられる introduce(d) // I am a dog. introduce(r) // I am a robot. } 36 インターフェイスの利点 == 振る舞いを切り替えられる

Slide 37

Slide 37 text

// Speaker インターフェイス interface Speaker { public function speak(): void; } // Dog クラス class Dog implements Speaker { public function speak(): void { echo 'I am a dog.' . PHP_EOL; } } // Robot クラス class Robot implements Speaker { public function speak(): void { echo 'I am a robot.' . PHP_EOL; } } function introduce(Speaker $speaker): void { $speaker->speak(); } // メイン処理 $dog = new Dog(); $robot = new Robot(); introduce($dog); // I am a dog. introduce($robot); // I am a robot. 37 PHPだとこう

Slide 38

Slide 38 text

// Speakerインターフェイスを定義 type Speaker interface { Speak() } // Person構造体を定義 type Person struct{} // PersonにSpeakメソッドを追加 func (p Person) Speak() { fmt.Println("Hello, I am a person.") } func introduce(s Speaker) { s.Speak() } func main() { p := Person{} // Person構造体はSpeakメソッドがあるので、Speakerイ ンターフェイスを満たす introduce(p) } ● Person構造体はSpeakerインター フェイスのSpeakメソッドを実装し ている ○ 暗黙的(自動的)にSpeakerイン ターフェイスを満たす ● インターフェイス型への代入 ○ introduce関数はSpeaker型の引 数を受け取るため、Speakerイン ターフェイスを満たすPerson構 造体を引数として渡せる 38 構造体がインターフェイスを満たす

Slide 39

Slide 39 text

// Formatter インターフェイス type Formatter interface { Format() string } // カスタム型 CustomString を定義 type CustomString string // ❌ 型エイリアス(型は変わらないのでメソッドを定義でき ない) // type CustomString = string // Format メソッドを定義(Formatter を満たす) func (s CustomString) Format() string { return strings.ToUpper(string(s)) } func main() { var str Formatter = CustomString("hello, world") fmt.Println(str.Format()) // HELLO, WORLD } ● Goは基本型(string, int, bool など) や、複合型(slice, map, chan, function など)に対して型を別で定 義できる ○ カスタム型 ○ type CustomString string ■ ⚠ 型エイリアスとは違うよ ■ type CustomString = string ● 基本型や複合型を基にしたカスタム 型を作成し、そのカスタム型にメ ソッドを追加することでインター フェースを満たすことができる 39 カスタム型がインターフェイスを満たす

Slide 40

Slide 40 text

// Formatter インターフェイス type Formatter interface { Format() string } // カスタム型 CustomString を定義 type CustomString string // ❌ 型エイリアス(型は変わらないのでメソッドを定義でき ない) // type CustomString = string // Format メソッドを定義(Formatter を満たす) func (s CustomString) Format() string { return strings.ToUpper(string(s)) } func main() { var str Formatter = CustomString("hello, world") fmt.Println(str.Format()) // HELLO, WORLD } ● Goは基本型(string, int, bool など) や、複合型(slice, map, chan, function など)に対して型を別で定 義できる ○ カスタム型 ○ type CustomString string ■ ⚠ 型エイリアスとは違うよ ■ type CustomString = string ● 基本型や複合型を基にしたカスタム 型を作成し、そのカスタム型にメ ソッドを追加することでインター フェースを満たすことができる 40 カスタム型がインターフェイスを満たす PHPにはない 便利だ

Slide 41

Slide 41 text

// Reader インターフェイス type Reader interface { Read() string } // Writer インターフェイス type Writer interface { Write(data string) } // ReaderとWriterを埋め込み // ReadWriterインターフェイスを定義 type ReadWriter interface { Reader Writer } 41 埋め込み ● インターフェイスを埋め 込んで新たなインター フェイスを定義できる ● コンポジション(合成)

Slide 42

Slide 42 text

// Reader インターフェイス type Reader interface { Read() string } // Writer インターフェイス type Writer interface { Write(data string) } // ReaderとWriterを埋め込み // ReadWriterインターフェイスを定義 type ReadWriter interface { Reader Writer } // FileHandler 構造体(ReaderとWriterを実装) type FileHandler struct { content string } func (f *FileHandler) Read() string { return f.content } func (f *FileHandler) Write(data string) { f.content = data } // ReadWriter インターフェイスを引数に取る関数 func handleReadWrite(rw ReadWriter) { // データを書き込む rw.Write("Hello, Go!") // データを読み込む fmt.Println("Read data:", rw.Read()) } func main() { file := &FileHandler{} // FileHandlerをReadWriterとして使用 handleReadWrite(file) // "Read data: Hello, Go!" } 42 埋め込み(利用例)

Slide 43

Slide 43 text

// Reader インターフェイス interface Reader { public function read(): string; } // Writer インターフェイス interface Writer { public function write(string $data): void; } // ReadWriterインターフェイスは // ReaderとWriterを継承 interface ReadWriter extends Reader, Writer {} 43 PHPだとこう ● PHPではextendsを用い てインターフェイスを継 承し、新たなインター フェイスを作成

Slide 44

Slide 44 text

// Reader インターフェイス interface Reader { public function read(): string; } // Writer インターフェイス interface Writer { public function write(string $data): void; } // ReadWriterインターフェイスはReaderとWriterを継承 interface ReadWriter extends Reader, Writer {} // FileHandlerクラス(ReadWriterインターフェイスを実装) class FileHandler implements ReadWriter { private string $content = ''; // Readerインターフェイスの実装 public function read(): string { return $this->content; } // Writerインターフェイスの実装 public function write(string $data): void { $this->content = $data; } } // ReadWriter インターフェイスを引数に取る関数 function handleReadWrite(ReadWriter $rw): void { // データを書き込む $rw->write("Hello, PHP!"); // データを読み込む echo "Read data: " . $rw->read() . PHP_EOL; } // メイン処理 $file = new FileHandler(); // FileHandlerをReadWriterとして使用 handleReadWrite($file); // "Read data: Hello, PHP!" 44 PHPだとこう(利用例)

Slide 45

Slide 45 text

func printAnything(v any) { fmt.Println(v) } func main() { printAnything(42) // 整数を出力 printAnything("Hello") // 文字列を出力 printAnything(3.14) // 浮動小数点を出力 printAnything(true) // ブール値を出力 } func main() { data := map[string]any{ "name": "Alice", "age": 25, "active": true, "scores": []int{85, 90, 78}, } // 値を動的に処理 for key, value := range data { fmt.Printf("Key: %s, Type: %T, Value: %v\n", key, value, value) } } Key: name, Type: string, Value: Alice Key: age, Type: int, Value: 25 Key: active, Type: bool, Value: true Key: scores, Type: []int, Value: [85 90 78] ● メソッドを一切持たないイン ターフェイス ● すべての型が暗黙的にこの空イ ンターフェイスを実装している 45 空(くう)インターフェイス any(interface{})

Slide 46

Slide 46 text

func printAnything(v any) { fmt.Println(v) } func main() { printAnything(42) // 整数を出力 printAnything("Hello") // 文字列を出力 printAnything(3.14) // 浮動小数点を出力 printAnything(true) // ブール値を出力 } func main() { data := map[string]any{ "name": "Alice", "age": 25, "active": true, "scores": []int{85, 90, 78}, } // 値を動的に処理 for key, value := range data { fmt.Printf("Key: %s, Type: %T, Value: %v\n", key, value, value) } } Key: name, Type: string, Value: Alice Key: age, Type: int, Value: 25 Key: active, Type: bool, Value: true Key: scores, Type: []int, Value: [85 90 78]46 空(くう)インターフェイス any(interface{}) ● メソッドを一切持たないイン ターフェイス ● すべての型が暗黙的にこの空イ ンターフェイスを実装している PHPだと mixed だな? object|resource|array|string|float|int|bool|null

Slide 47

Slide 47 text

● 型安全性が低下 ○ 空インターフェイスを多用するとGoの静的型付けの利点を失う ● 具体的な型を使える場合は使う ○ 本当に型を柔軟にする必要がある場合のみ使う ○ 可能であれば具体的な型や小さなインターフェイスを設計する方 が望ましい ○ それはそう 47 注意点 空インターフェイスを使う際の注意点

Slide 48

Slide 48 text

// TwoMethodsインターフェイスの定義(2つのメソッド が含まれている) type TwoMethods interface { MethodOne() MethodTwo() } // MethodOneのみを持つ(TwoMethodsには適合しない) type PartialOne struct{} func (p PartialOne) MethodOne() {} // MethodOneとMethodTwoを持つ(TwoMethodsに適合) type PartialTwo struct{} func (p PartialTwo) MethodOne() {} func (p PartialTwo) MethodTwo() {} // 3つのメソッドを持つが、TwoMethodsは満たしている type ExtraMethods struct{} func (e ExtraMethods) MethodOne() {} func (e ExtraMethods) MethodTwo() {} func (e ExtraMethods) MethodThree() {} // TwoMethodsインターフェイスを受け取る関数 func useTwoMethods(t TwoMethods) { t.MethodOne() t.MethodTwo() } func main() { // PartialOneはTwoMethodsを満たしていな いのでエラーとなる // useTwoMethods(PartialOne{}) useTwoMethods(PartialTwo{}) // OK useTwoMethods(ExtraMethods{}) // OK } 48 インターフェイスをさくっと満たせる

Slide 49

Slide 49 text

type TwoMethods interface { MethodOne() MethodTwo() } // MethodTwoを実装 type PartialOne struct{} func (p PartialOne) MethodOne() {} func (p PartialOne) MethodTwo() {} 49 TwoMethodsインターフェイスを満たすには MethodTwo() を実装するだけ

Slide 50

Slide 50 text

type TwoMethods interface { MethodOne() MethodTwo() } // MethodTwoを実装 type PartialOne struct{} func (p PartialOne) MethodOne() {} func (p PartialOne) MethodTwo() {} 50 TwoMethodsインターフェイスを満たすには MethodTwo() を実装するだけ インターフェイスをさくっと 満たせる

Slide 51

Slide 51 text

// TwoMethodsインターフェイスの定義(2つのメソッドを含む) interface TwoMethods { public function methodOne(): void; public function methodTwo(): void; } // methodOneのみを持つ //(TwoMethodsインターフェイスを実装しない, できない) class PartialOne { public function methodOne(): void {} } // methodOneとmethodTwoを持つ //(TwoMethodsインターフェイスを実装) class PartialTwo implements TwoMethods { public function methodOne(): void {} public function methodTwo(): void {} } // 3つのメソッドを持つが、TwoMethodsの2つを実装している class ExtraMethods implements TwoMethods { public function methodOne(): void {} public function methodTwo(): void {} public function methodThree(): void {} } // TwoMethodsインターフェイスを受け取る関数 function useTwoMethods(TwoMethods $t): void { $t->methodOne(); $t->methodTwo(); } // メイン処理 function main(): void { $partialOne = new PartialOne(); $partialTwo = new PartialTwo(); $extraMethods = new ExtraMethods(); // PartialOneはTwoMethodsを実装していな いのでエラー // useTwoMethods($partialOne); useTwoMethods($partialTwo); // OK useTwoMethods($extraMethods); // OK } main(); 51 PHPだとこう

Slide 52

Slide 52 text

interface TwoMethods { public function methodOne(): void; public function methodTwo(): void; } // methodOneとmethodTwoを実装 //(TwoMethodsインターフェイスを実装) class PartialOne implements TwoMethods { public function methodOne(): void {} public function methodTwo(): void {} } 52 TwoMethodsインターフェイスを実装するには methodTwo()を実装 してTwoMethodsを implementsする必 要がある

Slide 53

Slide 53 text

interface TwoMethods { public function methodOne(): void; public function methodTwo(): void; } // methodOneとmethodTwoを実装 //(TwoMethodsインターフェイスを実装) class PartialOne implements TwoMethods { public function methodOne(): void {} public function methodTwo(): void {} } 53 TwoMethodsインターフェイスを実装するには 手間はかかるが明示的

Slide 54

Slide 54 text

● PHP ○ implements を用いた明示的なインターフェイス実装 ○ 明確性? ● Go ○ ダックタイピングによる暗黙的なインターフェイス実装 ■ さくっと満たせる ○ 柔軟性? 54 いったんまとめ

Slide 55

Slide 55 text

55 アジェンダ 1. PHPとGoのインターフェイス 2. Goのインターフェイスについて 3. サンプルを交えてGoのインターフェイスを知る 4. アプリの実装を見て深ぼる 5. まとめ

Slide 56

Slide 56 text

● PHPとGoで同じ構成で用意し たサンプルアプリを見ていく ● Goに関してはPHPに合わせた パッケージ構成にしている ● インターフェイスの置き場所 に注目 ● ⚠ 唯一の正解ではない 56 DIPを使ったレイヤードアーキテクチャを例に見ていく

Slide 57

Slide 57 text

57 まずはPHP

Slide 58

Slide 58 text

id = $value; } } public private(set) string $name { set { if (empty($value)) throw new \InvalidArgumentException('名前は必須!!'); $this->name = $value; } } public function __construct(int $id, string $name) { $this->id = $id; $this->name = $name; } } 58 Domain/User.php

Slide 59

Slide 59 text

Slide 60

Slide 60 text

Slide 61

Slide 61 text

userRepository->getUserByID($userId); } } 61 UseCase/UserUseCase.php

Slide 62

Slide 62 text

handle(1); // ユーザーID 1 のUserを取得 62 index.php UserRepository (UserRepositoryInterface) を注入している

Slide 63

Slide 63 text

/ ├─ Domain │ ├─ User.php │ └─ UserRepositoryInterface.php ├─ Infrastructure │ └─ UserRepository.php ├─ UseCase │ └─ UserUseCase.php └─ index.php 63 Domain層にインターフェイスが置かれる

Slide 64

Slide 64 text

64 続いてGo

Slide 65

Slide 65 text

65 Domain層にインターフェイスを置くと思いきや ❌ ドメイン層に置かない ❌ ❌

Slide 66

Slide 66 text

66 じゃあどこに置くのか

Slide 67

Slide 67 text

67 ユースケース層(インターフェイスを満たした実装を使用する側)に置く ユースケース層に置く (使用する側に置く)

Slide 68

Slide 68 text

package domain import "fmt" type User struct { ID int Name string } func (u *User) String() string { return fmt.Sprintf("User{ID: %d, Name: %s}", u.ID, u.Name) } 68 domain/user.go

Slide 69

Slide 69 text

package infrastructure import ( "fmt" "user/domain" ) type UserRepository struct{} func (m UserRepository) GetUserByID(id int) (*domain.User, error) { fmt.Println("データベースからユーザーを取得します") return &domain.User{ID: id, Name: "User"}, nil } 69 infrastructure/user_repository.go

Slide 70

Slide 70 text

package usecase import "user/domain" type UserRepositoryInterface interface { GetUserByID(id int) (*domain.User, error) } type UserUseCase struct { repo UserRepositoryInterface } func NewUserUseCase(repo UserRepositoryInterface) *UserUseCase { return &UserUseCase{repo: repo} } func (u *UserUseCase) GetUser(id int) (*domain.User, error) { return u.repo.GetUserByID(id) } 70 usecase/user.go

Slide 71

Slide 71 text

package main import ( "fmt" "user/infrastructure" "user/usecase" ) func main() { repo := infrastructure.UserRepository{} uc := usecase.NewUserUseCase(&repo) user, err := uc.GetUser(1) if err != nil { fmt.Println("ユーザー取得中にエラーが発生しました:", err) return } fmt.Println(user.String()) } 71 main.go UserRepository (UserRepositoryInterface) を注入している

Slide 72

Slide 72 text

package infrastructure import ( "fmt" "user/domain" ) type UserRepository struct{} func (m UserRepository) GetUserByID(id int) (*domain.User, error) { fmt.Println("データベースからユーザーを取得します") return &domain.User{ID: id, Name: "User"}, nil } 72 Interfaceを満たしているか分かりづらい UserRepositoryInterfaceを 満たしているかどうか 分かりづらい

Slide 73

Slide 73 text

package infrastructure import ( "fmt" "user/domain" "user/usecase" ) // コンパイル時にインターフェースを満たしているかチェック var _ usecase.UserRepositoryInterface = (*UserRepository)(nil) type UserRepository struct{} func (m UserRepository) GetUserByID(id int) (*domain.User, error) { fmt.Println("データベースからユーザーを取得します") return &domain.User{ID: id, Name: "User"}, nil } 73 Interfaceを満たしているかどうか確認 こういう方法もある

Slide 74

Slide 74 text

74 ユースケース層(インターフェイスを満たした実装を使用する側)に置く なぜか

Slide 75

Slide 75 text

75 ユースケース層(インターフェイスを満たした実装を使用する側)に置く Userを登録するユースケース が出たときを考える

Slide 76

Slide 76 text

1. リポジトリにメソッドを実装 2. インターフェイスにメソッドを追加 3. ユースケースでインターフェイスを使用 4. テスト用のモックがあればそれも修正 76 PHPの場合 // 3. class RegisterUserUseCase { public function __construct( private UserRepositoryInterface $userRepository ){} public function handle(): void { $user = new User(1, 'taro'); $this->userRepository->save($user); } } // 2. interface UserRepositoryInterface { public function getUserByID(int $id): ?User; public function save(User $user): void; } // 1. class UserRepository implements UserRepositoryInterface { public function getUserByID(int $userId): User { return new User($userId, 'taro'); // 仮の実装 } public function save(User $user): void { // ここでユーザーを保存するロジックを実装 // 例えば、データベースにユーザー情報を保存するなど echo 'User saved: ' . $user->name; } }

Slide 77

Slide 77 text

1. リポジトリにメソッドを実装 2. インターフェイスにメソッドを追加 3. ユースケースでインターフェイスを使用 4. テスト用のモックがあればそれも修正 77 PHPの場合 // 3. class RegisterUserUseCase { public function __construct( private UserRepositoryInterface $userRepository ){} public function handle(): void { $user = new User(1, 'taro'); $this->userRepository->save($user); } } // 2. interface UserRepositoryInterface { public function getUserByID(int $id): ?User; public function save(User $user): void; } // 1. class UserRepository implements UserRepositoryInterface { public function getUserByID(int $userId): User { return new User($userId, 'taro'); // 仮の実装 } public function save(User $user): void { // ここでユーザーを保存するロジックを実装 // 例えば、データベースにユーザー情報を保存するなど echo 'User saved: ' . $user->name; } } そもそも最初から抽象化されてそう (インターフェイスにある程度メソッドがありそう)

Slide 78

Slide 78 text

78 PHPの場合 ソフトウェア開発におけるインターフェイスという考え方 / PHPerKaigi 2025 @k1low https://speakerdeck.com/k1low/phperkaigi-2025?slide=9 事前にインターフェイスが定義される (この感覚、あると思います)

Slide 79

Slide 79 text

1. リポジトリにメソッドを実装 2. ユースケースで必要なインターフェイスを定義して使用 3. 他の実装(モックなど)に影響がない // 1. package infrastructure import ( "fmt" "user/domain" ) type UserRepository struct{} func (m UserRepository) GetUserByID(id int) (*domain.User, error) { fmt.Println("データベースからユーザーを取得します") return &domain.User{ID: id, Name: "User"}, nil } func (m UserRepository) Save(user *domain.User) error { fmt.Println("データベースへユーザーを登録します") return nil } 79 Goの場合 // 2. package usecase import "user/domain" type RegisterUserInterface interface { Save(user *domain.User) error } type RegisterUserUseCase struct { repo RegisterUserInterface } func NewRegisterUserUseCase(repo RegisterUserInterface) *RegisterUserUseCase { return &RegisterUserUseCase{repo: repo} } func (u *RegisterUserUseCase) SaveUser(user *domain.User) error { return u.repo.Save(user) }

Slide 80

Slide 80 text

1. リポジトリにメソッドを実装 2. ユースケースで必要なインターフェイスを定義して使用 3. 他の実装(モックなど)に影響がない // 1. package infrastructure import ( "fmt" "user/domain" ) type UserRepository struct{} func (m UserRepository) GetUserByID(id int) (*domain.User, error) { fmt.Println("データベースからユーザーを取得します") return &domain.User{ID: id, Name: "User"}, nil } func (m UserRepository) Save(user *domain.User) error { fmt.Println("データベースへユーザーを登録します") return nil } 80 Goの場合 // 2. package usecase import "user/domain" type RegisterUserInterface interface { Save(user *domain.User) error } type RegisterUserUseCase struct { repo RegisterUserInterface } func NewRegisterUserUseCase(repo RegisterUserInterface) *RegisterUserUseCase { return &RegisterUserUseCase{repo: repo} } func (u *RegisterUserUseCase) SaveUser(user *domain.User) error { return u.repo.Save(user) } 使用する側がインターフェイスを定義する

Slide 81

Slide 81 text

PHPとGoのインターフェイスを定義する場所の考え方 81 ソフトウェア開発におけるインターフェイスという考え方 / PHPerKaigi 2025 @k1low https://speakerdeck.com/k1low/phperkaigi-2025?slide=22

Slide 82

Slide 82 text

● PHP ○ 提供する側がインターフェイスを定義する ○ 事前にインターフェイス(抽象)を考えがち ○ インターフェイスが先に明示されている感覚 ● Go ○ 使用する側がインターフェイスを定義する ○ 後からインターフェイス(抽象)を定義できる ○ インターフェイスをさくっと満たせる ↓ インターフェイスがさくっと定義できる 82 PHPとGoのインターフェイスを定義する場所の考え方

Slide 83

Slide 83 text

インターフェイスをさくっと満たせる ↓ インターフェイスがさくっと定義できる 83 Goのインターフェイスはさくっと定義できる

Slide 84

Slide 84 text

// 自分たちのコードで必要に応じてインターフェースを定義 package myservice // InfoとErrorのみ必要 type AppLogger interface { Info(msg string) Error(msg string) } type UserService struct { logger AppLogger } func NewUserService(logger AppLogger) *UserService { // vendor.FileLoggerは自動的にAppLoggerも満たす return &UserService{logger: logger} } // main.go fileLogger := vendor.FileLogger{} userService := myservice.NewUserService(fileLogger) 84 Goのインターフェイスはさくっと定義できる // サードパーティのロガー(修正不可) package vendor type FileLogger struct{} func (l FileLogger) Debug(msg string) { /* 実装 */ } func (l FileLogger) Info(msg string) { /* 実装 */ } func (l FileLogger) Error(msg string) { /* 実装 */ } func (l FileLogger) Fatal(msg string) { /* 実装 */ } ● 自分で定義したインターフェース でサードパーティーの実装を使え る ● PHPだとラッパークラスが必要

Slide 85

Slide 85 text

SOLID原則のI(Interface Segregation Principle) 85 インターフェイス分離の原則(ISP) ● クライアントに、クライアントが利用しないメソッドへの依存を 強制してはならない

Slide 86

Slide 86 text

SOLID原則のI(Interface Segregation Principle) 86 詳しくはこの本(9章)を読んでみよう

Slide 87

Slide 87 text

// Go type UserRepositoryInterface interface { GetUserByID(id int) (*domain.User, error) } type RegisterUserInterface interface { Save(user *domain.User) error } ● クライアントに必要なメソッドだけ を定義できる ● インターフェイスがさくっと定義で きることの利点 ● 😞 インターフェイスが増える 87 インターフェイス分離の原則(ISP)を自然に実践 // PHP interface UserRepositoryInterface { public function getUserByID(int $id): ?User; public function save(User $user): void; } ● ここだけを見れば分かる ● 😞 saveのみ必要なクライアントに は不要なメソッドがある(逆も然り) ● 😞 インターフェイスが太る

Slide 88

Slide 88 text

88 アジェンダ 1. PHPとGoのインターフェイス 2. Goのインターフェイスについて 3. サンプルを交えてGoのインターフェイスを知る 4. アプリの実装を見て深ぼる 5. まとめ

Slide 89

Slide 89 text

Goをあげてきましたが 89 PHPの明示的な実装の良さ ● コードの可読性 ○ implementsを見れば、どのインターフェイスを実装しているか一目 瞭然 ○ どこにインターフェイスがあって、どこで実装されているのか分かり やすい ○ 1クラス1ファイル最高!PSR4オートローダー最高! ● 明確性 ○ このクラスは必ずこのインターフェイスを実装しているという保証 ❤ じぶんはPHPのインターフェイス好きです

Slide 90

Slide 90 text

Goをあげてきましたが Goの暗黙的実装 ● どの型がインターフェイスを満たしているか正直分かりにくい Goのむずかしいところ // このインターフェイスを実装している型は? type Writer interface { Write([]byte) (int, error) } 90

Slide 91

Slide 91 text

Goをあげてきましたが Goのパッケージ指向 ● ひとつのファイルに構造体やインターフェイスや関数が入り混じる package usecase import "user/domain" type RegisterUserInterface interface {/*...*/} type RegisterUserUseCase struct {/*...*/} func NewRegisterUserUseCase(repo RegisterUserInterface) *RegisterUserUseCase {/*...*/} func (u *RegisterUserUseCase) SaveUser(user *domain.User) error {/*...*/} Goのむずかしいところ 91

Slide 92

Slide 92 text

PHPのインターフェイスの考え方 92 ● 事前にインターフェイス を定義し、制約を設ける ことによって秩序を提供 ● 値を提供する側が定義 ソフトウェア開発におけるインターフェイスという考え方 https://speakerdeck.com/k1low/phperkaigi-2025?slide=9

Slide 93

Slide 93 text

Goのインターフェイスの考え方 93 ● インターフェイスは消費 者側に存在する必要があ る(ほとんどの場合) ○ 消費者側 == 使用する側 生産者側のインタフェース (#6) https://100go.co/ja/#6

Slide 94

Slide 94 text

Goのインターフェイスの考え方 94 インタフェース汚染 (#5) https://100go.co/ja/#5 ● 抽象は作成するのではなく 発見する ● むやみに抽象化しない、実装を 直接呼び出すのも一つの手

Slide 95

Slide 95 text

1. Goのインターフェースは「ダックタイピング 🦆」 ● implementsを書かなくても、必要なメソッドがあれば暗黙的に満たされる ● メソッドさえあれば、後からでもインターフェースを満たせる柔軟性 2. インターフェースの定義場所 ● PHP(契約を先に明示) ○ 提供する側に置く → 事前に定義 ○ このインターフェースを使用してください(事前定義) ● Go(契約を後で発見) ○ 使用する側に置く → 必要に応じて定義できる ■ 小さなインターフェイスを作るという哲学 ○ このインターフェースを満たしていれば受け入れます(後から発見) 95 Goのインターフェイス、定義場所

Slide 96

Slide 96 text

1. Goのインターフェースは「ダックタイピング 🦆」 ● 必要なメソッドがあれば暗黙的に満たされる 2. インターフェース ● 使用する側に置く → 必要に応じて定義できる ● 小さなインターフェイスを作るという哲学 96 Goを書くうえでの最初のハードル この感覚がつかめていれば大丈夫

Slide 97

Slide 97 text

97 おわりに ● これが正解というわけではない ● Goでも提供する側にインターフェイスを置くパターンもある ● PHPでも使用する側にインターフェイスを置くパターンもある ○ ポート&アダプターパターンとか ● 少しでもGoに興味を持っていただければ幸いです!

Slide 98

Slide 98 text

● Go言語プログラミングエッセンス ○ https://gihyo.jp/book/2023/978-4-297-13419-8 ● ソフトウェア開発におけるインターフェイスという考え方 ○ https://speakerdeck.com/k1low/phperkaigi-2025 ● 100 Go Mistakes and How to Avoid Them(Go言語でありがちな間違い) ○ https://100go.co/ja/ ● Go言語 100Tips ありがちなミスを把握し、実装を最適化する ○ https://book.impress.co.jp/books/1122101133 ● Go の interface は構造体の利用側が定義すると言う話 – もばらぶエンジニアブログ ○ https://engineering.mobalab.net/2021/10/04/where-should-interface-defi ned-in-go/ 98 参考資料

Slide 99

Slide 99 text

ご清聴ありがとうございました 99