Upgrade to Pro — share decks privately, control downloads, hide ads and more …

明示と暗黙 ー PHPとGoの インターフェイスの違いを知る

明示と暗黙 ー PHPとGoの インターフェイスの違いを知る

PHP Conference Japan 2025 の発表資料になります
#phpcon #track2

Avatar for shimabox

shimabox

June 28, 2025
Tweet

More Decks by shimabox

Other Decks in Programming

Transcript

  1. NAME: "しまぶ" SNS: "@shimabox" TAMASHII: "沖縄" COMPANY: "カオナビ" SKILL: -

    "PHP" - "Go" KAMATA: "はじめて喋る" whoami.yml 2 自己紹介
  2. // カスタムエラー型を定義 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を書き始めて最も衝撃を受けたこと
  3. // カスタムエラー型を定義 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 }
  4. // カスタムエラー型を定義 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として 扱える...?」
  5. // カスタムエラー型を定義 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のコード多めです⚠⚠⚠
  6. 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のインターフェイス
  7. 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のインターフェイス インターフェイスの 定義
  8. 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のインターフェイス インターフェイスの 実装
  9. 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のインターフェイス インターフェイスに 依存
  10. 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を 実装しているもので あれば注入できる
  11. 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のインターフェイス
  12. 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のインターフェイス インターフェイスの 定義
  13. 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構造体
  14. 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は インターフェイスを 満たしている
  15. 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のインターフェイス インターフェイスに 依存
  16. 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) を満たしているもの であれば注入できる
  17. 30 Goのインターフェイス • 明示的に`implements`を宣言しない • 必要なメソッドを持っていればインターフェイスを実装し ているとみなされる • 「アヒルのように歩き、アヒルのように鳴くものは、アヒ ルだろう」→

    ダックタイピング 🦆 ※ Goは静的型付け言語なので、厳密には 「構造的型付け(Structural Typing)」です Goのインターフェイスはダックタイピング
  18. // カスタムエラー型を定義 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として 扱える...?」
  19. // カスタムエラー型を定義 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インターフェース として扱える
  20. 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 インターフェイスの利点 == 振る舞いを切り替えられる
  21. // 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だとこう
  22. // 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 構造体がインターフェイスを満たす
  23. // 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 カスタム型がインターフェイスを満たす
  24. // 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にはない 便利だ
  25. // Reader インターフェイス type Reader interface { Read() string }

    // Writer インターフェイス type Writer interface { Write(data string) } // ReaderとWriterを埋め込み // ReadWriterインターフェイスを定義 type ReadWriter interface { Reader Writer } 41 埋め込み • インターフェイスを埋め 込んで新たなインター フェイスを定義できる • コンポジション(合成)
  26. // 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 埋め込み(利用例)
  27. // 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を用い てインターフェイスを継 承し、新たなインター フェイスを作成
  28. // 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だとこう(利用例)
  29. 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{})
  30. 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
  31. // 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 インターフェイスをさくっと満たせる
  32. type TwoMethods interface { MethodOne() MethodTwo() } // MethodTwoを実装 type

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

    PartialOne struct{} func (p PartialOne) MethodOne() {} func (p PartialOne) MethodTwo() {} 50 TwoMethodsインターフェイスを満たすには MethodTwo() を実装するだけ インターフェイスをさくっと 満たせる
  34. // 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だとこう
  35. 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する必 要がある
  36. 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インターフェイスを実装するには 手間はかかるが明示的
  37. • PHP ◦ implements を用いた明示的なインターフェイス実装 ◦ 明確性? • Go ◦

    ダックタイピングによる暗黙的なインターフェイス実装 ▪ さくっと満たせる ◦ 柔軟性? 54 いったんまとめ
  38. <?php namespace App\Domain; class User { public private(set) int $id

    { // PHP 8.4 プロパティフック set { if ($value < 1) throw new \InvalidArgumentException('IDは1以上!!'); $this->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
  39. <?php namespace App\Infrastructure; use App\Domain\User; use App\Domain\UserRepositoryInterface; class UserRepository implements

    UserRepositoryInterface { public function getUserByID(int $userId): User { // ここでユーザーをIDで取得するロジックを実装 // 例えば、データベースからユーザー情報を取得するなど return new User($userId, 'taro'); // 仮の実装 } } 60 Infrastructure/UserRepository.php
  40. <?php namespace App\UseCase; use App\Domain\User; use App\Domain\UserRepositoryInterface; class UserUseCase {

    public function __construct( private UserRepositoryInterface $userRepository ) {} public function handle(int $userId): User { // ユーザーをIDで取得 return $this->userRepository->getUserByID($userId); } } 61 UseCase/UserUseCase.php
  41. <?php namespace App; require_once __DIR__ . '/../vendor/autoload.php'; use App\Infrastructure\UserRepository; use

    App\UseCase\UserUseCase; $userRepository = new UserRepository(); $useCase = new UserUseCase($userRepository); $useCase->handle(1); // ユーザーID 1 のUserを取得 62 index.php UserRepository (UserRepositoryInterface) を注入している
  42. / ├─ Domain │ ├─ User.php │ └─ UserRepositoryInterface.php ├─

    Infrastructure │ └─ UserRepository.php ├─ UseCase │ └─ UserUseCase.php └─ index.php 63 Domain層にインターフェイスが置かれる
  43. 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
  44. 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
  45. 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
  46. 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) を注入している
  47. 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を 満たしているかどうか 分かりづらい
  48. 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を満たしているかどうか確認 こういう方法もある
  49. 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; } }
  50. 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; } } そもそも最初から抽象化されてそう (インターフェイスにある程度メソッドがありそう)
  51. 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) }
  52. 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) } 使用する側がインターフェイスを定義する
  53. • PHP ◦ 提供する側がインターフェイスを定義する ◦ 事前にインターフェイス(抽象)を考えがち ◦ インターフェイスが先に明示されている感覚 • Go

    ◦ 使用する側がインターフェイスを定義する ◦ 後からインターフェイス(抽象)を定義できる ◦ インターフェイスをさくっと満たせる ↓ インターフェイスがさくっと定義できる 82 PHPとGoのインターフェイスを定義する場所の考え方
  54. // 自分たちのコードで必要に応じてインターフェースを定義 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だとラッパークラスが必要
  55. // 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のみ必要なクライアントに は不要なメソッドがある(逆も然り) • 😞 インターフェイスが太る
  56. Goをあげてきましたが 89 PHPの明示的な実装の良さ • コードの可読性 ◦ implementsを見れば、どのインターフェイスを実装しているか一目 瞭然 ◦ どこにインターフェイスがあって、どこで実装されているのか分かり

    やすい ◦ 1クラス1ファイル最高!PSR4オートローダー最高! • 明確性 ◦ このクラスは必ずこのインターフェイスを実装しているという保証 ❤ じぶんはPHPのインターフェイス好きです
  57. 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
  58. 1. Goのインターフェースは「ダックタイピング 🦆」 • implementsを書かなくても、必要なメソッドがあれば暗黙的に満たされる • メソッドさえあれば、後からでもインターフェースを満たせる柔軟性 2. インターフェースの定義場所 •

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

    必要に応じて定義できる • 小さなインターフェイスを作るという哲学 96 Goを書くうえでの最初のハードル この感覚がつかめていれば大丈夫
  60. • 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 参考資料