Python テンプレートメソッドパターン
テンプレートメソッドパターンは、ある操作におけるアルゴリズムの骨格を定義し、いくつかのステップをサブクラスに遅延させる動作デザインパターンです。テンプレートメソッドにより、サブクラスはアルゴリズムの構造を変更せずに、アルゴリズムの特定のステップを再定義できます。
簡単に言えば、料理のレシピのようなものです。レシピは料理の手順(野菜を洗う、切る、炒める、盛り付ける)を決めますが、具体的にどう洗うか、どう切るか、どう炒めるかは、各シェフが自分のスタイルで実装できます。
なぜテンプレートメソッドパターンが必要なのか?
コードの再利用
テンプレートメソッドパターンは、不変の振る舞いをスーパークラスに移動し、サブクラス内のコード重複を防ぎます。複数のサブクラスは、同じテンプレートメソッドが定義するアルゴリズム構造を共有できます。
拡張性
サブクラスは、フックメソッドや具体的なステップメソッドをオーバーライドすることで、アルゴリズムの全体的な構造を変更することなく、アルゴリズムの部分的な動作を拡張または変更できます。
制御の反転(IoC)
親クラスがアルゴリズムの実行フローを制御し、サブクラスは自分が実装する必要のある特定のステップだけに集中すればよい。これにより「ハリウッドの原則」—「こちらから電話するまで、電話しないでください」—が実現されます。
テンプレートメソッドパターンの構造
クラス図を使って、テンプレートメソッドパターンの構造を理解しましょう。

