はじめに
あなたは毎日、無意識のうちに様々な「パターン」を使って生活しています。朝起きてから夜寝るまで、効率的に物事を進めるために、経験から得た知恵を活用しているはずです。
プログラミングの世界にも、そんな「生活の知恵」のようなものがあります。それが「デザインパターン」です。
デザインパターンとは、プログラミングでよく遭遇する問題に対する、テスト済みの効果的な解決策のことです。これらを学ぶことで、効率的で保守性の高いコードを書くことができます。
この記事では、デザインパターンの基本的な概念を、誰もが経験する「朝の準備」という日常のシーンに例えて説明します。
デザインパターンの3つの主要カテゴリー
デザインパターンは主に以下の3つのカテゴリーに分類されます:
- 生成に関するパターン(Creational Patterns)
- 構造に関するパターン(Structural Patterns)
- 振る舞いに関するパターン(Behavioral Patterns)
それぞれのカテゴリーを、朝の準備のシーンを使って説明していきましょう。
1. 生成に関するパターン:朝の準備の始まり
生成に関するパターンは、オブジェクト(プログラムの中の部品)をどのように作成するかに関するものです。
例1:シングルトンパターン - 家族共有のバスルーム
日常の例:
家に1つしかないバスルーム。家族全員で共有し、誰かが使っているときは他の人は待つ必要があります。
プログラミングでの対応:
プログラム内で1つしか存在してはいけないオブジェクト(例:データベース接続、ログマネージャー)を作る際に使います。
class Bathroom:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# 使用例
bathroom1 = Bathroom()
bathroom2 = Bathroom()
print(bathroom1 is bathroom2) # True (同じインスタンス)
例2:ファクトリーメソッドパターン - その日の服を選ぶ
日常の例:
天気や予定に応じて、適切な服を選びます。晴れの日はTシャツ、雨の日はレインコート、フォーマルな予定の日はスーツなど。
プログラミングでの対応:
状況に応じて適切なオブジェクトを作成するメソッドを用意します。
class Clothes:
pass
class TShirt(Clothes):
pass
class Raincoat(Clothes):
pass
class Suit(Clothes):
pass
def choose_clothes(weather, occasion):
if weather == "sunny" and occasion == "casual":
return TShirt()
elif weather == "rainy":
return Raincoat()
elif occasion == "formal":
return Suit()
else:
return Clothes()
# 使用例
today_clothes = choose_clothes("sunny", "casual")
print(type(today_clothes)) # <class '__main__.TShirt'>
例3:ビルダーパターン - 朝食の準備手順
日常の例:
朝食を準備する複雑な手順。パンを焼く、卵を焼く、コーヒーを入れるなど、段階的に朝食を作っていきます。
プログラミングでの対応:
複雑なオブジェクトを段階的に構築するためのパターンです。
class BreakfastBuilder:
def __init__(self):
self.breakfast = {}
def add_toast(self):
self.breakfast['toast'] = 'Toasted bread'
return self
def add_egg(self):
self.breakfast['egg'] = 'Scrambled egg'
return self
def add_coffee(self):
self.breakfast['coffee'] = 'Black coffee'
return self
def get_breakfast(self):
return self.breakfast
# 使用例
builder = BreakfastBuilder()
my_breakfast = builder.add_toast().add_egg().add_coffee().get_breakfast()
print(my_breakfast)
# {'toast': 'Toasted bread', 'egg': 'Scrambled egg', 'coffee': 'Black coffee'}
2. 構造に関するパターン:朝の準備を整える
構造に関するパターンは、オブジェクト同士をどのように組み合わせて大きな構造を作るかに関するものです。
例:アダプタパターン - 海外旅行と電源アダプタ
日常の例:
海外旅行に行ったとき、日本の電化製品を現地のコンセントで使うために電源アダプタを使います。
プログラミングでの対応:
互換性のないインターフェースを持つクラスを協調して動作させます。
class JapaneseElectricPlug:
def use_japanese_plug(self):
return "日本のコンセントに接続"
class ForeignSocket:
def connect_foreign_socket(self):
return "海外のコンセントに接続"
class PlugAdapter(JapaneseElectricPlug, ForeignSocket):
def use_japanese_plug(self):
return self.connect_foreign_socket()
# 使用例
adapter = PlugAdapter()
print(adapter.use_japanese_plug()) # "海外のコンセントに接続"
3. 振る舞いに関するパターン:朝の行動を管理する
振る舞いに関するパターンは、オブジェクト間の相互作用や責任の分配に関するものです。
例:オブザーバーパターン - 目覚まし時計と家族
日常の例:
目覚まし時計が鳴ると、家族全員が起きる(または起こされる)という状況。
プログラミングでの対応:
あるオブジェクトの状態が変化したときに、それに依存する他のオブジェクトに自動的に通知する仕組みを提供します。
class AlarmClock:
def __init__(self):
self._observers = []
def add_observer(self, observer):
self._observers.append(observer)
def notify_all(self):
for observer in self._observers:
observer.wake_up()
class FamilyMember:
def __init__(self, name):
self.name = name
def wake_up(self):
print(f"{self.name}が起きました。")
# 使用例
alarm = AlarmClock()
alarm.add_observer(FamilyMember("お父さん"))
alarm.add_observer(FamilyMember("お母さん"))
alarm.add_observer(FamilyMember("子供"))
alarm.notify_all()
# お父さんが起きました。
# お母さんが起きました。
# 子供が起きました。
まとめ
このように、デザインパターンは私たちの日常生活の中にも見つけることができます。プログラミングの世界では、これらのパターンを理解し適切に使用することで、より効率的で保守性の高いコードを書くことができます。
デザインパターンは魔法の杖ではありませんが、適切に使用することで、より良いソフトウェア設計への道を開いてくれます。
次回のコーディングセッションでは、ここで学んだパターンを意識してみてください。きっと、新しい視点でコードを見ることができるはずです。
プログラミングの旅を楽しんでください。そして、日常生活の中にもプログラミングの知恵を見つける楽しさを味わってみてください!
参考記事
生成に関するデザインパターン
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Singleton
構造に関するデザインパターン
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
振る舞いに関するデザインパターン
- Chain of Responsibility
- Command
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Interpreter
複数のパターンの組み合わせ、その他