デザインパターン
コンピューターサイエンスにおけるデザインパターンは、ソフトウェア設計において再利用可能な解決策の一般的なモデルやテンプレートです。これらのパターンは、共通の課題や問題に対処するために繰り返し使われ、設計の柔軟性、拡張性、保守性を向上させます。デザインパターンは、ソフトウェアエンジニアリングのコミュニティで広く受け入れられており、GoF(Gang of Four)による "Design Patterns: Elements Of Reusable Object-Oriented Software"(邦題『オブジェクト指向における再利用のためのデザインパターン』)などの文献で有名です。
デザインパターンの主な特徴は以下の通りです:
- 再利用性
- デザインパターンは、共通の設計上の問題に対処するための汎用的な解決策を提供するため、再利用が容易です。同じまたは類似の課題に対して何度も設計を行う必要がなくなります。
- 柔軟性
- デザインパターンは柔軟性をもたらし、変更が必要な場合にも容易に対応できるような設計を促進します。新しい要件や機能の追加があっても、デザインパターンを利用することで変更の影響を最小限に抑えられます。
- 共通の語彙
- デザインパターンは、ソフトウェア開発者やエンジニアの間で共通の語彙を提供します。これにより、チーム内でのコミュニケーションが向上し、設計の理解が容易になります。
- ソフトウェアアーキテクチャ
- デザインパターンはソフトウェアアーキテクチャを構築するための基本的な構成要素として使われます。これにより、ソフトウェアの構造が理解しやすく、拡張が容易になります。
一部の有名なデザインパターンには、Singleton(単一のインスタンスを保持するパターン)、Factory Method(ファクトリークラスがオブジェクトの生成を担当するパターン)、Observer(オブジェクトの状態変化を監視するパターン)などがあります。これらのパターンはソフトウェア設計の中で広く利用され、開発者が共通の問題に対してより効果的にアプローチできるようになります。
デザインパターンの分類と概要
[編集]デザインパターンは、ゴフ(Gang of Four)によって提案された23のパターンを、以下の3つの大きなカテゴリに分類できます。それぞれのカテゴリには、ソフトウェア開発における特定の課題に対処するためのパターンが含まれています。
生成(Creational)デザインパターン
[編集]生成デザインパターンは、オブジェクトの生成に焦点を当て、オブジェクトの生成と構成に関する柔軟で再利用可能な手法を提供します。これらのパターンは、インスタンスの生成方法やクラスの初期化に関する課題に対処し、アプリケーションの柔軟性と保守性を向上させます。
Singleton(シングルトン)パターン
[編集]シングルトンパターンは、アプリケーション内で唯一のインスタンスを持つように設計されたデザインパターンです。このパターンは、クラスが単一のインスタンスしか持たないようにし、その唯一のインスタンスにアクセスするための手法を提供します。以下に、シングルトンパターンの基本的な説明とRubyでのコード例を示します。
- シングルトンパターンの特徴
- 単一のインスタンス:
- シングルトンクラスは一つのインスタンスしか持ちません。
- グローバルアクセスポイント:
- シングルトンインスタンスにアクセスするためのグローバルなアクセスポイントが提供されます。
- 共有リソース管理:
- シングルトンパターンは、共有のリソースや設定にアクセスする場合に有用です。例えば、データベース接続やログ管理など。
- 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 パターンの特徴
- 抽象クラスまたはインターフェース:
- Factory Methodパターンでは、オブジェクトの生成を抽象クラスまたはインターフェースで定義します。
- サブクラスでの生成プロセス実装:
- 具体的な生成プロセスはサブクラスに委譲され、サブクラスでそれぞれの生成方法が実装されます。
- 動的な型変更:
- 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 パターンの要素
- AbstractFactory(抽象ファクトリー):
- オブジェクトの生成に関するインターフェースを定義します。関連する一連のオブジェクトを生成するメソッドを提供します。
- ConcreteFactory(具体的なファクトリー):
AbstractFactory
を実装し、具体的なオブジェクトの生成を行います。通常、同じファミリーのオブジェクトを生成します。
- AbstractProduct(抽象製品):
- 生成されるオブジェクトの抽象クラスまたはインターフェースを定義します。
- ConcreteProduct(具体的な製品):
AbstractProduct
を実装し、具体的なオブジェクトのクラスを提供します。
- 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
が抽象ファクトリーとなります。具体的な製品はそれぞれWindowsButton
、WindowsCheckbox
、MacOSButton
、MacOSCheckbox
です。Application
クラスが抽象ファクトリーを通じてオブジェクトを生成し、クライアントコードが異なるプラットフォーム用のオブジェクトを利用できます。
Builder(ビルダー)パターン
[編集]Builderパターンは、複雑なオブジェクトの構築プロセスを抽象化し、同じ構築プロセスを使用して異なる表現のオブジェクトを作成できるようにします。これにより、柔軟で再利用可能な構築手法を提供します。Builderパターンは、複雑なオブジェクトを一貫して構築する必要があり、同じ構築プロセスで異なるオブジェクトを生成する必要がある場合に有用です。
- Builder パターンの要素
- Product(製品):
- ビルダーで構築されるオブジェクトの表現を定義します。
- Builder(ビルダー):
- 複雑なオブジェクトを構築するための抽象インターフェースを提供します。具体的なビルダーがこのインターフェースを実装します。
- ConcreteBuilder(具体的なビルダー):
- Builderを実装し、製品の各部分の構築を担当します。ビルドされた製品を取得するメソッドも提供します。
- 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 パターンの要素
- Prototype(プロトタイプ):
- オブジェクトのコピーを作成するためのインターフェースを提供します。
- ConcretePrototype(具体的なプロトタイプ):
Prototype
を実装し、オブジェクトのコピーを作成する具体的な実装を提供します。
- 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(コンポジット)パターン
[編集]コンポジットパターンは、オブジェクトとオブジェクトの構造を同一視し、個々のオブジェクトとその構造を再帰的に扱う手法を提供します。これにより、単一のオブジェクトと複数のオブジェクトの同一視が可能になります。このパターンは、個々のオブジェクトとオブジェクトのコンポジション(構造)を同じように扱い、クライアントが統一的に操作できるようにします。
- 特徴
- 同一視: コンポジットパターンでは、個々のオブジェクト(葉ノード)とオブジェクトの構造(複合ノード)を同じく扱います。これにより、単一のオブジェクトと複数のオブジェクトを同一視できます。
- 再帰的構造: 複合ノードは他の葉ノードや複合ノードを子に持つことができ、これが再帰的な構造を作ります。これにより、階層的で複雑な構造を表現できます。
- クライアントの簡略化: クライアントコードは、単一のオブジェクトとコンポジションを同じように扱うことができ、構造の詳細を気にせずに統一的な操作が可能です。
- 用途
- グラフィカルな構造: グラフィカルなツリーや図形の構造において、個々の要素とグループ(コンポジション)を同一視するのに適しています。
- ファイルシステム: ファイルやディレクトリといった構造を再帰的に扱う場合に利用されます。各要素はファイルかディレクトリかを同じように扱います。
- メニューシステム: 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(デコレーター)パターン
[編集]デコレーターパターンは、オブジェクトに新しい機能を動的に追加できるようにする手法を提供します。これにより、クラスを拡張する際に継承を使わずに機能を追加できます。デコレーターパターンは、既存のクラスを変更せずに新しい機能を追加する柔軟な手段を提供します。
- 特徴
- 柔軟な機能追加: デコレーターパターンは、既存のクラスを拡張せずに新しい機能を追加できます。これにより、クラスの変更が最小限に抑えられます。
- 再帰的なスタッキング: 複数のデコレーターを組み合わせて使用することができます。これにより、機能を再帰的にスタックすることが可能です。
- オープンクローズドの原則: 新しい機能は既存のコードを変更せずに追加できるため、オープンクローズドの原則をサポートします。
- 用途
- GUIコンポーネント: ウィンドウやボタンなどのGUIコンポーネントにおいて、外観や動作を動的に変更したい場合に利用されます。
- ファイルハンドリング: ファイルやストリームの処理において、データを暗号化する、圧縮するなどの機能を追加するのに適しています。
- ログ出力: ログ出力において、ログのフォーマットやレベルを動的に変更する際に利用されます。
- コード例
# コンポーネント 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
がデコレーターの基底クラスを、ConcreteDecoratorA
とConcreteDecoratorB
が具体的なデコレータークラスを表しています。クライアントコードは、これらのデコレーターを組み合わせて新しい機能を追加しています。
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
この例では、FlyweightFactory
がSharedData
のインスタンスを共有し、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
この例では、Proxy
がRealSubject
へのアクセスを制御し、リクエスト前にアクセスをチェックし、リクエスト後にログを記録します。クライアントはプロキシと本物のオブジェクトを同様に扱うことができます。
- これらの構造デザインパターンは、クラスやオブジェクトの構造に関する課題に対処し、ソフトウェア設計の柔軟性や拡張性を向上させるために広く使用されています。それぞれのパターンは特定の課題に焦点を当て、適切な状況で使用されます。
振る舞い(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
- これらのデザインパターンは、ソフトウェア設計における複雑な問題に対する洗練された解決策を提供します。各パターンは特定の設計上の課題に焦点を当て、コードの柔軟性、拡張性、および保守性を向上させるための強力な手法を示しています。
あとがき
[編集]デザインパターンは、現代のソフトウェアエンジニアリングにおいて、複雑な設計課題を解決するための知的な道具箱と言えるでしょう。本書を通じて、デザインパターンの本質的な概念と、その実践的な適用方法について理解を深めることができたはずです。
デザインパターンは、単なる抽象的な概念ではなく、実際のソフトウェア開発において具体的な価値をもたらす強力な設計手法です。これらのパターンを理解し、適切に活用することで、以下のような重要な開発上の利点を得ることができます:
- コードの再利用性の向上
- システムの保守性と拡張性の改善
- オブジェクト間の疎結合の実現
- 共通の設計語彙の確立
- 複雑性の管理と抽象化
デザインパターンの真の力は、机上の理論ではなく、実践的な開発経験から生まれます。理論を学ぶことも重要ですが、実際のプロジェクトで試行錯誤しながらパターンを適用し、その効果と限界を理解することが、真の習熟への道となります。
パターンの適用において最も重要なのは、「万能の解決策」を求めないことです。各デザインパターンは、特定の文脈や課題に対して最適化されています。機械的に適用するのではなく、プロジェクトの具体的な要件と、コードの長期的な進化を常に意識することが求められます。
熟練のソフトウェアエンジニアは、デザインパターンを単なるテンプレートとしてではなく、柔軟な思考のためのツールキットとして捉えます。パターンは、創造的な問題解決のためのインスピレーションであり、盲目的に従うべき教義ではありません。
本書を通じて、デザインパターンへの探求を始めた読者の皆さんへ、一つの励ましの言葉を贈りたいと思います。デザインパターンの学習は、ソフトウェア設計における継続的な成長の旅です。失敗を恐れず、常に好奇心を持ち、新たな解決策を探求し続けてください。
今日学んだパターンは、明日の革新的なソリューションへの第一歩となるでしょう。デザインパターンとともに、より優れたソフトウェアを創造する旅に、皆さんが情熱と創造性を注ぐことを心から願っています。
- この本が、読者の皆さんのソフトウェア設計スキルへの探求において、一つの道標となれば幸いです。デザインパターンは終着点ではなく、新たな可能性への入り口なのです。