0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

全30回:静的と動的でどう違うのか、JavaとPythonで学ぶデザインパターン - Day 7 Prototypeパターン:既存のオブジェクトから新しいオブジェクトを作る

Posted at

はじめに

皆さん、こんにちは!「JavaとPythonで比べるデザインパターン」シリーズの第7回目です。
今回は、既存のオブジェクトをコピーして新しいオブジェクトを作成するPrototype(プロトタイプ)パターンについて解説します。


Prototypeパターンとは?

Prototypeパターンは、生成したいオブジェクトの原型(プロトタイプ)をあらかじめ用意しておき、その原型をコピー(クローン)することで新しいインスタンスを生成するデザインパターンです。

このパターンが解決する問題

従来のnewキーワードによるオブジェクト生成では、以下のような課題があります:

  • 生成コストの高さ:データベースアクセス、ファイル読み込み、ネットワーク通信など重い処理を伴う初期化
  • 複雑な初期化処理:多数のパラメータや複雑な設定が必要なオブジェクト
  • 実行時の型決定:コンパイル時には具象クラスが分からない状況
  • 設定の再利用:似たような設定を持つオブジェクトを大量生成する場合

実際の適用場面

  • ゲーム開発:敵キャラクター、武器、アイテムの大量生成
  • 設定管理:環境別の設定オブジェクト(開発、テスト、本番)
  • グラフィックス:図形オブジェクトのコピー&ペースト機能
  • 文書処理:テンプレート文書からの新規文書作成

Javaでの実装:Cloneableインターフェースと深いコピー

Javaでは、Objectクラスのclone()メソッドとCloneableインターフェースを使ってPrototypeパターンを実装します。

基本的な実装

// JavaでのPrototypeパターンの実装例

// 基本的なプロトタイプ実装
class Car implements Cloneable {
    private String model;
    private int year;
    private String color;
    
    public Car(String model, int year, String color) {
        this.model = model;
        this.year = year;
        this.color = color;
        // 実際には重い初期化処理があると仮定
        System.out.println("Heavy initialization for " + model);
    }
    
    // Getterメソッド
    public String getModel() { return model; }
    public int getYear() { return year; }
    public String getColor() { return color; }
    
    // Setterメソッド(クローン後の変更用)
    public void setColor(String color) { this.color = color; }
    public void setYear(int year) { this.year = year; }
    
    @Override
    public Car clone() throws CloneNotSupportedException {
        // シャローコピーを実行
        System.out.println("Cloning " + model + " - no heavy initialization!");
        return (Car) super.clone();
    }
    
    @Override
    public String toString() {
        return String.format("Car{model='%s', year=%d, color='%s'}", 
                           model, year, color);
    }
}

複合オブジェクトのディープコピー実装

// より複雑なオブジェクトのディープコピー例
class Engine implements Cloneable {
    private String type;
    private int horsepower;
    
    public Engine(String type, int horsepower) {
        this.type = type;
        this.horsepower = horsepower;
    }
    
    // Getterとsetter
    public String getType() { return type; }
    public int getHorsepower() { return horsepower; }
    public void setHorsepower(int horsepower) { this.horsepower = horsepower; }
    
    @Override
    public Engine clone() throws CloneNotSupportedException {
        return (Engine) super.clone();
    }
    
    @Override
    public String toString() {
        return String.format("Engine{type='%s', horsepower=%d}", type, horsepower);
    }
}

class AdvancedCar implements Cloneable {
    private String model;
    private int year;
    private Engine engine;  // 複合オブジェクト
    private List<String> features;  // コレクション
    
