コンテンツにスキップ

デザインパターン

出典: フリー教科書『ウィキブックス(Wikibooks)』

コンピューターサイエンスにおけるデザインパターンは、ソフトウェア設計において再利用可能な解決策の一般的なモデルやテンプレートです。これらのパターンは、共通の課題や問題に対処するために繰り返し使われ、設計の柔軟性、拡張性、保守性を向上させます。デザインパターンは、ソフトウェアエンジニアリングのコミュニティで広く受け入れられており、GoF(Gang of Four)による "Design Patterns: Elements Of Reusable Object-Oriented Software"(邦題『オブジェクト指向における再利用のためのデザインパターン』)などの文献で有名です。

デザインパターンの主な特徴は以下の通りです:

再利用性
デザインパターンは、共通の設計上の問題に対処するための汎用的な解決策を提供するため、再利用が容易です。同じまたは類似の課題に対して何度も設計を行う必要がなくなります。
柔軟性
デザインパターンは柔軟性をもたらし、変更が必要な場合にも容易に対応できるような設計を促進します。新しい要件や機能の追加があっても、デザインパターンを利用することで変更の影響を最小限に抑えられます。
共通の語彙
デザインパターンは、ソフトウェア開発者やエンジニアの間で共通の語彙を提供します。これにより、チーム内でのコミュニケーションが向上し、設計の理解が容易になります。
ソフトウェアアーキテクチャ
デザインパターンはソフトウェアアーキテクチャを構築するための基本的な構成要素として使われます。これにより、ソフトウェアの構造が理解しやすく、拡張が容易になります。

一部の有名なデザインパターンには、Singleton(単一のインスタンスを保持するパターン)、Factory Method(ファクトリークラスがオブジェクトの生成を担当するパターン)、Observer(オブジェクトの状態変化を監視するパターン)などがあります。これらのパターンはソフトウェア設計の中で広く利用され、開発者が共通の問題に対してより効果的にアプローチできるようになります。

デザインパターンの分類と概要

[編集]

デザインパターンは、ゴフ(Gang of Four)によって提案された23のパターンを、以下の3つの大きなカテゴリに分類できます。それぞれのカテゴリには、ソフトウェア開発における特定の課題に対処するためのパターンが含まれています。

生成(Creational)デザインパターン

[編集]

生成デザインパターンは、オブジェクトの生成に焦点を当て、オブジェクトの生成と構成に関する柔軟で再利用可能な手法を提供します。これらのパターンは、インスタンスの生成方法やクラスの初期化に関する課題に対処し、アプリケーションの柔軟性と保守性を向上させます。

Singleton(シングルトン)パターン

[編集]

シングルトンパターンは、アプリケーション内で唯一のインスタンスを持つように設計されたデザインパターンです。このパターンは、クラスが単一のインスタンスしか持たないようにし、その唯一のインスタンスにアクセスするための手法を提供します。以下に、シングルトンパターンの基本的な説明とRubyでのコード例を示します。

シングルトンパターンの特徴
  1. 単一のインスタンス:
    • シングルトンクラスは一つのインスタンスしか持ちません。
  2. グローバルアクセスポイント:
    • シングルトンインスタンスにアクセスするためのグローバルなアクセスポイントが提供されます。
  3. 共有リソース管理:
    • シングルトンパターンは、共有のリソースや設定にアクセスする場合に有用です。例えば、データベース接続やログ管理など。
Rubyでのシングルトンパターンの実装(1)
class SingletonClass
  # クラス変数で唯一のインスタンスを保持
  @@instance = nil

  # newメソッドをprivateにすることで、外部から直接インスタンス化できなくなる
  private_class_method :new

  # インスタンスにアクセスするためのメソッド
  def self.instance() @@instance ||= new end

  # その他のシングルトンクラスのメソッドやプロパティ
  def some_method() puts "Singleton instance method called" end
end

# シングルトンクラスのインスタンスを取得
singleton_instance = SingletonClass.instance
singleton_instance.some_method

この例では、SingletonClassクラスがシングルトンパターンを実現しています。instanceメソッドを通じて唯一のインスタンスにアクセスできます。newメソッドはprivate_class_methodでprivateになっており、外部から直接インスタンス化ができないようになっています。

Rubyには、シングルトンパターンをクラスで実践するために Singleton Mix-in が用意されています。

Rubyでのシングルトンパターンの実装(2)
require 'singleton'

class SingletonClass
  include Singleton

  # その他のシングルトンクラスのメソッドやプロパティ
  def some_method() puts "Singleton instance method called" end
end

# シングルトンクラスのインスタンスを取得
singleton_instance = SingletonClass.instance
singleton_instance.some_method

この例では、SingletonClassクラスがSingletonモジュールをincludeしています。これにより、SingletonClassのインスタンスはシングルトンとして扱われ、instanceメソッドで唯一のインスタンスにアクセスできます。

singletonモジュールを使用することで、シングルトンパターンの実装が簡潔になり、また必要なメソッドやプロパティを追加できます。シングルトンパターンは、共有リソースへのアクセスや設定管理などのシナリオで役立ちます。

このように実装されたシングルトンパターンでは、複数のインスタンスが作成されることを防ぎ、唯一の共有リソースへのアクセスを確実に制御できます。

Factory Method(ファクトリーメソッド)パターン

[編集]

Factory Methodパターンは、オブジェクトの生成をサブクラスに委譲し、具体的な生成プロセスをサブクラスで実装する手法です。これにより、生成されるオブジェクトの型をサブクラスによって動的に変更できます。Factory Methodは、生成するオブジェクトの種類に依存せず、柔軟性を提供するために使用されます。

Factory Method パターンの特徴
  1. 抽象クラスまたはインターフェース:
    • Factory Methodパターンでは、オブジェクトの生成を抽象クラスまたはインターフェースで定義します。
  2. サブクラスでの生成プロセス実装:
    • 具体的な生成プロセスはサブクラスに委譲され、サブクラスでそれぞれの生成方法が実装されます。
  3. 動的な型変更:
    • Factory Methodによって生成されるオブジェクトの型は、実行時にサブクラスの選択によって動的に変更できます。
