36
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rust・Kotlin・Go・TypeScriptで再考するGoFデザインパターン:モダン言語機能が変えた設計の常識

36
Last updated at Posted at 2026-03-21

Rust・Kotlin・Go・TypeScriptで再考するGoFデザインパターン:モダン言語機能が変えた設計の常識

1994年に発表されたGoF(Gang of Four)の23のデザインパターンは、オブジェクト指向プログラミングの設計指針として30年以上にわたり参照されてきました。しかし、Rust・Kotlin・Go・TypeScriptといったモダン言語が持つ代数的データ型、パターンマッチング、ファーストクラス関数、トレイトシステムなどの機能は、GoFパターンの多くを「言語機能に吸収」しています。

本記事では、GoF 23パターンをモダン言語の視点で分類し、「不要になったパターン」「形を変えて生き残るパターン」「今なお有用なパターン」を具体的なコード例とともに整理します。

この記事でわかること

  • GoF 23パターンのうち、モダン言語の機能で不要化・簡素化された具体的なパターンと理由
  • Rust・Kotlin・Go・TypeScriptにおける代替実装の具体的なコード例
  • 言語機能(代数的データ型、パターンマッチング、高階関数、トレイト)がどのパターンを吸収したかの対応表
  • 2026年の実務で依然として有用なパターンの見極め方
  • 各パターンの代替手法を選ぶ際のトレードオフと注意点

対象読者

  • 想定読者: Javaや C++ でGoFパターンを学んだ経験があり、モダン言語での設計手法を知りたいエンジニア
  • 必要な前提知識:
    • GoFデザインパターンの基本概念(Singleton、Strategy、Observer等を聞いたことがあるレベル)
    • Rust / Kotlin / Go / TypeScript のいずれか1つ以上の基礎文法
    • オブジェクト指向プログラミングの基本概念(継承、ポリモーフィズム、カプセル化)

結論・成果

Peter Norvig氏の1996年の研究では、GoF 23パターンのうち16パターンがLispやDylanなどの動的言語で単純化または不要化されると報告されています(Design Patterns in Dynamic Languages)。2026年現在のモダン言語では、この傾向がさらに進んでいます。

本記事の分析結果を以下にまとめます。

分類 パターン数 該当パターン例
言語機能に吸収(不要化) 8 Iterator, Singleton, Command, Strategy, State, Template Method, Visitor, Observer
形を変えて簡素化 9 Builder, Factory Method, Abstract Factory, Adapter, Decorator, Proxy, Chain of Responsibility, Mediator, Memento
今なお有用 6 Facade, Composite, Bridge, Flyweight, Interpreter, Prototype

モダン言語で設計する際は、GoFパターンを「そのまま適用する」のではなく、「その言語のイディオムで同じ問題を解決する」という発想が重要です。

GoFパターンの3分類を理解する

GoF 23パターンをモダン言語の視点で評価するにあたり、まず「なぜ特定のパターンが不要になるのか」のメカニズムを理解しましょう。

言語機能がパターンを吸収するメカニズム

GoFパターンの多くは、1990年代のC++やSmalltalkの言語的制約を回避するためのワークアラウンドでした。モダン言語が以下の機能を持つことで、パターンとして明示的に実装する必要がなくなっています。

言語機能 吸収するGoFパターン 具体例
ファーストクラス関数 Strategy, Command, Template Method 関数を引数として渡すだけで済む
代数的データ型(enum/sealed class) State, Visitor 列挙型 + パターンマッチングで型安全に表現
パターンマッチング Visitor, Chain of Responsibility match/when式で網羅的に分岐
モジュールシステム Singleton モジュール自体がシングルトンとして機能
トレイト/インターフェース Iterator, Observer 言語標準ライブラリで提供
構造体合成(Embedding) Decorator, Adapter 継承なしで型の合成が可能

注意: 「パターンが不要」とは「その設計意図が不要」という意味ではありません。問題自体は存在し続けますが、解決手段が言語機能に組み込まれたため、明示的なパターン適用が不要になった、という意味です。

パターン分類の判定基準

本記事では以下の基準でGoFパターンを分類しました。

  1. 言語に吸収(不要化): 言語の標準機能で自然に解決でき、パターンとして意識する必要がない
  2. 簡素化: パターンの設計意図は有用だが、実装がモダン言語では大幅に簡潔になる
  3. 依然有用: モダン言語でもパターンとしての構造化が設計上の価値を持つ