    public AdvancedCar(String model, int year, Engine engine) {
        this.model = model;
        this.year = year;
        this.engine = engine;
        this.features = new ArrayList<>();
        
        // 重い初期化処理をシミュレート
        try {
            Thread.sleep(100); // データベースアクセスをシミュレート
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        System.out.println("Heavy initialization completed for " + model);
    }
    
    public void addFeature(String feature) {
        features.add(feature);
    }
    
    public List<String> getFeatures() { return new ArrayList<>(features); }
    public String getModel() { return model; }
    public int getYear() { return year; }
    public Engine getEngine() { return engine; }
    
    @Override
    public AdvancedCar clone() throws CloneNotSupportedException {
        System.out.println("Fast cloning " + model + " - skipping heavy initialization!");
        
        // ディープコピーを実行
        AdvancedCar cloned = (AdvancedCar) super.clone();
        
        // 複合オブジェクトを個別にクローン
        cloned.engine = this.engine.clone();
        
        // コレクションの新しいインスタンスを作成
        cloned.features = new ArrayList<>(this.features);
        
        return cloned;
    }
    
    @Override
    public String toString() {
        return String.format("AdvancedCar{model='%s', year=%d, engine=%s, features=%s}", 
                           model, year, engine, features);
    }
}

// プロトタイプレジストリーパターン
class CarPrototypeRegistry {
    private Map<String, AdvancedCar> prototypes = new HashMap<>();
    
    public void registerPrototype(String key, AdvancedCar prototype) {
        prototypes.put(key, prototype);
    }
    
    public AdvancedCar getPrototype(String key) throws CloneNotSupportedException {
        AdvancedCar prototype = prototypes.get(key);
        if (prototype != null) {
            return prototype.clone();
        }
        throw new IllegalArgumentException("Prototype not found: " + key);
    }
}

// 使用例
public class PrototypeDemo {
    public static void main(String[] args) throws CloneNotSupportedException {
        // プロトタイプレジストリーの設定
        CarPrototypeRegistry registry = new CarPrototypeRegistry();
        
        // 重い初期化を伴うプロトタイプを作成
        Engine v8Engine = new Engine("V8", 400);
        AdvancedCar luxuryCar = new AdvancedCar("Mercedes S-Class", 2023, v8Engine);
        luxuryCar.addFeature("Leather Seats");
        luxuryCar.addFeature("Sunroof");
        luxuryCar.addFeature("Navigation");
        
        registry.registerPrototype("luxury", luxuryCar);
        
        // 高速なクローン生成(重い初期化をスキップ)
        AdvancedCar car1 = registry.getPrototype("luxury");
        AdvancedCar car2 = registry.getPrototype("luxury");
        
        // クローン後の個別カスタマイズ
        car1.getEngine().setHorsepower(450);
        car2.addFeature("Sport Package");
        
        System.out.println("Original: " + luxuryCar);
        System.out.println("Clone 1: " + car1);
        System.out.println("Clone 2: " + car2);
        
        // 独立性の確認
        System.out.println("car1 == car2: " + (car1 == car2)); // false
        System.out.println("car1.engine == car2.engine: " + (car1.getEngine() == car2.getEngine())); // false
    }
}

Javaの特徴

  • Cloneableインターフェースによる明示的なクローン対応宣言
  • clone()メソッドのオーバーライドで制御されたコピー処理
  • ディープコピーの明示的な実装が必要
  • プロトタイプレジストリーによる管理が可能

Pythonでの実装:copyモジュールとカスタムクローン

Pythonでは、copyモジュールを使用してより柔軟にPrototypeパターンを実装できます。

基本的な実装

# PythonでのPrototypeパターンの実装例
import copy
import time
from typing import List, Dict, Any

class Engine:
    def __init__(self, engine_type: str, horsepower: int):
        self.type = engine_type
        self.horsepower = horsepower
    
    def __repr__(self):
        return f"Engine(type='{self.type}', horsepower={self.horsepower})"

class Car:
    def __init__(self, model: str, year: int, engine: Engine):
        self.model = model
        self.year = year
        self.engine = engine
        self.features: List[str] = []
        
        # 重い初期化処理をシミュレート
        time.sleep(0.1)  # データベースアクセスをシミュレート
        print(f"Heavy initialization completed for {model}")
    
    def add_feature(self, feature: str):
        self.features.append(feature)
    
