創造的なパターンは、有名な4つのギャングで初めて描かれた。 本書は、それぞれのパターンを専用の章で紹介し、それぞれのための厳格な構造に従う:意図、動機、適用性、構造、参加者、協力、結果、実装、サンプルコード、既知の用途、および関連パターン。 デザインパターン たとえば、ここに、The Excerpt for the パターン: 建築家 企画: 複雑な物体の構造をその構成から分離して、同じ構造プロセスによって異なる構成を生み出すことができるようにする。 適用性: Builder パターンを使用すると、 複雑なオブジェクトを作成するためのアルゴリズムは、オブジェクトを構成する部品やそれらがどのように組み立てられているかに関係なくなければならない。 建設プロセスは、構築されるオブジェクトの異なる表現を可能にする必要があります。 : Intent 複雑な物体の構造をその構成から分離して、同じ構造プロセスによって異なる構成を生み出すことができるようにする。 : Applicability Builder パターンを使用すると、 複雑なオブジェクトを作成するためのアルゴリズムは、オブジェクトを構成する部品やそれらがどのように組み立てられているかに関係なくなければならない。 建設プロセスは、構築されるオブジェクトの異なる表現を可能にする必要があります。 GoF(Gang of Four)は、オブジェクト指向プログラミングの分野における基礎であり、Javaのような広く普及しているプログラミング言語を含むプログラミング言語の設計に影響を与えています。 再びエンジニアリングのポジションにいる私は、維持性に関して多くの改善の可能性がある新しく書かれたJavaコードに遭遇しますが、エンジニアは、元の著者の将来の自分と私を含め、それを更新する必要があると想像します。 同じタイプの多くのパラメータを持つ構造体。 いくつかの String パラメータを持つコンクリートを想像してみましょう: public License ( String id, String licenseeName, String licenseId, String environment, LocalDateTime generatedAt ) このコンクリエーターを呼び出すとき、チャンスは、呼び出し者が不意にパラメータオーダーを切り替える可能性があります: var license = new License("My license", "XXX-123", "Customer", "User-acceptance tests", new LocalDateTime()); Oops, I changed the licensee name and the license ID. Your IDE may help here, but there are other ways. ライセンス取得者の名前とライセンスIDを変更しました。 タイプ Wrappers 純粋なOOPの支持者は、誰も直接の文字列を使用すべきではないことを喜んで指摘します。 ・Java : エジ record public record Id(String id) { ... } public record LicenseeName(String licenseeName) { ... } public record LicenseeId(String licenseId) { ... } public record Environment(String environment) { ... } public record GeneratedAt(LocalDateTime generatedAt) { ... } 間違いを犯すことはできません: var id = new Id("My license"); var licenseeName = new LicenseeName("Customer"); var licenseId = new LicenseeId("XXX-123"); var environment = new Environment("User-acceptance tests"); var generatedAt = new LocalDateTime(); var license = new License(id, licenseId, licenseName, environment, generatedAt); //1 コンピュータタイムエラー このアプローチは確実にメンテナンス能力を向上させる一方で、ウラッパーはメモリサイズを増加させます. 正確な増加はJDKの実装に依存しますが、単一のタイプでは約5倍です。 Kotlin makes it a breeze by providing : the wrapping is a compile-time check, but the bytecode points to the wrapped type with the following limitation: 包装はコンパイルタイムチェックですが、バイトコードは次の制限を伴う包装型に指します。 INLINE VALUE CLASS Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. Inline class must have a single property initialized in the primary constructor. パラメータ名 Java では、位置パラメータを含むメソッド呼び出しのみを提供していますが、他の言語は、 , Python, Kotlin, and Rust, also offer named parameters. Python, Kotlin, and Rust, also offer named parameters. Python, Kotlin, and Rust, also offer named parameters. エジ 以下は、上記のクラスを反映するKotlin コンクリュータです。 class License ( val id: String, val licenseeName: String, val licenseId: String, val environment: String, val generatedAt: LocalDateTime ) パラメータを名前で構築者に呼び出し、ミスを犯すリスクを減らすことができます: val license = License( id = "My license", licenseeName = "Customer", licenseId = "XXX-123", environment = "User-acceptance tests", generatedAt = LocalDateTime() ) THE パターン 建築家 THE pattern は、GoF で説明された使用例の一部ではないにもかかわらず、もう一つの実行可能なアプローチです。 建築家 こちらはコード: public class License { private final String id; private final String licenseeName; private final String licenseId; private final String environment; private final LocalDateTime generatedAt; private License ( //1 String id, String licenseeName, String licenseId, String environment, LocalDateTime generatedAt ) { ... } public static LicenseBuilder builder() { //2 return new LicenseBuilder(); } public static class LicenseBuilder { private String id; //3 private String licenseeName; //3 private String licenseId; //3 private String environment; //3 private LocalDateTime generatedAt; //3 private LicenseBuilder() {} //1 public LicenseBuilder withId(String id) { //4 this.id = id; //5 return this; //4 } // Other `withXXX` methods public License build() { //6 return new License( id, licenseeName, licenseId, environment, generatedAt ); } } } 直接オブジェクトインスタンテーションを防ぐ 新たな構築者を作る Builder フィールドは、オブジェクトのフィールドを模します。 各メソッドは、builder オブジェクト自体を返します。 Attribute を指定する 完全なオブジェクトを返す 建築家をこう呼ぶことができます: val license = License.builder() .withId("My license") .withLicenseName("Customer") .withLicenseId("XXX-123") .withEnvironment("User-acceptance tests") .withGeneratedAt(new LocalDateTime()) ビルダーコードを作成することは苦痛です(AIを使用しない限り)、しかし、より良い読み取りを可能にします。さらに、構築中のオブジェクトが有効であることを保証するために、各メソッド呼び出しのための検証を追加することができます。 . Faceted Builder 概要 Approach Pros Cons Type wrappers Object-Oriented Programming - More verbose- Can be memory-heavy depending on the language Named parameters Easy Not available in Java Builder pattern Verbose - Allows creating complex objects- Allows validating タイプ Wrappers Object-Oriented プログラミング - More verbose- Can be memory-heavy depending on the language パラメーター名 簡単 Java では利用できません。 建築パターン スルー - 複雑なオブジェクトを作成することを可能にする - Validating 例外を投げる製造業者 同じコードベースで、以下のコードが見つかりました。 public Stuff(UuidService uuidService, FallbackUuidService fallbackUuidService) { try { uuid = uuidService.getUuid(); } catch(CannotGetUuidException e) { try { uuid = fallbackUuidService.getUuid(); } catch(CannotGetUuidException e1) { uuid = "UUID can be fetched"; } } } With a modicum of experience, you can notice what's wrong in the above snippet. If both services fail, UUID に依存するすべてのコードは、おそらくUUID 以外の値に対処しなければならない。 uuid public Stuff(UuidService uuidService, FallbackUuidService fallbackUuidService) { try { uuid = uuidService.getUuid(); } catch(CannotGetUuidException e) { try { uuid = fallbackUuidService.getUuid(); } catch(CannotGetUuidException e1) { throw new RuntimeException(e1); } } } Now, every オブジェクトは有効な UUID を持っていますが、構造体内の例外を投げるには、潜在的な問題があります: Stuff リソース漏洩: 構築者がリソース(例えば、ファイル、ソケット)を割り当て、例外を投げると、これらのリソースはリリースされない場合があります。 相続: スーパークラス ビルダーが例外を投げると、サブクラス ビルダーは実行されません。 Checked exceptions: It's impossible to use checked exceptions in constructors, only runtime ones. これらの理由から、私は例外は構築者に場所がないと思うし、私はそれらを避けることができます。 最初のセクションで説明されたパターンですが、上記のように、コードの多くは必要ではないと思います。 . 建築家 工場方法 試み オブジェクトを作成するためのインターフェイスを定義しますが、サブクラスがどのクラスをインスタンティエートするかを決定します。 適用性 工場方法パターンを使用する場合: a class can't anticipate the class of objects it must create. a class wants its subclasses to specify the objects it creates. クラスは、そのサブクラスが作成するオブジェクトを指定することを望んでいます。 classes delegate responsibility to one of several helper subclasses, and you want to localize the knowledge of which helper subclass is the delegate. Intent Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. Applicability 工場方法パターンを使用する場合: クラスは、作成しなければならないオブジェクトのクラスを予測することはできません。 a class wants its subclasses to specify the objects it creates. クラスは、そのサブクラスが作成するオブジェクトを指定することを望んでいます。 クラスは、いくつかのヘルパーサブクラスのいずれかに責任を委任し、ヘルパーサブクラスが代理人である知識をローカル化する必要があります。 この場合、私たちは別の理由でそれを使用していることに注意してください. Here is the updated code: public class Stuff { private final UUID uuid; private Stuff(UUID uuid) { //1 this.uuid = uuid; } public static Stuff create(UuidService uuidService, FallbackUuidService fallbackUuidService) throws CannotGetUuidException { try { return new Stuff(uuidService.getUuid()); } catch(CannotGetUuidException e) { return new Stuff(fallbackUuidService.getUuid()); //2 } } } 外部インスタンスを防ぐ 失敗すると、新しい CannotGetUuidException を投げます。 上の1つは、こんな感じです。 var stuff = Stuff.create(uuidService, fallbackUuidService); //1 Need to Catch CannotGetUuid例外 この時点で、呼び出しが成功した場合、オブジェクトが完全に初期化されていることを確実にします。 結論 この投稿では、本書に記載されていないGoFの創造的なパターンの2つの用途について説明しました:保守性を向上させ、オブジェクトが完全に初期化されていることを確認します。 To go further: GoF デザインパターン A dive into the Builder pattern ビルダーパターンは、限られた状態のマシンです! オリジナルの投稿 A Java Geek on August 31st, 2025 ・Java Geek