RubyでのFactory Method パターンの実装
以下は、Factory Methodパターンの簡単なコード例です。
# Product(生成されるオブジェクト)の抽象クラス
class Product
  def operation() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# ConcreteProduct1(具体的な生成物1)
class ConcreteProduct1 < Product
  def operation() "Operation from ConcreteProduct1" end
end

# ConcreteProduct2(具体的な生成物2)
class ConcreteProduct2 < Product
  def operation() "Operation from ConcreteProduct2" end
end

# Creator(生成するオブジェクトの抽象クラス)
class Creator
  def factory_method() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end

  def some_operation()
    product = factory_method
    "Creator: #{product.operation}"
  end
end

# ConcreteCreator1(具体的な生成者1)
class ConcreteCreator1 < Creator
  def factory_method() ConcreteProduct1.new end
end

# ConcreteCreator2(具体的な生成者2)
class ConcreteCreator2 < Creator
  def factory_method() ConcreteProduct2.new end
end

# クライアントコード
def client_code(creator) creator.some_operation end

# 具体的な生成者1を利用する場合
creator1 = ConcreteCreator1.new
puts client_code(creator1)  # Output: "Creator: Operation from ConcreteProduct1"

# 具体的な生成者2を利用する場合
creator2 = ConcreteCreator2.new
puts client_code(creator2)  # Output: "Creator: Operation from ConcreteProduct2"

この例では、Productが生成されるオブジェクトを表す抽象クラスであり、ConcreteProduct1およびConcreteProduct2が具体的な生成物を表します。Creatorが生成するオブジェクトの抽象クラスであり、ConcreteCreator1およびConcreteCreator2が具体的な生成者を表します。クライアントコードは、具体的な生成者を選択して生成物を得ることができます。

Factory Methodパターンを使用することで、クライアントコードは生成物の種類に依存せず、柔軟に生成プロセスを変更できるメリットがあります。

Abstract Factory(抽象ファクトリー)パターン

[編集]

Abstract Factoryパターンは、関連する一連のオブジェクトを生成するためのインターフェースを提供し、一貫性を持たせます。異なるファクトリーを利用することで、異なるオブジェクトのファミリーを生成できます。このパターンは、オブジェクトが互いに関連しており、一緒に利用される場合に特に有用です。

Abstract Factory パターンの要素
  1. AbstractFactory(抽象ファクトリー):
    • オブジェクトの生成に関するインターフェースを定義します。関連する一連のオブジェクトを生成するメソッドを提供します。
  2. ConcreteFactory(具体的なファクトリー):
    • AbstractFactoryを実装し、具体的なオブジェクトの生成を行います。通常、同じファミリーのオブジェクトを生成します。
  3. AbstractProduct(抽象製品):
    • 生成されるオブジェクトの抽象クラスまたはインターフェースを定義します。
  4. ConcreteProduct(具体的な製品):
    • AbstractProductを実装し、具体的なオブジェクトのクラスを提供します。
  5. Client(クライアント):
    • Abstract Factoryを利用してオブジェクトを生成します。クライアントは具体的なファクトリーを意識せずにオブジェクトを取得できます。
Rubyでの Abstract Factory パターンの実装
以下は、Abstract Factoryパターンの簡単なコード例です。
# AbstractProduct
class Button
  def click() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# ConcreteProduct1
class WindowsButton < Button
  def click() "Windows button clicked" end
end

# ConcreteProduct2
class MacOSButton < Button
  def click() "MacOS button clicked" end
end

# AbstractProduct
class Checkbox
  def check() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# ConcreteProduct1
class WindowsCheckbox < Checkbox
  def check() "Windows checkbox checked" end
end

# ConcreteProduct2
class MacOSCheckbox < Checkbox
  def check() "MacOS checkbox checked" end
end

# AbstractFactory
class GUIFactory
  def create_button() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
  def create_checkbox() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# ConcreteFactory1
class WindowsFactory < GUIFactory
  def create_button() WindowsButton.new end
  def create_checkbox() WindowsCheckbox.new end
end

# ConcreteFactory2
class MacOSFactory < GUIFactory
  def create_button() MacOSButton.new end
  def create_checkbox() MacOSCheckbox.new end
end

# Client
class Application
  def initialize(factory)
    @button = factory.create_button
    @checkbox = factory.create_checkbox
  end

  def run()
    puts @button.click
    puts @checkbox.check
  end
end

# クライアントコード
windows_app = Application.new(WindowsFactory.new)
macos_app = Application.new(MacOSFactory.new)

windows_app.run
# Output:
# "Windows button clicked"
# "Windows checkbox checked"

macos_app.run
# Output:
# "MacOS button clicked"
# "MacOS checkbox checked"

この例では、WindowsおよびMacOS用のボタンとチェックボックスを生成するGUIFactoryが抽象ファクトリーとなります。具体的な製品はそれぞれWindowsButtonWindowsCheckboxMacOSButtonMacOSCheckboxです。Applicationクラスが抽象ファクトリーを通じてオブジェクトを生成し、クライアントコードが異なるプラットフォーム用のオブジェクトを利用できます。

Builder(ビルダー)パターン

[編集]

Builderパターンは、複雑なオブジェクトの構築プロセスを抽象化し、同じ構築プロセスを使用して異なる表現のオブジェクトを作成できるようにします。これにより、柔軟で再利用可能な構築手法を提供します。Builderパターンは、複雑なオブジェクトを一貫して構築する必要があり、同じ構築プロセスで異なるオブジェクトを生成する必要がある場合に有用です。

Builder パターンの要素
  1. Product(製品):
    • ビルダーで構築されるオブジェクトの表現を定義します。
  2. Builder(ビルダー):
    • 複雑なオブジェクトを構築するための抽象インターフェースを提供します。具体的なビルダーがこのインターフェースを実装します。
  3. ConcreteBuilder(具体的なビルダー):
    • Builderを実装し、製品の各部分の構築を担当します。ビルドされた製品を取得するメソッドも提供します。
  4. Director(ディレクター):
    • クライアントからの指示に基づいてビルダーを利用してオブジェクトを構築します。ビルダーの組み合わせ方を知っています。