言語に吸収されたパターンをコードで確認する

ここからは、モダン言語の機能によって不要化されたGoFパターンを、具体的なコード例で確認していきます。

StrategyパターンをTypeScriptの高階関数で置き換える

GoFのStrategyパターンは、アルゴリズムをオブジェクトとしてカプセル化し、実行時に切り替える設計です。C++やJava では、Strategyインターフェースを定義し、具象クラスを複数実装する必要がありました。

TypeScriptでは、関数がファーストクラスオブジェクトであるため、関数を引数に渡すだけで同じことが実現できます。

// TypeScript: Strategy パターンの代替
// 関数シグネチャがそのまま「戦略」の型定義になる
type SortStrategy<T> = (items: T[]) => T[];

// 各戦略は単なる関数
const bubbleSort: SortStrategy<number> = (items) => {
  const arr = [...items];
  for (let i = 0; i < arr.length; i++) {
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
    }
  }
  return arr;
};

const quickSort: SortStrategy<number> = (items) => {
  if (items.length <= 1) return items;
  const pivot = items[0];
  const left = items.slice(1).filter((x) => x <= pivot);
  const right = items.slice(1).filter((x) => x > pivot);
  return [...quickSort(left), pivot, ...quickSort(right)];
};

// Context は戦略を受け取る関数
function processData(data: number[], strategy: SortStrategy<number>): number[] {
  console.log(`Processing ${data.length} items`);
  return strategy(data);
}

// 使用例: 戦略の切り替えは関数の差し替えだけ
const data = [5, 3, 8, 1, 9, 2];
processData(data, bubbleSort);  // バブルソートで処理
processData(data, quickSort);   // クイックソートで処理

なぜこの実装を選んだか:

  • Java版ではインターフェース定義 + 具象クラス2つ + Contextクラスで約50行必要だったものが、TypeScriptでは型エイリアス1行 + 関数定義で完結します
  • 型安全性は SortStrategy<T> 型エイリアスで担保されており、シグネチャが合わない関数を渡すとコンパイルエラーになります

制約: この手法は「状態を持たない戦略」には適していますが、戦略自体が内部状態を持つ場合(例: 学習率をトラッキングする最適化戦略)はクロージャまたはクラスの方が適切です。Pythonの __init__ で初期化パラメータを持つStrategyクラスのような使い方が必要な場合は、クロージャで状態を閉じ込める方法を検討してください。

StateパターンをRustのenumで型安全に実装する

GoFのStateパターンは、オブジェクトの状態に応じて振る舞いを変更する設計です。従来はStateインターフェースと具象Stateクラスを用意し、Contextクラスが現在の状態オブジェクトに処理を委譲していました。

Rustでは、enum + パターンマッチングで状態遷移を型安全に表現できます。

// Rust: State パターンの代替(enum + パターンマッチング)

// 状態を enum で定義。各バリアントが状態固有のデータを保持
#[derive(Debug)]
enum ConnectionState {
    Disconnected,
    Connecting { attempt: u32, timeout_ms: u64 },
    Connected { session_id: String },
    Error { message: String, retry_count: u32 },
}

// イベントも enum で定義
enum ConnectionEvent {
    Connect,
    Established(String),  // session_id
    Timeout,
    Disconnect,
    Failure(String),      // error message
}

impl ConnectionState {
    // 状態遷移を match 式で網羅的に記述
    fn transition(self, event: ConnectionEvent) -> Self {
        match (self, event) {
            // Disconnected + Connect → Connecting
            (ConnectionState::Disconnected, ConnectionEvent::Connect) => {
                ConnectionState::Connecting { attempt: 1, timeout_ms: 3000 }
            }
            // Connecting + Established → Connected
            (ConnectionState::Connecting { .. }, ConnectionEvent::Established(sid)) => {
                ConnectionState::Connected { session_id: sid }
            }
            // Connecting + Timeout → リトライまたはエラー
            (ConnectionState::Connecting { attempt, .. }, ConnectionEvent::Timeout) => {
                if attempt < 3 {
                    ConnectionState::Connecting {
                        attempt: attempt + 1,
                        timeout_ms: 3000 * (attempt as u64 + 1), // 指数バックオフ
                    }
                } else {
                    ConnectionState::Error {
                        message: "Max retries exceeded".into(),
                        retry_count: attempt,
                    }
                }
            }
            // Connected + Disconnect → Disconnected
            (ConnectionState::Connected { .. }, ConnectionEvent::Disconnect) => {
                ConnectionState::Disconnected
            }
            // どの状態でも Failure → Error
            (_, ConnectionEvent::Failure(msg)) => {
                ConnectionState::Error { message: msg, retry_count: 0 }
            }
            // 無効な遷移はそのまま状態を維持
            (state, _) => state,
        }
    }
}