主要コンポーネントの説明
| コンポーネント | 責任 | 説明 |
|---|---|---|
| AbstractClass | アルゴリズムの骨格を定義する | テンプレートメソッドと抽象ステップメソッドを含む |
| ConcreteClass | 具体的なステップを実装する | 親クラスで定義された抽象メソッドを実装する |
| template_method | テンプレートメソッド | アルゴリズムの不変部分を定義する |
| step1, step2 | 抽象メソッド | サブクラスでの実装が必要な具体的なステップ |
| hook | フックメソッド | オプションのステップであり、サブクラスはオーバーライドするかどうかを選択できる |
基本構文と実装
抽象基底クラスの定義
例
class AbstractClass(ABC):
"""テンプレートメソッドパターンの抽象基底クラス"""
def template_method(self):
"""テンプレートメソッド - アルゴリズムの骨格を定義"""
self.step1()
self.step2()
self.hook()
self.step3()
@abstractmethod
def step1(self):
"""抽象メソッド1 - サブクラスで実装する必要がある"""
pass
@abstractmethod
def step2(self):
"""抽象メソッド2 - サブクラスで実装する必要がある"""
pass
def step3(self):
"""具体メソッド - デフォルト実装がある"""
print("ステップ3を実行 - デフォルト実装")
def hook(self):
"""フックメソッド - オプションのステップ、サブクラスはオーバーライドを選択できる"""
print("フックメソッドを実行 - デフォルトでは何もしない")
具体的なサブクラスの実装
例
"""具体的な実装クラスA"""
def step1(self):
print("ConcreteClassA - ステップ1を実行")
def step2(self):
print("ConcreteClassA - ステップ2を実行")
def hook(self):
print("ConcreteClassA - フックメソッドをオーバーライドし、追加機能を追加")
class ConcreteClassB(AbstractClass):
"""具体的な実装クラスB"""
def step1(self):
print("ConcreteClassB - ステップ1を実行")
def step2(self):
print("ConcreteClassB - ステップ2を実行")
# hookメソッドをオーバーライドせず、デフォルト実装を使用する
実際の適用例
いくつかの実際の例を通して、テンプレートメソッドパターンの応用を深く理解しましょう。
例1:データ処理のテンプレート
例
import json
import csv
class DataProcessor(ABC):
"""データ処理テンプレート"""
def process_data(self, input_file, output_file):
"""データ処理テンプレートメソッド"""
print(f"データ処理を開始: {input_file} -> {output_file}")
# データを読み込む
data = self.read_data(input_file)
print(f"{len(data)} 件のデータを読み込みました")
# データを変換する
transformed_data = self.transform_data(data)
print("データ変換が完了しました")
# データを保存する
self.save_data(transformed_data, output_file)
print("データ保存が完了しました")
# クリーンアップ作業
self.cleanup()
@abstractmethod
def read_data(self, file_path):
"""データ読み込み - 抽象メソッド"""
pass
@abstractmethod
def transform_data(self, data):
"""データ変換 - 抽象メソッド"""
pass
def save_data(self, data, file_path):
"""データ保存 - 具体メソッド"""
with open(file_path, 'w', encoding='utf-8') as f:
if isinstance(data, list):
for item in data:
f.write(str(item) + '\n')
else:
f.write(str(data))
def cleanup(self):
"""クリーンアップ - フックメソッド"""
print("クリーンアップ完了")
class JSONProcessor(DataProcessor):
"""JSONデータプロセッサ"""
def read_data(self, file_path):
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def transform_data(self, data):
# 簡単な変換:すべての文字列値を大文字に変換する
if isinstance(data, dict):
return {k: v.upper() if isinstance(v, str) else v
for k, v in data.items()}
elif isinstance(data, list):
return [item.upper() if isinstance(item, str) else item
for item in data]
return data
class CSVProcessor(DataProcessor):
"""CSVデータプロセッサ"""
def read_data(self, file_path):
data = []
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(row)
return data
def transform_data(self, data):
# 各レコードに処理タイムスタンプを追加する
import datetime
timestamp = datetime.datetime.now().isoformat()
for record in data:
record['processed_at'] = timestamp
return data
def cleanup(self):
"""クリーンアップメソッドをオーバーライドし、追加のクリーンアップロジックを追加"""
print("CSVプロセッサのクリーンアップ完了")
print("CSVパーサーのリソースを解放する")
# 使用例
if __name__ == "__main__":
# テストデータを作成する
import os
# JSONプロセッサをテストする
json_data = {'name': 'john', 'age': 30, 'city': 'new york'}
with open('test.json', 'w') as f:
json.dump(json_data, f)
json_processor = JSONProcessor()
json_processor.process_data('test.json', 'output_json.txt')
print("\n" + "="*50 + "\n")
# CSVプロセッサをテストする
csv_data = "name,age,city\njohn,30,new york\njane,25,los angeles"
with open('test.csv', 'w') as f:
f.write(csv_data)
csv_processor = CSVProcessor()
csv_processor.process_data('test.csv', 'output_csv.txt')
# テストファイルをクリーンアップする
for file in ['test.json', 'test.csv', 'output_json.txt', 'output_csv.txt']:
if os.path.exists(file):
os.remove(file)
例2:飲み物作成テンプレート
例
class BeverageMaker(ABC):
"""飲み物作成テンプレート"""
def make_beverage(self):
"""飲み物を作成するテンプレートメソッド"""
self.boil_water()
self.brew()
self.pour_in_cup()
if self.customer_wants_condiments():
self.add_condiments()
self.serve()
def boil_water(self):
"""お湯を沸かす - 具体メソッド"""
print("お湯を沸かす")
@abstractmethod
def brew(self):
"""抽出する - 抽象メソッド"""
pass
def pour_in_cup(self):
"""カップに注ぐ - 具体メソッド"""
print("カップに注ぐ")
@abstractmethod
def add_condiments(self):
"""調味料を追加する - 抽象メソッド"""
pass
def customer_wants_condiments(self):
"""フックメソッド - お客様が調味料を必要とするかどうか"""
return True
def serve(self):
"""飲み物を提供する - 具体メソッド"""
print("飲み物ができました。お召し上がりください!")
class CoffeeMaker(BeverageMaker):
"""コーヒー作成"""
def brew(self):
print("沸騰したお湯でコーヒー粉を抽出する")
def add_condiments(self):
print("砂糖と牛乳を加える")
def customer_wants_condiments(self):
answer = input("コーヒーに砂糖と牛乳を加えますか?(y/n): ")
return answer.lower() == 'y'
class TeaMaker(BeverageMaker):
"""お茶作成"""
def brew(self):
print("沸騰したお湯で茶葉を浸す")
def add_condiments(self):
print("レモンを加える")
# 使用例
if __name__ == "__main__":
print("コーヒーを作成:")
coffee = CoffeeMaker()
coffee.make_beverage()
print("\n" + "="*30 + "\n")
print("お茶を作成:")
tea = TeaMaker()
tea.make_beverage()
テンプレートメソッドパターンの変種
1. パラメータ付きテンプレートメソッド
例
"""設定可能なデータプロセッサ"""
def process_with_config(self, input_file, output_file, config):
"""設定パラメータ付きテンプレートメソッド"""
self.validate_config(config)
data = self.read_data(input_file)
processed_data = self.process_with_config_impl(data, config)
self.save_data(processed_data, output_file)
self.post_process(config)
def validate_config(self, config):
"""設定の検証 - 具体メソッド"""
required_keys = ['format', 'encoding']
for key in required_keys:
if key not in config:
raise ValueError(f"設定に必須のキーがありません: {key}")
@abstractmethod
def process_with_config_impl(self, data, config):
"""設定を使用してデータを処理する実装"""
pass
def post_process(self, config):
"""後処理 - フックメソッド"""
if config.get('cleanup', False):
print("クリーンアップ操作を実行")
2. マルチステップテンプレートメソッド
例
"""マルチステッププロセッサ"""
def complex_processing(self):
"""複雑な処理フロー"""
self.initialize()
self.pre_process()
self.main_process()
self.post_process()
self.finalize()
def initialize(self):
print("プロセッサを初期化する")
def pre_process(self):
"""前処理 - フックメソッド"""
pass
@abstractmethod
def main_process(self):
"""主要な処理ロジック"""
pass
def post_process(self):
"""後処理 - フックメソッド"""
pass
def finalize(self):
print(処理が完了しました)
ベストプラクティスと注意事項
1. 抽象メソッドの適切な使用
- 本当にサブクラスの実装が必要なメソッドのみを抽象メソッドとして宣言する
- オプションのステップにはデフォルト実装のフックメソッドを提供する
2. テンプレートメソッドのアクセス制御
- テンプレートメソッドは通常、次のように宣言すべきです
final(Pythonでは命名規則によって実現できます) - ステップメソッドは外部から直接呼び出されないように保護されるべきです
3. エラーハンドリング
例
"""堅牢なテンプレートクラス"""
def template_method(self):
try:
self.setup()
self.execute_steps()
except Exception as e:
self.handle_error(e)
finally:
self.cleanup()
def execute_steps(self):
"""実行ステップのシーケンス"""
self.step1()
self.step2()
self.step3()
def handle_error(self, error):
"""エラーハンドリング - フックメソッド"""
print(f"処理中にエラーが発生しました: {error}")
@abstractmethod
def setup(self):
pass
@abstractmethod
def step1(self):
pass
@abstractmethod
def step2(self):
pass
@abstractmethod
def step3(self):
pass
def cleanup(self):
print("リソースのクリーンアップ")
実践演習
さあ、あなたの番です!以下の演習を完了して、テンプレートメソッドパターンの理解を深めましょう:
演習1:ファイルエクスポートテンプレートの実装
さまざまな形式(TXT、HTML、JSON)へのエクスポートをサポートするファイルエクスポートテンプレートを作成します。
要件:
- 抽象基底クラスを定義する
FileExporter - 具体的なサブクラスを実装する
TXTExporter、HTMLExporter、JSONExporter - テンプレートメソッドには、データの準備、データのフォーマット、ファイルの保存、後処理などのステップを含める必要があります。
演習2:飲み物作成テンプレートの改良
以前の飲料作成テンプレートを拡張し、以下の機能を追加します:
- 飲料のサイズ選択(小カップ、中カップ、大カップ)をサポートする
- 価格計算機能を追加する
- カスタム調味料をサポートする
まとめ
テンプレートメソッドパターンは、強力で実用的なデザインパターンであり、以下の方法でより良いコードを構築するのに役立ちます:
主な利点
- コードの再利用性の向上:共通コードを親クラスに配置する
- 拡張性の向上:サブクラスで特定のステップを拡張する
- 開放閉鎖の原則に準拠:拡張に対して開かれ、修正に対して閉じている
- 制御の反転:親クラスがフローを制御し、サブクラスが詳細を実装する
適用シナリオ
- 複数のクラスに同じメソッドがあるが、具体的な実装が異なる
- サブクラスの拡張ポイントを制御する必要がある
- アルゴリズムの骨格を定義する必要があるが、一部のステップの変更を許可する
核心ポイント
- テンプレートメソッドはアルゴリズムの不変部分を定義する
- 抽象メソッドはアルゴリズムの可変部分を定義する
- フックメソッドはオプションの拡張ポイントを提供する