Rubyでの Builder パターンの実装
以下は、Builderパターンの簡単なコード例です。
# Product
class Computer
  attr_accessor :cpu, :memory, :storage

  def to_s() "Computer with CPU: #{@cpu}, Memory: #{@memory}, Storage: #{@storage}" end
end

# Builder
class ComputerBuilder
  def build_cpu() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
  def build_memory() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
  def build_storage() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
  def computer() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# ConcreteBuilder
class HighPerformanceComputerBuilder < ComputerBuilder
  def initialize()    @computer         = Computer.new end
  def build_cpu()     @computer.cpu     = "High Performance CPU" end
  def build_memory()  @computer.memory  = "16GB RAM" end
  def build_storage() @computer.storage = "1TB SSD" end
  def computer()      @computer end
end

# Director
class ComputerEngineer
  def initialize(builder) @builder = builder end

  def assemble_computer()
    @builder.build_cpu
    @builder.build_memory
    @builder.build_storage
  end

  def get_computer() @builder.computer end
end

# クライアントコード
builder = HighPerformanceComputerBuilder.new
engineer = ComputerEngineer.new(builder)

engineer.assemble_computer
high_performance_computer = engineer.get_computer

puts high_performance_computer.to_s
# Output: "Computer with CPU: High Performance CPU, Memory: 16GB RAM, Storage: 1TB SSD"

この例では、Computerがビルダーパターンで構築される製品を表しています。ComputerBuilderがビルダーの抽象クラスであり、HighPerformanceComputerBuilderが具体的なビルダーです。ComputerEngineerがディレクターで、ビルダーを利用して製品を組み立てます。クライアントコードはディレクターを通じてビルダーを利用して製品を構築し、最終的な製品を取得します。

このように、Builderパターンを使用することで、複雑なオブジェクトの構築プロセスをカプセル化し、柔軟かつ再利用可能な方法で異なる表現のオブジェクトを生成できます。

Prototype(プロトタイプ)パターン

[編集]

Prototypeパターンは、オブジェクトのコピーを作成する手法を提供し、新しいオブジェクトを既存のオブジェクトを基にして生成できるようにします。これにより、オブジェクトの生成コストを削減できます。プロトタイプパターンは、同じようなオブジェクトが複数必要であり、それぞれのオブジェクトが少しだけ異なる場合に有用です。

Prototype パターンの要素
  1. Prototype(プロトタイプ):
    • オブジェクトのコピーを作成するためのインターフェースを提供します。
  2. ConcretePrototype(具体的なプロトタイプ):
    • Prototypeを実装し、オブジェクトのコピーを作成する具体的な実装を提供します。
  3. Client(クライアント):
    • プロトタイプを使用して新しいオブジェクトのコピーを作成します。
Rubyでの Prototype パターンの実装
以下は、Prototypeパターンの簡単なコード例です。
# Prototype
class Cloneable
  def clone() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# ConcretePrototype
class ConcreteCloneable < Cloneable
  attr_accessor :attribute

  def initialize(attribute) @attribute = attribute end
  def clone() self.class.new(@attribute) end
end

# Client
class ObjectCloner
  def initialize(prototype) @prototype = prototype end
  def clone_object() @prototype.clone end
end

# クライアントコード
original_object = ConcreteCloneable.new("Original Attribute")
cloner = ObjectCloner.new(original_object)

cloned_object = cloner.clone_object

puts "Original Object Attribute: #{original_object.attribute}"
puts "Cloned Object Attribute: #{cloned_object.attribute}"
# Output:
# Original Object Attribute: Original Attribute
# Cloned Object Attribute: Original Attribute

この例では、Cloneableがプロトタイプの抽象クラスを表し、ConcreteCloneableが具体的なプロトタイプを表しています。ObjectClonerがクライアントで、指定されたプロトタイプを使用して新しいオブジェクトのコピーを作成します。クライアントコードは、元のオブジェクトとクローンされたオブジェクトの属性を出力しています。

このようにプロトタイプパターンを使用することで、オブジェクトの生成コストを低減し、柔軟性を向上させることができます。

これらの生成デザインパターンは、オブジェクトの生成と初期化に関連する様々な課題に対処し、柔軟性や再利用性を向上させるために広く使用されています。それぞれのパターンは異なる状況や要件に対応し、効果的なソフトウェア設計を支援します。

構造(Structural)デザインパターン

[編集]

構造デザインパターンは、ソフトウェア設計においてクラスやオブジェクトの組み合わせを利用して大きな構造を形成する方法を提供します。これらのパターンは、ソフトウェアのコンポーネントを効果的に組み合わせ、機能や構造を拡張しやすくするための手法を提供します。

Adapter(アダプター)パターン

[編集]

アダプターパターンは、互換性のないインターフェースを持つクラス同士を連携させるための手法を提供します。これにより、既存のクラスを変更せずに連携できるようになります。アダプターは、既存のクラスと新しいクラスの仲介を行います。

クラスベースのアダプター
クラスベースのアダプターパターンでは、既存のクラスを継承して新しいクラスを作成します。
# 既存のクラス
class LegacySystem
  def specific_request() "Legacy System Request" end
end

# アダプタークラス
class Adapter < LegacySystem
  def adapted_request() "Adapter: #{specific_request}" end
end

# クライアントコード
adapter = Adapter.new
puts adapter.adapted_request
# Output: "Adapter: Legacy System Request"
オブジェクトベースのアダプター
オブジェクトベースのアダプターパターンでは、既存のクラスのオブジェクトを新しいクラスの内部で使用します。
# 既存のクラス
class LegacySystem
  def specific_request() "Legacy System Request" end
end

# アダプタークラス
class Adapter
  def initialize(legacy_system) @legacy_system = legacy_system end
  def adapted_request() "Adapter: #{@legacy_system.specific_request}" end
end

# クライアントコード
legacy_system = LegacySystem.new
adapter = Adapter.new(legacy_system)
puts adapter.adapted_request
# Output: "Adapter: Legacy System Request"

アダプターパターンは、既存のコードとの互換性を維持しながら新しいコードを導入する際に役立ちます。新しいクラスを既存のクラスと同じように振る舞わせることができ、クライアントコードは変更せずに利用できるようになります。

Bridge(ブリッジ)パターン

[編集]