なぜこの実装を選んだか:

  • Java版のStateパターンでは、各Stateクラスで不要なメソッドにも空実装を書く必要がありました。Rustの match 式は網羅性チェックがあるため、状態遷移の漏れをコンパイル時に検出できます
  • 各状態に固有のデータ(attempt, session_id)をenumバリアントに直接持たせられるため、型安全性が高まります

注意: Rustにはさらに高度なTypestateパターンがあり、状態をジェネリクスの型パラメータとしてエンコードすることで、コンパイル時に不正な状態遷移を防ぐことができます(State Machines III: Type States)。ただし、PhantomData やジェネリクスの理解が必要なため、チームの習熟度に応じてenum版から始めることが推奨されています。

VisitorパターンをKotlinのsealed classで置き換える

GoFのVisitorパターンは、データ構造とその操作を分離する設計です。従来はダブルディスパッチ(double dispatch)を使い、acceptメソッドとvisitメソッドの組み合わせで実現していました。この実装は構造が複雑になりやすいという課題がありました。

Kotlin(およびJava 21以降)では、sealed class + when式の網羅的パターンマッチングで同じ目的を達成できます。

// Kotlin: Visitor パターンの代替(sealed class + when 式)

// データ構造を sealed class で定義
sealed class Shape {
    data class Circle(val radius: Double) : Shape()
    data class Rectangle(val width: Double, val height: Double) : Shape()
    data class Triangle(val base: Double, val heightVal: Double) : Shape()
}

// 操作は拡張関数として定義(Visitor クラス不要)
fun Shape.area(): Double = when (this) {
    is Shape.Circle -> Math.PI * radius * radius
    is Shape.Rectangle -> width * height
    is Shape.Triangle -> base * heightVal / 2.0
    // sealed class なので網羅性がコンパイラにより保証される
    // 新しいサブクラスを追加すると、ここでコンパイルエラーになる
}

fun Shape.perimeter(): Double = when (this) {
    is Shape.Circle -> 2 * Math.PI * radius
    is Shape.Rectangle -> 2 * (width + height)
    is Shape.Triangle -> {
        // ヘロンの公式で辺の長さを概算(簡略化のため底辺と高さから)
        val side = Math.sqrt(base * base / 4 + heightVal * heightVal)
        base + 2 * side
    }
}

fun Shape.describe(): String = when (this) {
    is Shape.Circle -> "半径${radius}の円"
    is Shape.Rectangle -> "${width}x${height}の長方形"
    is Shape.Triangle -> "底辺${base}, 高さ${heightVal}の三角形"
}

// 使用例
fun main() {
    val shapes: List<Shape> = listOf(
        Shape.Circle(5.0),
        Shape.Rectangle(4.0, 6.0),
        Shape.Triangle(3.0, 4.0),
    )
    shapes.forEach { shape ->
        println("${shape.describe()}: 面積=${shape.area()}, 周長=${shape.perimeter()}")
    }
}

なぜこの実装を選んだか:

  • GoF版Visitorは accept/visit のダブルディスパッチ構造が複雑で、新しいVisitorの追加は容易だが新しいデータ型の追加にはすべてのVisitorを修正する必要がありました(Expression Problem)
  • Kotlinのsealed class版では、新しい操作は拡張関数を追加するだけです。新しいデータ型(Shape)を追加した場合は、コンパイラが修正漏れを検出します

トレードオフ: sealed class方式は「新しいデータ型の追加」時にすべてのwhen式を修正する必要があります。これは従来のVisitorパターンの「新しい操作の追加が容易」というトレードオフの裏返しです。データ型が安定していて操作を頻繁に追加するケースではsealed class方式が適しています。

Iteratorパターン:すべてのモダン言語に組み込み済み

GoFのIteratorパターンは、コレクションの内部構造を公開せずに要素を順次アクセスする設計です。2026年現在、このパターンはすべてのモダン言語に標準ライブラリとして組み込まれており、明示的にパターンとして実装する場面はほとんどありません。