    def __repr__(self):
        return f"Car(model='{self.model}', year={self.year}, engine={self.engine}, features={self.features})"

# 基本的な使用例
prototype_car = Car("Toyota Camry", 2023, Engine("V6", 300))
prototype_car.add_feature("Hybrid System")
prototype_car.add_feature("Safety Package")

# 高速なシャローコピー
print("\n=== Shallow Copy ===")
car1 = copy.copy(prototype_car)
print(f"Fast cloning {car1.model} - skipping heavy initialization!")

# 安全なディープコピー
print("\n=== Deep Copy ===")
car2 = copy.deepcopy(prototype_car)
print(f"Fast cloning {car2.model} - skipping heavy initialization!")

# 独立性の確認
car1.engine.horsepower = 350  # シャローコピーでは元のオブジェクトも影響を受ける
car2.engine.horsepower = 400  # ディープコピーでは独立

print(f"\nOriginal engine HP: {prototype_car.engine.horsepower}")  # 350(影響あり)
print(f"Car1 engine HP: {car1.engine.horsepower}")              # 350
print(f"Car2 engine HP: {car2.engine.horsepower}")              # 400(独立)

より高度な実装:カスタムクローンメソッド

# カスタムクローン機能を持つプロトタイプ実装
from abc import ABC, abstractmethod
import copy

class Prototype(ABC):
    """プロトタイプの抽象基底クラス"""
    
    @abstractmethod
    def clone(self):
        """カスタムクローンメソッド"""
        pass

class GameCharacter(Prototype):
    def __init__(self, name: str, character_class: str, level: int = 1):
        self.name = name
        self.character_class = character_class
        self.level = level
        self.equipment: Dict[str, str] = {}
        self.skills: List[str] = []
        self.stats = {
            'hp': 100,
            'mp': 50,
            'attack': 10,
            'defense': 10
        }
        
        # 重い初期化処理(データベースからスキル情報を読み込みなど)
        time.sleep(0.05)
        print(f"Character {name} initialized with heavy processing")
    
    def add_equipment(self, slot: str, item: str):
        self.equipment[slot] = item
    
    def add_skill(self, skill: str):
        self.skills.append(skill)
    
    def level_up(self):
        self.level += 1
        self.stats['hp'] += 10
        self.stats['attack'] += 2
        self.stats['defense'] += 2
    
    def clone(self, new_name: str = None):
        """カスタムクローンメソッド"""
        print(f"Fast cloning character - skipping heavy initialization!")
        
        # ディープコピーを実行
        cloned = copy.deepcopy(self)
        
        # 新しい名前を設定(指定された場合)
        if new_name:
            cloned.name = new_name
        
        return cloned
    
    def __repr__(self):
        return (f"GameCharacter(name='{self.name}', class='{self.character_class}', "
                f"level={self.level}, equipment={self.equipment}, skills={self.skills})")

class CharacterPrototypeRegistry:
    """キャラクタープロトタイプの管理クラス"""
    
    def __init__(self):
        self._prototypes: Dict[str, GameCharacter] = {}
    
    def register_prototype(self, key: str, prototype: GameCharacter):
        """プロトタイプを登録"""
        self._prototypes[key] = prototype
    
    def get_prototype(self, key: str, name: str = None) -> GameCharacter:
        """プロトタイプを取得してクローン"""
        if key not in self._prototypes:
            raise ValueError(f"Prototype '{key}' not found")
        
        return self._prototypes[key].clone(name)
    
    def list_prototypes(self) -> List[str]:
        """登録されたプロトタイプ一覧を取得"""
        return list(self._prototypes.keys())

# Pythonicなファクトリー関数アプローチ
def create_character_templates():
    """キャラクターテンプレートを作成する関数"""
    templates = {}
    
    # 戦士テンプレート
    warrior = GameCharacter("Warrior_Template", "Warrior", 10)
    warrior.add_equipment("weapon", "Steel Sword")
    warrior.add_equipment("armor", "Chain Mail")
    warrior.add_skill("Slash")
    warrior.add_skill("Shield Block")
    warrior.stats.update({'hp': 150, 'attack': 20, 'defense': 18})
    templates['warrior'] = warrior
    
    # 魔法使いテンプレート
    mage = GameCharacter("Mage_Template", "Mage", 10)
    mage.add_equipment("weapon", "Magic Staff")
    mage.add_equipment("armor", "Robe")
    mage.add_skill("Fireball")
    mage.add_skill("Heal")
    mage.stats.update({'hp': 80, 'mp': 120, 'attack': 25, 'defense': 8})
    templates['mage'] = mage
    