ブリッジパターンは、抽象化と実装を別々に拡張できるようにし、これらの間に橋渡しを行う手法を提供します。これにより、異なる抽象化と実装を組み合わせて新しい機能を追加できます。ブリッジパターンは、抽象クラスと実装クラスを分離して、それらが独立して拡張できるようにします。

クラスベースのブリッジ
クラスベースのブリッジパターンでは、抽象クラスと実装クラスがそれぞれ独立して拡張されます。
# 実装層
class Implementation
  def operation_implementation() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# 具体的な実装クラス
class ConcreteImplementationA < Implementation
  def operation_implementation() "Concrete Implementation A" end
end

class ConcreteImplementationB < Implementation
  def operation_implementation() "Concrete Implementation B" end
end

# 抽象化
class Abstraction
  def initialize(implementation) @implementation = implementation end
  def operation() "Abstraction: #{implementation_operation}" end

  private

  def implementation_operation() @implementation.operation_implementation end
end

# クライアントコード
implementation_a = ConcreteImplementationA.new
abstraction_a = Abstraction.new(implementation_a)
puts abstraction_a.operation
# Output: "Abstraction: Concrete Implementation A"

implementation_b = ConcreteImplementationB.new
abstraction_b = Abstraction.new(implementation_b)
puts abstraction_b.operation
# Output: "Abstraction: Concrete Implementation B"
オブジェクトベースのブリッジ
オブジェクトベースのブリッジパターンでは、抽象クラスと実装クラスをそれぞれオブジェクトとして組み合わせます。
# 実装層
class Implementation
  def operation_implementation() raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" end
end

# 具体的な実装クラス
class ConcreteImplementationA < Implementation
  def operation_implementation() "Concrete Implementation A" end
end

class ConcreteImplementationB < Implementation
  def operation_implementation() "Concrete Implementation B" end
end

# 抽象化
class Abstraction
  def initialize(implementation) @implementation = implementation end
  def operation() "Abstraction: #{@implementation.operation_implementation}" end
end

# クライアントコード
implementation_a = ConcreteImplementationA.new
abstraction_a = Abstraction.new(implementation_a)
puts abstraction_a.operation
# Output: "Abstraction: Concrete Implementation A"

implementation_b = ConcreteImplementationB.new
abstraction_b = Abstraction.new(implementation_b)
puts abstraction_b.operation
# Output: "Abstraction: Concrete Implementation B"

ブリッジパターンは、抽象化と実装の組み合わせが複雑になる場合や、将来的な変更に備えて柔軟性を持たせる場合に有用です。

Composite(コンポジット)パターン

[編集]

コンポジットパターンは、オブジェクトとオブジェクトの構造を同一視し、個々のオブジェクトとその構造を再帰的に扱う手法を提供します。これにより、単一のオブジェクトと複数のオブジェクトの同一視が可能になります。このパターンは、個々のオブジェクトとオブジェクトのコンポジション(構造)を同じように扱い、クライアントが統一的に操作できるようにします。

特徴
  1. 同一視: コンポジットパターンでは、個々のオブジェクト(葉ノード)とオブジェクトの構造(複合ノード)を同じく扱います。これにより、単一のオブジェクトと複数のオブジェクトを同一視できます。
  2. 再帰的構造: 複合ノードは他の葉ノードや複合ノードを子に持つことができ、これが再帰的な構造を作ります。これにより、階層的で複雑な構造を表現できます。
  3. クライアントの簡略化: クライアントコードは、単一のオブジェクトとコンポジションを同じように扱うことができ、構造の詳細を気にせずに統一的な操作が可能です。
用途
  1. グラフィカルな構造: グラフィカルなツリーや図形の構造において、個々の要素とグループ(コンポジション)を同一視するのに適しています。
  2. ファイルシステム: ファイルやディレクトリといった構造を再帰的に扱う場合に利用されます。各要素はファイルかディレクトリかを同じように扱います。
  3. メニューシステム: GUIアプリケーションにおいて、メニュー項目とサブメニューを同様の要素として扱うのに適しています。
コード例
# コンポーネント
class Component
  def operation
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

# 葉ノード
class Leaf < Component
  def operation() "Leaf" end
end

# 複合ノード
class Composite < Component
  def initialize() @children = [] end
  def add(child) @children << child end
  def remove(child) @children.delete(child) end
  def operation()
    results = @children.map(&:operation).join(', ')
    "Composite: [#{results}]"
  end
end

# クライアントコード
leaf1 = Leaf.new
leaf2 = Leaf.new
composite = Composite.new

composite.add(leaf1)
composite.add(leaf2)

puts leaf1.operation
# Output: "Leaf"

puts composite.operation
# Output: "Composite: [Leaf, Leaf]"

この例では、Leafは葉ノードを、Compositeは複合ノードを表しています。クライアントコードは、葉ノードと複合ノードを同様に扱っています。

Decorator(デコレーター)パターン

[編集]

デコレーターパターンは、オブジェクトに新しい機能を動的に追加できるようにする手法を提供します。これにより、クラスを拡張する際に継承を使わずに機能を追加できます。デコレーターパターンは、既存のクラスを変更せずに新しい機能を追加する柔軟な手段を提供します。

特徴
  1. 柔軟な機能追加: デコレーターパターンは、既存のクラスを拡張せずに新しい機能を追加できます。これにより、クラスの変更が最小限に抑えられます。
  2. 再帰的なスタッキング: 複数のデコレーターを組み合わせて使用することができます。これにより、機能を再帰的にスタックすることが可能です。
  3. オープンクローズドの原則: 新しい機能は既存のコードを変更せずに追加できるため、オープンクローズドの原則をサポートします。
用途
  1. GUIコンポーネント: ウィンドウやボタンなどのGUIコンポーネントにおいて、外観や動作を動的に変更したい場合に利用されます。
  2. ファイルハンドリング: ファイルやストリームの処理において、データを暗号化する、圧縮するなどの機能を追加するのに適しています。
  3. ログ出力: ログ出力において、ログのフォーマットやレベルを動的に変更する際に利用されます。
コード例
# コンポーネント
class Component
  def operation() "Component" end
end