// Rust: Iterator トレイトは言語の中核機能
// カスタムイテレータも Iterator トレイトを実装するだけ
struct Fibonacci {
    curr: u64,
    next: u64,
}

impl Fibonacci {
    fn new() -> Self {
        Fibonacci { curr: 0, next: 1 }
    }
}

impl Iterator for Fibonacci {
    type Item = u64;
    fn next(&mut self) -> Option<Self::Item> {
        let result = self.curr;
        self.curr = self.next;
        self.next = result + self.next;
        Some(result)
    }
}

// 使用例: map, filter, take 等のアダプタが自動的に使える
fn main() {
    let sum: u64 = Fibonacci::new()
        .take(20)               // 最初の20個
        .filter(|&x| x % 2 == 0) // 偶数のみ
        .sum();                   // 合計
    println!("偶数フィボナッチの合計: {sum}");
}
// Kotlin: Sequence(遅延評価イテレータ)も標準
val fibonacci = sequence {
    var a = 0L
    var b = 1L
    while (true) {
        yield(a)
        val temp = a + b
        a = b
        b = temp
    }
}

// 使い方
val result = fibonacci
    .take(20)
    .filter { it % 2 == 0L }
    .sum()

Singletonパターン:モジュールシステムが解決

Singletonパターンは、グローバルに1つのインスタンスのみを保証する設計です。モダン言語ではモジュールシステム自体がSingletonとして機能するため、パターンとして実装する意味がほとんどなくなりました。

// TypeScript: モジュール自体がシングルトン
// config.ts
class DatabaseConfig {
  readonly host: string;
  readonly port: number;
  readonly database: string;

  constructor() {
    this.host = process.env.DB_HOST ?? "localhost";
    this.port = Number(process.env.DB_PORT ?? 5432);
    this.database = process.env.DB_NAME ?? "myapp";
  }
}

// モジュールスコープの変数は一度だけ初期化される
export const dbConfig = new DatabaseConfig();
// Kotlin: object 宣言がシングルトンそのもの
object AppConfig {
    val databaseUrl: String = System.getenv("DATABASE_URL") ?: "localhost:5432"
    val maxRetries: Int = 3
    val timeoutMs: Long = 5000
}

// 使用: AppConfig.databaseUrl(クラスを意識する必要なし)
// Go: sync.Once + パッケージ変数(必要な場合のみ)
package config

import "sync"

type appConfig struct {
    DatabaseURL string
    MaxRetries  int
}

var (
    instance *appConfig
    once     sync.Once
)

func GetConfig() *appConfig {
    once.Do(func() {
        instance = &appConfig{
            DatabaseURL: "localhost:5432",
            MaxRetries:  3,
        }
    })
    return instance
}

よくある間違い: Singletonが不要になったからといって、グローバル状態を自由に使ってよいわけではありません。テスタビリティの観点からは、依存性注入(DI)で設定オブジェクトを渡す方が望ましい場合が多いです。Singletonの代わりにDIコンテナ(例: TypeScriptのtsyringe, KotlinのKoin)を使うアプローチが実務では推奨されています。

形を変えて生き残るパターンを実装する

次に、GoFパターンの設計意図は有用だが、モダン言語では実装が大幅に簡素化されるパターンを見ていきます。

BuilderパターンをRustの型安全ビルダーで再設計する

GoFのBuilderパターンは、複雑なオブジェクトの構築過程をカプセル化します。Rustでは、メソッドチェーン + 所有権の移動により、型安全なビルダーを実現できます。

// Rust: 型安全な Builder パターン
#[derive(Debug)]
struct HttpRequest {
    method: String,
    url: String,
    headers: Vec<(String, String)>,
    body: Option<String>,
    timeout_ms: u64,
}

// Builder はメソッドチェーンで self を消費して返す
struct HttpRequestBuilder {
    method: String,
    url: String,
    headers: Vec<(String, String)>,
    body: Option<String>,
    timeout_ms: u64,
}

impl HttpRequestBuilder {
    fn new(method: &str, url: &str) -> Self {
        // 必須フィールドはコンストラクタで要求
        HttpRequestBuilder {
            method: method.to_string(),
            url: url.to_string(),
            headers: Vec::new(),
            body: None,
            timeout_ms: 30000,
        }
    }

    fn header(mut self, key: &str, value: &str) -> Self {
        self.headers.push((key.to_string(), value.to_string()));
        self
    }