    return templates

# 使用例
def main():
    print("=== Character Prototype Demo ===")
    
    # プロトタイプレジストリーを設定
    registry = CharacterPrototypeRegistry()
    templates = create_character_templates()
    
    for key, template in templates.items():
        registry.register_prototype(key, template)
    
    print(f"Available prototypes: {registry.list_prototypes()}")
    
    # 高速なキャラクター生成
    print("\n=== Fast Character Creation ===")
    player1 = registry.get_prototype('warrior', 'Aragorn')
    player2 = registry.get_prototype('mage', 'Gandalf')
    player3 = registry.get_prototype('warrior', 'Boromir')
    
    # 個別カスタマイズ
    player1.level_up()
    player1.add_skill("Berserker Rage")
    
    player2.add_skill("Lightning Bolt")
    player2.stats['mp'] = 150
    
    print(f"\nPlayer 1: {player1}")
    print(f"Player 2: {player2}")
    print(f"Player 3: {player3}")
    
    # 独立性確認
    print(f"\nIndependence check:")
    print(f"player1 is player3: {player1 is player3}")
    print(f"player1.stats is player3.stats: {player1.stats is player3.stats}")

if __name__ == "__main__":
    main()

Pythonの特徴

  • copyモジュールによる簡潔な実装
  • copy.copy()(シャロー)とcopy.deepcopy()(ディープ)の選択可能
  • カスタムクローンメソッドによる柔軟な制御
  • 動的な型システムを活かしたプロトタイプレジストリー

パフォーマンスと実用性の考慮

パフォーマンス比較

# パフォーマンス測定例(Python)
import time
import copy

class HeavyObject:
    def __init__(self):
        # 重い初期化処理をシミュレート
        time.sleep(0.1)
        self.data = list(range(10000))
        self.config = {'key' + str(i): f'value{i}' for i in range(1000)}

def performance_test():
    print("=== Performance Comparison ===")
    
    # 通常の生成
    start_time = time.time()
    objects1 = [HeavyObject() for _ in range(10)]
    normal_time = time.time() - start_time
    print(f"Normal creation (10 objects): {normal_time:.2f} seconds")
    
    # プロトタイプパターン
    prototype = HeavyObject()
    start_time = time.time()
    objects2 = [copy.deepcopy(prototype) for _ in range(10)]
    prototype_time = time.time() - start_time
    print(f"Prototype pattern (10 objects): {prototype_time:.2f} seconds")
    
    print(f"Speedup: {normal_time / prototype_time:.1f}x faster")

# performance_test()

実用的な考慮事項

  1. メモリ使用量: ディープコピーは元オブジェクトと同じメモリを消費
  2. 参照の管理: シャローコピー時の共有参照に注意
  3. セキュリティ: センシティブな情報を含むオブジェクトのコピー時の考慮
  4. スレッドセーフティ: マルチスレッド環境でのプロトタイプ共有

まとめ:コピーによる効率的なオブジェクト生成

特性 Java Python
実装方法 Cloneableインターフェース + clone()メソッド copyモジュール + カスタムメソッド
コピーの種類 手動でディープコピーを実装 copy()deepcopy()を選択
型安全性 コンパイル時チェック 実行時の動的チェック
実装の複雑さ 明示的だが冗長 シンプルで直感的
パフォーマンス制御 きめ細かい制御が可能 標準実装に依存
適用場面 大規模システム、厳密な制御が必要 プロトタイピング、柔軟な実装

Prototypeパターンの本質は、**「既存のオブジェクトをテンプレートとして活用し、重いオブジェクト生成処理を回避する」**ことです。

Javaでは厳密な型システムの中で制御されたクローン機能を提供し、Pythonでは言語の柔軟性を活かしてより直感的で簡潔な実装を可能にします。どちらのアプローチも、オブジェクト生成のパフォーマンス向上と設計の柔軟性向上という共通の目標を達成します。

次回からは、構造パターンに入ります。オブジェクトやクラスの組み合わせ方に焦点を当てたパターン群について学んでいきます。

次回予告:Week 2: 構造パターン「Day 8 構造パターン入門:クラスとオブジェクトを組み合わせる」

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?