# デコレーター基底クラス
class Decorator < Component
  def initialize(component) @component = component end
  def operation() @component.operation end
end

# 具体的なデコレータークラス
class ConcreteDecoratorA < Decorator
  def operation() "ConcreteDecoratorA: [#{super}]" end
end

class ConcreteDecoratorB < Decorator
  def operation() "ConcreteDecoratorB: [#{super}]" end
end

# クライアントコード
component = Component.new
decorator_a = ConcreteDecoratorA.new(component)
decorator_b = ConcreteDecoratorB.new(decorator_a)

puts decorator_b.operation
# Output: "ConcreteDecoratorB: [ConcreteDecoratorA: [Component]]"

この例では、Componentが基本のコンポーネントを表し、Decoratorがデコレーターの基底クラスを、ConcreteDecoratorAConcreteDecoratorBが具体的なデコレータークラスを表しています。クライアントコードは、これらのデコレーターを組み合わせて新しい機能を追加しています。

Facade(ファサード)パターン

[編集]

ファサードパターンは、ソフトウェアデザインにおいて、サブシステムの一連のインターフェースを提供し、クライアントがサブシステムをより簡単に利用できるようにする手法を指します。このデザインパターンは、クライアントとサブシステムとの間に中間層を導入し、クライアントにとって使いやすい方法でサブシステムを操作できるようにします。これにより、クライアントはサブシステムの詳細に関与することなく、シンプルで一貫性のあるAPIを使用して機能を利用できます。

特徴
簡単なインターフェース提供
ファサードパターンは、サブシステム全体にわたる単一の簡潔なインターフェースを提供します。クライアントはこのファサードを通じてサブシステムを利用し、複雑な構造や相互作用を気にする必要がありません。
サブシステムの複雑性を隠蔽
ファサードはサブシステムの内部構造や複雑な操作をクライアントから隠蔽します。これにより、クライアントは必要な機能をシンプルなインターフェースを介して利用でき、サブシステムの詳細に気を取られることなくコードを記述できます。
カプセル化
サブシステムの各コンポーネントはファサードによってカプセル化され、外部からの直接アクセスを防ぎます。これにより、サブシステム内の変更が影響を及ぼす範囲が狭まります。
利点
シンプルな利用
ファサードを使用することで、クライアントは複雑なサブシステムを理解せずにシンプルなインターフェースを利用できます。これにより、プログラムの理解や保守が容易になります。
柔軟性と拡張性
サブシステムの変更や新しい機能の追加があっても、ファサードを変更するだけで済みます。これにより、システム全体の柔軟性と拡張性が向上します。
クライアントとサブシステムの分離
クライアントはサブシステムの詳細について知る必要がなくなります。ファサードによって、クライアントとサブシステムが疎結合になり、それぞれの変更が影響を及ぼしにくくなります。

音声処理システムのファサードパターンの例を示します。

# サブシステムの一部
class AudioPlayer
  def play(file) puts "Playing audio file: #{file}" end
end

class AudioConverter
  def convert(file, format) puts "Converting audio file #{file} to #{format}" end
end

class AudioMixer
  def mix(files) puts "Mixing audio files: #{files.join(', ')}" end
end

# ファサード
class AudioFacade
  def initialize() 
    @player = AudioPlayer.new
    @converter = AudioConverter.new
    @mixer = AudioMixer.new
  end

  def play_audio(file) @player.play(file) end
  def convert_audio(file, format) @converter.convert(file, format) end
  def mix_audio(files) @mixer.mix(files) end
end

# クライアント
if __FILE__ == $0
  audio_facade = AudioFacade.new

  # クライアントはファサードを通してサブシステムを利用
  audio_facade.play_audio("song.mp3")
  audio_facade.convert_audio("song.mp3", "wav")
  audio_facade.mix_audio(["song1.wav", "song2.wav"])
end

この例では、AudioFacadeがファサードとして機能し、クライアントはこのファサードを介して音声処理システムを利用します。クライアントはサブシステムの詳細について心配する必要がなく、ファサードが提供する単純で使いやすいインターフェースを通じて操作を行います。

Flyweight(フライウェイト)パターン

[編集]

フライウェイトパターンは、メモリ使用量を最適化するために多くの類似したオブジェクトを共有するデザインパターンです。このパターンでは、共有される部分とインスタンス固有の部分を分離し、共有される部分を再利用することで効率的なリソース利用を実現します。特に大規模なオブジェクトの集合を扱う場合に威力を発揮します。

特徴
共有
フライウェイトは、同じ状態を持つ複数のオブジェクトが共有されることによってメモリ使用量を最適化します。これにより、大量のオブジェクトが同じデータを保持する場合に効果を発揮します。
状態の内外部化
フライウェイトは、内部状態(共有可能な部分)と外部状態(インスタンス固有の部分)を分離します。共有可能な部分は共有され、インスタンス固有の部分はインスタンスごとに持たれます。
イミュータブル
フライウェイトの内部状態は通常、イミュータブルであることが好まれます。これにより、共有状態が変更されることなく安全に共有できます。
利点
メモリ効率
類似のオブジェクトが共有されることで、メモリ使用量が減少し、効率的なリソース利用が可能になります。
インスタンスの生成コスト低減
インスタンスの生成がコストが高い場合、同じデータを持つオブジェクトを共有することで生成コストを低減できます。
変更容易性
共有状態が変更される場合、それが全ての参照先に影響を与えるため、変更が容易になります。
フライウェイトパターンのコード例
# フライウェイト
class SharedData
  attr_reader :data

  def initialize(data) @data = data end
end

# フライウェイトファクトリ
class FlyweightFactory
  def initialize() @flyweights = {} end
  def get_flyweight(key) @flyweights[key] ||= SharedData.new(key) end
  def flyweights_count() @flyweights.length end
end

# クライアント
class Client
  def initialize(factory)
    @factory = factory
    @flyweights = []
  end

  def get_or_create_flyweight(key)
    flyweight = @factory.get_flyweight(key)
    @flyweights.push(flyweight)
  end

  def report
    puts "Number of unique flyweights: #{flyweights_count}"
  end

  private

  def flyweights_count()
    @flyweights.map(&:object_id).uniq.length
  end
end