    fn body(mut self, body: &str) -> Self {
        self.body = Some(body.to_string());
        self
    }

    fn timeout(mut self, ms: u64) -> Self {
        self.timeout_ms = ms;
        self
    }

    fn build(self) -> HttpRequest {
        HttpRequest {
            method: self.method,
            url: self.url,
            headers: self.headers,
            body: self.body,
            timeout_ms: self.timeout_ms,
        }
    }
}

// 使用例
fn main() {
    let request = HttpRequestBuilder::new("POST", "https://api.example.com/data")
        .header("Content-Type", "application/json")
        .header("Authorization", "Bearer token123")
        .body(r#"{"key": "value"}"#)
        .timeout(5000)
        .build();

    println!("{:?}", request);
}

注意点:

Rustでは self の所有権を移動するため、ビルダーの同一インスタンスを複数箇所で使い回すことはできません(コンパイルエラー)。これは意図しない再利用を防ぐ安全策でもありますが、テンプレートとなるビルダーを使い回したい場合は Clone トレイトの実装を検討してください。

DecoratorパターンをGoの構造体埋め込みで実現する

GoFのDecoratorパターンは、オブジェクトに動的に機能を追加する設計です。Goにはクラスも継承もありませんが、インターフェースと構造体の埋め込みで同様の効果を得られます。

package main

import (
	"fmt"
	"log"
	"time"
)

// 基本インターフェース
type DataStore interface {
	Get(key string) (string, error)
	Set(key string, value string) error
}

// 基本実装
type InMemoryStore struct {
	data map[string]string
}

func NewInMemoryStore() *InMemoryStore {
	return &InMemoryStore{data: make(map[string]string)}
}

func (s *InMemoryStore) Get(key string) (string, error) {
	val, ok := s.data[key]
	if !ok {
		return "", fmt.Errorf("key not found: %s", key)
	}
	return val, nil
}

func (s *InMemoryStore) Set(key string, value string) error {
	s.data[key] = value
	return nil
}

// Decorator 1: ロギング
type LoggingStore struct {
	inner DataStore // インターフェースを保持(埋め込みではなくラップ)
}

func WithLogging(store DataStore) *LoggingStore {
	return &LoggingStore{inner: store}
}

func (s *LoggingStore) Get(key string) (string, error) {
	log.Printf("GET key=%s", key)
	val, err := s.inner.Get(key)
	if err != nil {
		log.Printf("GET key=%s error=%v", key, err)
	}
	return val, err
}

func (s *LoggingStore) Set(key string, value string) error {
	log.Printf("SET key=%s", key)
	return s.inner.Set(key, value)
}

// Decorator 2: メトリクス
type MetricsStore struct {
	inner    DataStore
	getCount int
	setCount int
}

func WithMetrics(store DataStore) *MetricsStore {
	return &MetricsStore{inner: store}
}

func (s *MetricsStore) Get(key string) (string, error) {
	start := time.Now()
	val, err := s.inner.Get(key)
	s.getCount++
	log.Printf("GET duration=%v total_gets=%d", time.Since(start), s.getCount)
	return val, err
}

func (s *MetricsStore) Set(key string, value string) error {
	start := time.Now()
	err := s.inner.Set(key, value)
	s.setCount++
	log.Printf("SET duration=%v total_sets=%d", time.Since(start), s.setCount)
	return err
}

// Decorator の積み重ね
func main() {
	store := WithMetrics(WithLogging(NewInMemoryStore()))
	store.Set("user:1", "Alice")
	val, _ := store.Get("user:1")
	fmt.Println(val) // "Alice"
}

なぜこの実装を選んだか:

  • Goにはクラス継承がないため、GoFのDecorator(抽象クラスを継承)をそのまま適用できません
  • インターフェースベースのラッピングは、任意の実装を差し替え可能な柔軟性を持ちます
  • WithLogging(WithMetrics(...)) のように関数合成でデコレータを積み重ねるのは、Goのイディオムとして定着しています

Factory MethodをTypeScriptの型ガードで簡素化する

Factory Methodは、生成するオブジェクトの型をサブクラスに決定させるパターンです。TypeScriptでは、ユニオン型 + 型ガードで十分に代替できます。

// TypeScript: Factory Method の代替(ユニオン型 + 型ガード)

// 判別可能なユニオン型(Discriminated Union)
type Notification =
  | { type: "email"; to: string; subject: string; body: string }
  | { type: "sms"; phoneNumber: string; message: string }
  | { type: "push"; deviceToken: string; title: string; body: string };

// ファクトリ関数(クラス階層不要)
function createNotification(
  channel: "email" | "sms" | "push",
  recipient: string,
  content: string
): Notification {
  switch (channel) {
    case "email":
      return {
        type: "email",
        to: recipient,
        subject: "Notification",
        body: content,
      };
    case "sms":
      return {
        type: "sms",
        phoneNumber: recipient,
        message: content,
      };
    case "push":
      return {
        type: "push",
        deviceToken: recipient,
        title: "Notification",
        body: content,
      };
  }
}

// 型安全な分岐処理(exhaustive check)
function sendNotification(notification: Notification): void {
  switch (notification.type) {
    case "email":
      console.log(`Sending email to ${notification.to}: ${notification.subject}`);
      break;
    case "sms":
      console.log(`Sending SMS to ${notification.phoneNumber}`);
      break;
    case "push":
      console.log(`Sending push to ${notification.deviceToken}`);
      break;
  }
}

よくある問題と解決方法

GoFパターンからモダン言語のイディオムに移行する際によくある問題を整理します。

問題 原因 解決方法
enumバリアントが増えすぎてmatch式が巨大化 状態やイベントの種類が多い 関連する状態をサブenumにグルーピングする
高階関数で状態を持つ戦略が書きにくい クロージャのキャプチャが複雑 構造体 + トレイト実装に切り替え(Rust)、クラスに戻す(TypeScript)
sealed classの新規追加でwhen式が大量にコンパイルエラー Expression Problem 操作が安定している場合はインターフェースベースのVisitorを検討
Goの構造体埋め込みで意図しないメソッドが公開される 埋め込みは全メソッドをプロモートする インターフェースでの明示的ラッピングに変更
Singleton代替のモジュール変数がテストで状態を引きずる モジュールスコープは共有される DIコンテナを使い、テスト時にモックを注入する

Expression Problem への対処

GoFパターンのモダン化で最も注意すべきなのが、Expression Problem(式問題)です。これは「新しいデータ型の追加」と「新しい操作の追加」の両方を容易にすることが困難であるという根本的な問題です。

  • データ型が安定・操作を頻繁に追加: sealed class / enum + パターンマッチングが有利
  • 操作が安定・データ型を頻繁に追加: トレイトオブジェクト / インターフェースが有利
  • 両方を頻繁に追加: TypeScriptのTagless Final、Scalaの型クラスなど高度なテクニックが必要

まとめと次のステップ

まとめ:

  • GoF 23パターンのうち約8パターンはモダン言語の機能に吸収され、明示的な実装が不要になりました。ファーストクラス関数がStrategy・Command・Template Methodを、代数的データ型がState・Visitorを、モジュールシステムがSingletonを、標準ライブラリがIteratorを吸収しています
  • 約9パターンは形を変えて簡素化されています。BuilderはRustの所有権移動チェーン、Decoratorは Goの構造体ラッピング、Factory Methodは TypeScriptの判別可能ユニオン型で、GoF版より大幅に簡潔に実装できます
  • 約6パターンは2026年でも設計上の価値を持ちます。Facade(複雑なサブシステムのシンプルなAPI)、Composite(木構造の再帰的処理)、Bridge(抽象と実装の分離)は言語機能だけでは解決しにくい構造的な設計課題を扱います
  • 重要なのは「GoFパターンを捨てる」ではなく、「その言語のイディオムで同じ問題を解く」という視点の転換です
  • Expression Problemに注意し、「データ型の追加」と「操作の追加」のどちらが頻繁かで手法を選択してください

次にやるべきこと:

  • 自分のプロジェクトのコードベースから、GoFパターンを明示的に実装している箇所を洗い出してみましょう。特にStrategyやVisitorは高階関数やsealed classに書き換えることで、コード量を削減できる可能性があります
  • RustのRust Design Patterns やKotlinのDesign Patterns In Kotlinのリポジトリを参照し、各言語のイディオマティックな実装を確認してください
  • Expression Problemが発生しやすい箇所(プラグインシステム、AST操作など)を特定し、適切なパターンを選択してください

参考


注意: この記事はAI(Claude Code)により自動生成されました。内容の正確性については複数の情報源で検証していますが、実際の利用時は公式ドキュメントもご確認ください。

36
41
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?