# クライアントの使用例
if __FILE__ == $0
  factory = FlyweightFactory.new
  client = Client.new(factory)

  client.get_or_create_flyweight("A")
  client.get_or_create_flyweight("B")
  client.get_or_create_flyweight("A")

  client.report
  # 出力: Number of unique flyweights: 2
end

この例では、FlyweightFactorySharedDataのインスタンスを共有し、Clientがそれを利用しています。Clientは同じデータを持つSharedDataを共有することで、効率的にメモリを利用しています。

Proxy(プロキシ)パターン

[編集]

プロキシパターンは、別のオブジェクトにアクセスするためのプレースホルダーを提供し、そのアクセスを制御するデザインパターンです。主にアクセス制御、キャッシュ、遠隔アクセス、ログなどの機能を追加したり、本物のオブジェクトを生成する遅延初期化を行ったりする場合に使用されます。

特徴
制御機能の追加
プロキシは本物のオブジェクトへのアクセスを制御するため、特定の機能を追加することができます。例えば、アクセス制御、キャッシュ、遠隔アクセス、ログなど。
遅延初期化
プロキシは本物のオブジェクトの生成を遅延させることができます。本物のオブジェクトが本当に必要になるまで生成を遅らせ、リソースの効率的な利用が可能です。
単純なインターフェース
プロキシは本物のオブジェクトと同じインターフェースを提供します。クライアントはプロキシと本物のオブジェクトを同様に扱うことができます。
利点
アクセス制御
プロキシは本物のオブジェクトへのアクセスを制御できるため、セキュリティや権限の管理が容易になります。
遅延初期化
本物のオブジェクトの生成を遅延させることで、リソースを効率的に利用できます。
透過性
プロキシは本物のオブジェクトと同じインターフェースを提供するため、クライアントはプロキシと本物のオブジェクトを同様に扱うことができます。
プロキシパターンのコード例
# 本物のオブジェクト
class RealSubject
  def request() puts "RealSubject handles the request" end
end

# プロキシ
class Proxy
  def initialize(real_subject) @real_subject = real_subject end

  def request
    check_access
    @real_subject.request
    log_request
  end

  private

  def check_access
    puts "Proxy: Checking access before making request."
    # アクセス制御のロジックをここに実装
  end

  def log_request
    puts "Proxy: Logging the request."
    # ログの記録ロジックをここに実装
  end
end

# クライアント
class Client
  def initialize(subject) @subject = subject end
  def make_request() @subject.request end
end

# クライアントの使用例
if __FILE__ == $0
  real_subject = RealSubject.new
  proxy = Proxy.new(real_subject)

  client = Client.new(proxy)
  client.make_request
  # 出力:
  # Proxy: Checking access before making request.
  # RealSubject handles the request
  # Proxy: Logging the request.
end

この例では、ProxyRealSubjectへのアクセスを制御し、リクエスト前にアクセスをチェックし、リクエスト後にログを記録します。クライアントはプロキシと本物のオブジェクトを同様に扱うことができます。

これらの構造デザインパターンは、クラスやオブジェクトの構造に関する課題に対処し、ソフトウェア設計の柔軟性や拡張性を向上させるために広く使用されています。それぞれのパターンは特定の課題に焦点を当て、適切な状況で使用されます。

振る舞い(Behavioral)デザインパターン

[編集]

振る舞いデザインパターンは、オブジェクト間の相互作用、責任の分散、およびアルゴリズムの柔軟な実装に焦点を当てたソフトウェア設計手法です。これらのパターンは、システムの結合度を低減し、拡張性と保守性を向上させることを目的としています。以下に、代表的な振る舞いデザインパターンの詳細と実践的なRubyでのコード例を示します。

Chain of Responsibility(責任の連鎖)パターン

[編集]

Chain of Responsibilityパターンは、複数のオブジェクトがリクエストを処理する柔軟な連鎖メカニズムを提供します。リクエストは連鎖を順次渡され、各オブジェクトは自身で処理可能な場合はそれを処理し、できない場合は次のオブジェクトに委譲します。

# 抽象ハンドラクラス
class Handler
  attr_accessor :next_handler

  def handle_request(request)
    handle(request) || forward_to_next(request)
  end

  private

  def handle(request)
    raise NotImplementedError, "#{self.class} has not implemented method 'handle'"
  end

  def forward_to_next(request)
    @next_handler&.handle_request(request)
  end
end

# 具体的なハンドラクラス
class LogHandler < Handler
  private

  def handle(request)
    if request.type == :log
      puts "ログを記録: #{request.content}"
      true
    else
      false
    end
  end
end

class EmailHandler < Handler
  private

  def handle(request)
    if request.type == :email
      puts "メールを送信: #{request.content}"
      true
    else
      false
    end
  end
end

class Request
  attr_reader :type, :content

  def initialize(type, content)
    @type = type
    @content = content
  end
end

# クライアントコード
log_handler = LogHandler.new
email_handler = EmailHandler.new
log_handler.next_handler = email_handler

log_handler.handle_request(Request.new(:log, "システムエラーが発生"))
log_handler.handle_request(Request.new(:email, "重要な通知"))

Command(コマンド)パターン

[編集]

Commandパターンは、リクエストや操作をオブジェクトとしてカプセル化し、クライアントと処理の間の疎結合を実現します。これにより、コマンドの遅延実行、キュー、アンドゥ/リドゥ操作が容易に実装できます。

# レシーバークラス
class TextEditor
  def copy  = puts "テキストをコピー"
  def paste = puts "テキストをペースト"
  def cut   = puts "テキストをカット"
end

# 抽象コマンドインターフェース
class Command
  def execute = raise NotImplementedError
  def undo    = raise NotImplementedError
  end
end

# 具体的なコマンドクラス
class CopyCommand < Command
  def initialize(editor)
    @editor = editor
  end

  def execute = @editor.copy
  def undo
    # コピー操作の取り消し(この例では特に何もしない)
  end
end

# クライアントコード
editor = TextEditor.new
copy_cmd = CopyCommand.new(editor)
copy_cmd.execute

Interpreter(インタープリター)パターン

[編集]

Interpreterパターンは、特定のドメイン固有言語(DSL)の文法を解釈および実行するためのクラス群を提供します。

# 抽象式クラス
class Expression
  def interpret(context)
    raise NotImplementedError
  end
end

# 端末式クラス
class NumberExpression < Expression
  def initialize(number)
    @number = number
  end

  def interpret(context)
    context[@number.to_s]
  end
end

# 非端末式クラス
class AddExpression < Expression
  def initialize(left, right)
    @left = left
    @right = right
  end

  def interpret(context)
    @left.interpret(context) + @right.interpret(context)
  end
end

# クライアントコード
context = { '1' => 10, '2' => 20, '3' => 30 }
expression = AddExpression.new(
  NumberExpression.new('1'),
  AddExpression.new(NumberExpression.new('2'), NumberExpression.new('3'))
)
result = expression.interpret(context)
puts "計算結果: #{result}"  # 出力: 計算結果: 60

Iterator(イテレータ)パターン

[編集]

Iterator(イテレータ)パターンは、オブジェクトの要素に順次アクセスするための方法を提供します。これにより、コレクション内の要素を順番にアクセスするための標準的な手法を提供し、同時にコレクションの内部構造を隠蔽します。

class CustomCollection
  include Enumerable

  def initialize()   = @items = []
  def add_item(item) = @items << item
  def each(&block)   = @items.each(&block)
end

collection = CustomCollection.new
collection.add_item("りんご")
collection.add_item("バナナ")
collection.add_item("オレンジ")

collection.each { |item| puts item }

Mediator(仲介者)パターン

[編集]

Mediator(仲介者)パターンは、オブジェクト間の相互作用をカプセル化し、オブジェクトが直接通信するのではなく、中央の仲介者を通じて通信する手法を提供します。これにより、オブジェクト間の結合度を低くし、変更に強いシステムを構築できます。

class ChatMediator
  def initialize
    @users = []
  end

  def add_user(user)
    @users << user
  end

  def send_message(message, sender)
    @users.each do |user|
      user.receive(message) unless user == sender
    end
  end
end

class User
  attr_reader :name
  attr_writer :mediator

  def initialize(name, mediator)
    @name = name
    @mediator = mediator
    mediator.add_user(self)
  end

  def send(message)
    puts "#{@name}: #{message}を送信"
    @mediator.send_message(message, self)
  end

  def receive(message)
    puts "#{@name}: #{message}を受信"
  end
end

Memento(メメント)パターン

[編集]

Memento(メメント)パターンは、オブジェクトの状態を保存し、後で復元できるようにするパターンです。

class Originator
  attr_accessor :state

  def create_memento
    Memento.new(@state)
  end

  def restore_from_memento(memento)
    @state = memento.state
  end
end

class Memento
  attr_reader :state

  def initialize(state)
    @state = state
  end
end

class Caretaker
  def initialize
    @mementos = []
  end

  def add_memento(memento)
    @mementos << memento
  end

  def get_memento(index)
    @mementos[index]
  end
end

# 使用例
editor = Originator.new
history = Caretaker.new

editor.state = "初期状態"
history.add_memento(editor.create_memento)

editor.state = "変更後の状態"
history.add_memento(editor.create_memento)

editor.state = "最終状態"

# 以前の状態に戻す
editor.restore_from_memento(history.get_memento(1))
puts editor.state  # "変更後の状態"

Observer(観察者)パターン

[編集]

Observer(観察者)パターンは、オブジェクトの状態変化を他のオブジェクトに通知するパターンです。

class Subject
  def initialize
    @observers = []
  end

  def add_observer(observer)
    @observers << observer
  end

  def remove_observer(observer)
    @observers.delete(observer)
  end

  def notify_observers(data)
    @observers.each { |observer| observer.update(data) }
  end
end

class WeatherStation < Subject
  def initialize
    super
    @temperature = 0
  end

  def temperature=(new_temperature)
    @temperature = new_temperature
    notify_observers(@temperature)
  end
end

class Observer
  def update(temperature)
    raise NotImplementedError, "#{self.class} must implement update"
  end
end

class TemperatureDisplay < Observer
  def update(temperature)
    puts "温度ディスプレイ: #{temperature}度"
  end
end

class Heater < Observer
  def update(temperature)
    if temperature < 20
      puts "ヒーターをONにします"
    else
      puts "ヒーターをOFFにします"
    end
  end
end

# 使用例
weather_station = WeatherStation.new
display = TemperatureDisplay.new
heater = Heater.new

weather_station.add_observer(display)
weather_station.add_observer(heater)

weather_station.temperature = 15
weather_station.temperature = 25

State(状態)パターン

[編集]

State(状態)パターンは、オブジェクトの内部状態が変化したときに、そのオブジェクトの振る舞いも動的に変更できるようにするパターンです。

class TrafficLight
  attr_accessor :state

  def initialize
    @state = RedState.new
  end

  def change
    @state.change(self)
  end

  def current_color
    @state.color
  end
end

class TrafficLightState
  def change(traffic_light)
    raise NotImplementedError, "#{self.class} has not implemented method 'change'"
  end

  def color
    raise NotImplementedError, "#{self.class} has not implemented method 'color'"
  end
end

class RedState < TrafficLightState
  def change(traffic_light)
    puts "赤から黄色に変更"
    traffic_light.state = YellowState.new
  end

  def color
    "赤"
  end
end

class YellowState < TrafficLightState
  def change(traffic_light)
    puts "黄色から緑に変更"
    traffic_light.state = GreenState.new
  end

  def color
    "黄色"
  end
end

class GreenState < TrafficLightState
  def change(traffic_light)
    puts "緑から赤に変更"
    traffic_light.state = RedState.new
  end

  def color
    "緑"
  end
end

# 使用例
traffic_light = TrafficLight.new
3.times do
  puts "現在の色: #{traffic_light.current_color}"
  traffic_light.change
end

Strategy(戦略)パターン

[編集]

Strategy(戦略)パターンは、アルゴリズムを実行時に切り替えられるようにするパターンです。

class Sorter
  attr_writer :strategy

  def initialize(strategy)
    @strategy = strategy
  end

  def sort(data)
    @strategy.sort(data)
  end
end

class SortStrategy
  def sort(data)
    raise NotImplementedError, "#{self.class} has not implemented method 'sort'"
  end
end

class BubbleSort < SortStrategy
  def sort(data)
    puts "バブルソートを実行"
    data.sort
  end
end

class QuickSort < SortStrategy
  def sort(data)
    puts "クイックソートを実行"
    data.sort
  end
end

# 使用例
data = [5, 2, 9, 1, 7]
sorter = Sorter.new(BubbleSort.new)
puts sorter.sort(data)

sorter.strategy = QuickSort.new
puts sorter.sort(data)

Template Method(テンプレートメソッド)パターン

[編集]

Template Method(テンプレートメソッド)パターンは、アルゴリズムの骨格を定義し、一部の手順をサブクラスで実装できるようにするパターンです。

class DataMiner
  def mine
    open_file
    extract_data
    parse_data
    analyze_data
    send_report
    close_file
  end

  private

  def open_file
    raise NotImplementedError, "#{self.class} must implement open_file"
  end

  def extract_data
    raise NotImplementedError, "#{self.class} must implement extract_data"
  end

  def parse_data
    raise NotImplementedError, "#{self.class} must implement parse_data"
  end

  def analyze_data
    puts "共通の分析処理を実行"
  end

  def send_report
    puts "レポートを送信"
  end

  def close_file
    puts "ファイルをクローズ"
  end
end

class PDFDataMiner < DataMiner
  private

  def open_file
    puts "PDFファイルを開く"
  end

  def extract_data
    puts "PDFからデータを抽出"
  end

  def parse_data
    puts "PDFデータをパース"
  end
end

class CSVDataMiner < DataMiner
  private

  def open_file
    puts "CSVファイルを開く"
  end

  def extract_data
    puts "CSVからデータを抽出"
  end

  def parse_data
    puts "CSVデータをパース"
  end
end

# 使用例
pdf_miner = PDFDataMiner.new
pdf_miner.mine

csv_miner = CSVDataMiner.new
csv_miner.mine

Visitor(訪問者)パターン

[編集]

Visitor(訪問者)パターン:データ構造と処理を分離し、新しい操作を追加しやすくするパターンです。

module ShapeVisitor
  def visit_circle(circle)
    raise NotImplementedError, "#{self.class} must implement visit_circle"
  end

  def visit_rectangle(rectangle)
    raise NotImplementedError, "#{self.class} must implement visit_rectangle"
  end
end

class Shape
  def accept(visitor)
    raise NotImplementedError, "#{self.class} must implement accept"
  end
end

class Circle < Shape
  attr_reader :radius

  def initialize(radius)
    @radius = radius
  end

  def accept(visitor)
    visitor.visit_circle(self)
  end
end

class Rectangle < Shape
  attr_reader :width, :height

  def initialize(width, height)
    @width = width
    @height = height
  end

  def accept(visitor)
    visitor.visit_rectangle(self)
  end
end

class AreaCalculatorVisitor
  include ShapeVisitor

  def visit_circle(circle)
    Math::PI * circle.radius ** 2
  end

  def visit_rectangle(rectangle)
    rectangle.width * rectangle.height
  end
end

class XMLExportVisitor
  include ShapeVisitor

  def visit_circle(circle)
    "<circle radius='#{circle.radius}'/>"
  end

  def visit_rectangle(rectangle)
    "<rectangle width='#{rectangle.width}' height='#{rectangle.height}'/>"
  end
end

# 使用例
shapes = [Circle.new(5), Rectangle.new(3, 4)]

area_calculator = AreaCalculatorVisitor.new
xml_exporter = XMLExportVisitor.new

shapes.each do |shape|
  puts "面積: #{shape.accept(area_calculator)}"
  puts "XML: #{shape.accept(xml_exporter)}"
end
これらのデザインパターンは、ソフトウェア設計における複雑な問題に対する洗練された解決策を提供します。各パターンは特定の設計上の課題に焦点を当て、コードの柔軟性、拡張性、および保守性を向上させるための強力な手法を示しています。

あとがき

[編集]

デザインパターンは、現代のソフトウェアエンジニアリングにおいて、複雑な設計課題を解決するための知的な道具箱と言えるでしょう。本書を通じて、デザインパターンの本質的な概念と、その実践的な適用方法について理解を深めることができたはずです。

デザインパターンは、単なる抽象的な概念ではなく、実際のソフトウェア開発において具体的な価値をもたらす強力な設計手法です。これらのパターンを理解し、適切に活用することで、以下のような重要な開発上の利点を得ることができます:

  • コードの再利用性の向上
  • システムの保守性と拡張性の改善
  • オブジェクト間の疎結合の実現
  • 共通の設計語彙の確立
  • 複雑性の管理と抽象化

デザインパターンの真の力は、机上の理論ではなく、実践的な開発経験から生まれます。理論を学ぶことも重要ですが、実際のプロジェクトで試行錯誤しながらパターンを適用し、その効果と限界を理解することが、真の習熟への道となります。

パターンの適用において最も重要なのは、「万能の解決策」を求めないことです。各デザインパターンは、特定の文脈や課題に対して最適化されています。機械的に適用するのではなく、プロジェクトの具体的な要件と、コードの長期的な進化を常に意識することが求められます。

熟練のソフトウェアエンジニアは、デザインパターンを単なるテンプレートとしてではなく、柔軟な思考のためのツールキットとして捉えます。パターンは、創造的な問題解決のためのインスピレーションであり、盲目的に従うべき教義ではありません。

本書を通じて、デザインパターンへの探求を始めた読者の皆さんへ、一つの励ましの言葉を贈りたいと思います。デザインパターンの学習は、ソフトウェア設計における継続的な成長の旅です。失敗を恐れず、常に好奇心を持ち、新たな解決策を探求し続けてください。

今日学んだパターンは、明日の革新的なソリューションへの第一歩となるでしょう。デザインパターンとともに、より優れたソフトウェアを創造する旅に、皆さんが情熱と創造性を注ぐことを心から願っています。

この本が、読者の皆さんのソフトウェア設計スキルへの探求において、一つの道標となれば幸いです。デザインパターンは終着点ではなく、新たな可能性への入り口なのです。

脚註

[編集]