Skip to content

原型模式详解:概念、实现与应用

引言

原型模式是一种创建型设计模式,它允许你通过复制现有对象来创建新对象,而无需知道其具体类。原型模式通过克隆已有对象来创建新对象,避免了重复初始化步骤,特别适用于创建成本较高的对象。

什么是原型模式?

原型模式是一种创建型设计模式,它能让你通过复制现有对象来创建新对象,而无需知道其具体类。原型模式的核心思想是通过克隆来创建对象,而不是通过实例化类。

核心思想

原型模式的核心思想是:

  1. 克隆机制:通过复制现有对象来创建新对象
  2. 避免重复初始化:避免重复执行昂贵的初始化操作
  3. 动态创建:在运行时动态创建对象

为什么需要原型模式?

在许多情况下,我们需要创建与现有对象相似的新对象:

1. 对象创建成本高

当对象的创建过程比较昂贵时:

  • 需要进行大量的初始化工作
  • 需要从数据库或网络加载数据
  • 需要执行复杂的计算

2. 对象结构复杂

当对象的结构比较复杂时:

  • 包含多个嵌套的对象
  • 具有复杂的初始化逻辑
  • 需要保持对象的一致性

3. 动态对象创建

当需要在运行时动态创建对象时:

  • 根据配置创建不同对象
  • 通过克隆创建对象变体
  • 避免硬编码对象类型

原型模式的基本实现

让我们从一个简单的原型模式实现开始:

javascript
// 原型接口
class Prototype {
  clone() {
    throw new Error('必须实现 clone 方法');
  }
}

// 具体原型
class ConcretePrototype extends Prototype {
  constructor(field1, field2) {
    super();
    this.field1 = field1;
    this.field2 = field2;
  }
  
  clone() {
    // 浅拷贝
    return new ConcretePrototype(this.field1, this.field2);
  }
  
  toString() {
    return `ConcretePrototype { field1: ${this.field1}, field2: ${this.field2} }`;
  }
}

// 使用示例
function clientCode() {
  const prototype = new ConcretePrototype('value1', 'value2');
  console.log('原始对象:', prototype.toString());
  
  const clone = prototype.clone();
  console.log('克隆对象:', clone.toString());
  
  console.log('对象相等性:', prototype === clone);
  console.log('内容相等性:', 
    prototype.field1 === clone.field1 && 
    prototype.field2 === clone.field2);
}

clientCode();

实现要点分析

  1. 原型接口:定义克隆方法的接口
  2. 具体原型:实现克隆方法,创建自身的副本
  3. 克隆方式:可以选择浅拷贝或深拷贝
  4. 客户端使用:通过克隆创建新对象

原型模式的深拷贝实现

在实际应用中,我们通常需要实现深拷贝来确保克隆对象的独立性:

javascript
// 深拷贝原型实现
class DeepClonePrototype extends Prototype {
  constructor(name, details) {
    super();
    this.name = name;
    this.details = details; // 包含嵌套对象
  }
  
  // 浅拷贝实现
  shallowClone() {
    return new DeepClonePrototype(this.name, this.details);
  }
  
  // 深拷贝实现
  deepClone() {
    const clonedDetails = JSON.parse(JSON.stringify(this.details));
    return new DeepClonePrototype(this.name, clonedDetails);
  }
  
  // 使用递归深拷贝
  deepCloneRecursive() {
    function deepClone(obj) {
      if (obj === null || typeof obj !== 'object') {
        return obj;
      }
      
      if (obj instanceof Date) {
        return new Date(obj.getTime());
      }
      
      if (obj instanceof Array) {
        return obj.map(item => deepClone(item));
      }
      
      if (typeof obj === 'object') {
        const clonedObj = {};
        for (const key in obj) {
          if (obj.hasOwnProperty(key)) {
            clonedObj[key] = deepClone(obj[key]);
          }
        }
        return clonedObj;
      }
    }
    
    const clonedDetails = deepClone(this.details);
    return new DeepClonePrototype(this.name, clonedDetails);
  }
  
  toString() {
    return `DeepClonePrototype { name: ${this.name}, details: ${JSON.stringify(this.details)} }`;
  }
}

// 使用示例
function deepCloneExample() {
  const original = new DeepClonePrototype('原始对象', {
    id: 1,
    data: {
      value: '测试数据',
      nested: {
        deep: '深层数据'
      }
    },
    tags: ['tag1', 'tag2']
  });
  
  console.log('原始对象:', original.toString());
  
  // 浅拷贝
  const shallowClone = original.shallowClone();
  console.log('浅拷贝对象:', shallowClone.toString());
  
  // 修改原始对象的嵌套数据
  original.details.data.value = '修改后的数据';
  console.log('修改原始对象后:');
  console.log('原始对象:', original.toString());
  console.log('浅拷贝对象:', shallowClone.toString());
  console.log('浅拷贝数据共享:', original.details.data === shallowClone.details.data);
  
  // 重置原始对象
  original.details.data.value = '测试数据';
  
  // 深拷贝
  const deepClone = original.deepClone();
  console.log('深拷贝对象:', deepClone.toString());
  
  // 修改原始对象的嵌套数据
  original.details.data.value = '修改后的数据';
  console.log('修改原始对象后:');
  console.log('原始对象:', original.toString());
  console.log('深拷贝对象:', deepClone.toString());
  console.log('深拷贝数据独立:', original.details.data !== deepClone.details.data);
}

deepCloneExample();

原型模式的实际应用场景

1. 图形编辑器中的形状克隆

在图形编辑器中,原型模式非常适合用于克隆图形对象:

javascript
// 图形基类
class Shape {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.color = 'black';
  }
  
  clone() {
    throw new Error('必须实现 clone 方法');
  }
  
  move(x, y) {
    this.x = x;
    this.y = y;
  }
  
  draw() {
    throw new Error('必须实现 draw 方法');
  }
}

// 圆形
class Circle extends Shape {
  constructor(x, y, radius) {
    super(x, y);
    this.radius = radius;
  }
  
  clone() {
    const clone = new Circle(this.x, this.y, this.radius);
    clone.color = this.color;
    return clone;
  }
  
  draw() {
    return `绘制圆形: 位置(${this.x}, ${this.y}), 半径${this.radius}, 颜色${this.color}`;
  }
}

// 矩形
class Rectangle extends Shape {
  constructor(x, y, width, height) {
    super(x, y);
    this.width = width;
    this.height = height;
  }
  
  clone() {
    const clone = new Rectangle(this.x, this.y, this.width, this.height);
    clone.color = this.color;
    return clone;
  }
  
  draw() {
    return `绘制矩形: 位置(${this.x}, ${this.y}), 宽度${this.width}, 高度${this.height}, 颜色${this.color}`;
  }
}

// 图形编辑器
class GraphicEditor {
  constructor() {
    this.shapes = [];
    this.prototypes = new Map();
  }
  
  // 注册原型
  registerPrototype(name, prototype) {
    this.prototypes.set(name, prototype);
  }
  
  // 通过原型创建图形
  createShape(prototypeName, x, y) {
    const prototype = this.prototypes.get(prototypeName);
    if (!prototype) {
      throw new Error(`未找到原型: ${prototypeName}`);
    }
    
    const shape = prototype.clone();
    shape.move(x, y);
    this.shapes.push(shape);
    return shape;
  }
  
  // 克隆现有图形
  cloneShape(index, x, y) {
    if (index < 0 || index >= this.shapes.length) {
      throw new Error('无效的图形索引');
    }
    
    const originalShape = this.shapes[index];
    const clonedShape = originalShape.clone();
    clonedShape.move(x, y);
    this.shapes.push(clonedShape);
    return clonedShape;
  }
  
  // 绘制所有图形
  drawAll() {
    return this.shapes.map((shape, index) => 
      `图形 ${index}: ${shape.draw()}`
    );
  }
}

// 使用示例
function graphicEditorExample() {
  const editor = new GraphicEditor();
  
  // 注册原型
  editor.registerPrototype('circle', new Circle(0, 0, 10));
  editor.registerPrototype('rectangle', new Rectangle(0, 0, 20, 15));
  
  // 创建图形
  const circle1 = editor.createShape('circle', 100, 100);
  circle1.color = 'red';
  
  const rectangle1 = editor.createShape('rectangle', 200, 200);
  rectangle1.color = 'blue';
  
  // 克隆图形
  const circle2 = editor.cloneShape(0, 150, 150);
  circle2.color = 'green';
  
  const rectangle2 = editor.cloneShape(1, 250, 250);
  rectangle2.color = 'yellow';
  
  // 绘制所有图形
  console.log('图形编辑器内容:');
  editor.drawAll().forEach(line => console.log(line));
}

graphicEditorExample();

2. 游戏中的角色克隆

在游戏中,原型模式非常适合用于克隆角色或敌人:

javascript
// 游戏角色基类
class GameCharacter {
  constructor(name, health, attack, defense) {
    this.name = name;
    this.health = health;
    this.maxHealth = health;
    this.attack = attack;
    this.defense = defense;
    this.level = 1;
    this.experience = 0;
    this.inventory = [];
  }
  
  clone() {
    throw new Error('必须实现 clone 方法');
  }
  
  takeDamage(damage) {
    const actualDamage = Math.max(0, damage - this.defense);
    this.health = Math.max(0, this.health - actualDamage);
    return actualDamage;
  }
  
  heal(amount) {
    this.health = Math.min(this.maxHealth, this.health + amount);
  }
  
  gainExperience(exp) {
    this.experience += exp;
    // 简化的升级逻辑
    if (this.experience >= this.level * 100) {
      this.levelUp();
    }
  }
  
  levelUp() {
    this.level++;
    this.maxHealth += 10;
    this.health = this.maxHealth;
    this.attack += 2;
    this.defense += 1;
  }
  
  addToInventory(item) {
    this.inventory.push(item);
  }
  
  getStats() {
    return {
      name: this.name,
      level: this.level,
      health: this.health,
      maxHealth: this.maxHealth,
      attack: this.attack,
      defense: this.defense,
      experience: this.experience,
      inventory: [...this.inventory]
    };
  }
}

// 战士角色
class Warrior extends GameCharacter {
  constructor(name) {
    super(name, 100, 15, 10);
    this.class = 'Warrior';
    this.specialAbility = '强力攻击';
  }
  
  clone() {
    const clone = new Warrior(this.name);
    // 复制所有属性
    Object.assign(clone, this.getStats());
    clone.class = this.class;
    clone.specialAbility = this.specialAbility;
    return clone;
  }
  
  useSpecialAbility() {
    return `${this.name} 使用了 ${this.specialAbility}!`;
  }
}

// 法师角色
class Mage extends GameCharacter {
  constructor(name) {
    super(name, 70, 20, 5);
    this.class = 'Mage';
    this.mana = 100;
    this.maxMana = 100;
    this.spells = ['火球术', '治疗术'];
  }
  
  clone() {
    const clone = new Mage(this.name);
    // 复制所有属性
    Object.assign(clone, this.getStats());
    clone.class = this.class;
    clone.mana = this.mana;
    clone.maxMana = this.maxMana;
    clone.spells = [...this.spells];
    return clone;
  }
  
  castSpell(spellName) {
    if (this.spells.includes(spellName)) {
      return `${this.name} 施放了 ${spellName}!`;
    }
    return `${this.name} 不会 ${spellName}!`;
  }
}

// 怪物基类
class Monster extends GameCharacter {
  constructor(name, health, attack, defense, loot) {
    super(name, health, attack, defense);
    this.loot = loot || [];
  }
  
  clone() {
    const clone = new Monster(this.name, this.health, this.attack, this.defense, [...this.loot]);
    // 复制所有属性
    Object.assign(clone, this.getStats());
    clone.loot = [...this.loot];
    return clone;
  }
  
  getLoot() {
    return [...this.loot];
  }
}

// 游戏世界管理器
class GameWorld {
  constructor() {
    this.characters = [];
    this.monsters = [];
    this.prototypes = new Map();
  }
  
  // 注册角色原型
  registerCharacterPrototype(name, prototype) {
    this.prototypes.set(`character_${name}`, prototype);
  }
  
  // 注册怪物原型
  registerMonsterPrototype(name, prototype) {
    this.prototypes.set(`monster_${name}`, prototype);
  }
  
  // 创建角色
  createCharacter(prototypeName, name) {
    const prototype = this.prototypes.get(`character_${prototypeName}`);
    if (!prototype) {
      throw new Error(`未找到角色原型: ${prototypeName}`);
    }
    
    const character = prototype.clone();
    character.name = name;
    this.characters.push(character);
    return character;
  }
  
  // 生成怪物
  spawnMonster(prototypeName) {
    const prototype = this.prototypes.get(`monster_${prototypeName}`);
    if (!prototype) {
      throw new Error(`未找到怪物原型: ${prototypeName}`);
    }
    
    const monster = prototype.clone();
    this.monsters.push(monster);
    return monster;
  }
  
  // 克隆现有角色(例如复活队友)
  cloneCharacter(index) {
    if (index < 0 || index >= this.characters.length) {
      throw new Error('无效的角色索引');
    }
    
    const originalCharacter = this.characters[index];
    const clonedCharacter = originalCharacter.clone();
    this.characters.push(clonedCharacter);
    return clonedCharacter;
  }
  
  // 获取所有角色状态
  getCharactersStatus() {
    return this.characters.map((character, index) => 
      `角色 ${index + 1}: ${JSON.stringify(character.getStats())}`
    );
  }
  
  // 获取所有怪物状态
  getMonstersStatus() {
    return this.monsters.map((monster, index) => 
      `怪物 ${index + 1}: ${JSON.stringify(monster.getStats())}`
    );
  }
}

// 使用示例
function gameWorldExample() {
  const gameWorld = new GameWorld();
  
  // 注册角色原型
  gameWorld.registerCharacterPrototype('warrior', new Warrior('勇士原型'));
  gameWorld.registerCharacterPrototype('mage', new Mage('法师原型'));
  
  // 注册怪物原型
  gameWorld.registerMonsterPrototype('goblin', new Monster('哥布林', 30, 8, 3, ['金币', '小刀']));
  gameWorld.registerMonsterPrototype('orc', new Monster('兽人', 60, 12, 6, ['金币', '斧头']));
  
  // 创建玩家角色
  const player1 = gameWorld.createCharacter('warrior', '阿尔萨斯');
  player1.addToInventory('长剑');
  player1.gainExperience(150); // 升级
  
  const player2 = gameWorld.createCharacter('mage', '吉安娜');
  player2.addToInventory('法杖');
  player2.mana = 80;
  
  // 生成怪物
  const goblin1 = gameWorld.spawnMonster('goblin');
  const orc1 = gameWorld.spawnMonster('orc');
  orc1.health = 40; // 受伤的兽人
  
  // 克隆角色(例如复活)
  const player3 = gameWorld.cloneCharacter(0);
  player3.name = '阿尔萨斯的幻象';
  
  // 显示游戏状态
  console.log('玩家角色:');
  gameWorld.getCharactersStatus().forEach(line => console.log(line));
  
  console.log('\n怪物:');
  gameWorld.getMonstersStatus().forEach(line => console.log(line));
  
  // 战斗示例
  console.log('\n战斗示例:');
  console.log(player1.useSpecialAbility());
  console.log(player2.castSpell('火球术'));
  console.log(`哥布林受到 ${player1.attack} 点伤害`);
  goblin1.takeDamage(player1.attack);
  console.log(`哥布林剩余生命: ${goblin1.health}`);
}

gameWorldExample();

3. 配置对象克隆

在配置管理中,原型模式可以用于克隆和修改配置对象:

javascript
// 配置原型基类
class ConfigPrototype {
  constructor(config = {}) {
    this.config = {
      database: {
        host: 'localhost',
        port: 3306,
        username: 'root',
        password: '',
        database: 'myapp'
      },
      server: {
        port: 3000,
        host: '0.0.0.0',
        ssl: false
      },
      logging: {
        level: 'info',
        file: 'app.log',
        console: true
      },
      ...config
    };
  }
  
  clone() {
    // 深拷贝配置
    const clonedConfig = JSON.parse(JSON.stringify(this.config));
    return new ConfigPrototype(clonedConfig);
  }
  
  // 获取配置值
  get(path) {
    return path.split('.').reduce((obj, key) => obj?.[key], this.config);
  }
  
  // 设置配置值
  set(path, value) {
    const keys = path.split('.');
    const lastKey = keys.pop();
    const target = keys.reduce((obj, key) => {
      if (!obj[key]) obj[key] = {};
      return obj[key];
    }, this.config);
    target[lastKey] = value;
    return this;
  }
  
  // 合并配置
  merge(newConfig) {
    function deepMerge(target, source) {
      for (const key in source) {
        if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
          if (!target[key]) target[key] = {};
          deepMerge(target[key], source[key]);
        } else {
          target[key] = source[key];
        }
      }
      return target;
    }
    
    deepMerge(this.config, newConfig);
    return this;
  }
  
  // 导出配置
  export() {
    return JSON.parse(JSON.stringify(this.config));
  }
  
  // 配置验证
  validate() {
    const errors = [];
    
    if (!this.config.database.host) {
      errors.push('数据库主机地址不能为空');
    }
    
    if (!this.config.database.username) {
      errors.push('数据库用户名不能为空');
    }
    
    if (this.config.server.port < 1 || this.config.server.port > 65535) {
      errors.push('服务器端口必须在 1-65535 之间');
    }
    
    return errors;
  }
}

// 开发环境配置原型
class DevelopmentConfig extends ConfigPrototype {
  constructor() {
    super({
      database: {
        host: 'localhost',
        port: 3306,
        username: 'dev_user',
        password: 'dev_password',
        database: 'myapp_dev'
      },
      server: {
        port: 3000,
        host: 'localhost',
        ssl: false
      },
      logging: {
        level: 'debug',
        file: 'dev.log',
        console: true
      }
    });
  }
  
  clone() {
    const clone = new DevelopmentConfig();
    clone.config = JSON.parse(JSON.stringify(this.config));
    return clone;
  }
}

// 生产环境配置原型
class ProductionConfig extends ConfigPrototype {
  constructor() {
    super({
      database: {
        host: 'prod-db.example.com',
        port: 3306,
        username: 'prod_user',
        password: 'prod_password',
        database: 'myapp_prod'
      },
      server: {
        port: 80,
        host: '0.0.0.0',
        ssl: true
      },
      logging: {
        level: 'error',
        file: '/var/log/myapp/prod.log',
        console: false
      }
    });
  }
  
  clone() {
    const clone = new ProductionConfig();
    clone.config = JSON.parse(JSON.stringify(this.config));
    return clone;
  }
}

// 测试环境配置原型
class TestConfig extends ConfigPrototype {
  constructor() {
    super({
      database: {
        host: 'test-db.example.com',
        port: 3306,
        username: 'test_user',
        password: 'test_password',
        database: 'myapp_test'
      },
      server: {
        port: 3001,
        host: 'localhost',
        ssl: false
      },
      logging: {
        level: 'warn',
        file: 'test.log',
        console: true
      }
    });
  }
  
  clone() {
    const clone = new TestConfig();
    clone.config = JSON.parse(JSON.stringify(this.config));
    return clone;
  }
}

// 配置管理器
class ConfigManager {
  constructor() {
    this.prototypes = new Map();
    this.instances = new Map();
  }
  
  // 注册配置原型
  registerPrototype(name, prototype) {
    this.prototypes.set(name, prototype);
  }
  
  // 获取配置实例
  getConfig(name, instanceId = 'default') {
    const instanceKey = `${name}_${instanceId}`;
    
    if (this.instances.has(instanceKey)) {
      return this.instances.get(instanceKey);
    }
    
    const prototype = this.prototypes.get(name);
    if (!prototype) {
      throw new Error(`未找到配置原型: ${name}`);
    }
    
    const configInstance = prototype.clone();
    this.instances.set(instanceKey, configInstance);
    return configInstance;
  }
  
  // 创建自定义配置
  createCustomConfig(prototypeName, customizations = {}) {
    const prototype = this.prototypes.get(prototypeName);
    if (!prototype) {
      throw new Error(`未找到配置原型: ${prototypeName}`);
    }
    
    const customConfig = prototype.clone();
    customConfig.merge(customizations);
    
    // 验证配置
    const errors = customConfig.validate();
    if (errors.length > 0) {
      throw new Error(`配置验证失败: ${errors.join(', ')}`);
    }
    
    return customConfig;
  }
  
  // 获取所有配置实例
  getAllConfigs() {
    return Array.from(this.instances.entries()).map(([key, config]) => ({
      key,
      config: config.export()
    }));
  }
}

// 使用示例
function configManagerExample() {
  const configManager = new ConfigManager();
  
  // 注册配置原型
  configManager.registerPrototype('development', new DevelopmentConfig());
  configManager.registerPrototype('production', new ProductionConfig());
  configManager.registerPrototype('test', new TestConfig());
  
  // 获取默认开发配置
  const devConfig = configManager.getConfig('development');
  console.log('默认开发配置:');
  console.log(devConfig.export());
  
  // 获取特定实例的开发配置
  const devConfigInstance1 = configManager.getConfig('development', 'instance1');
  devConfigInstance1.set('server.port', 3005);
  console.log('自定义开发配置:');
  console.log(devConfigInstance1.export());
  
  // 创建自定义测试配置
  try {
    const customTestConfig = configManager.createCustomConfig('test', {
      database: {
        host: 'custom-test-db.example.com'
      },
      server: {
        port: 4000
      }
    });
    
    console.log('自定义测试配置:');
    console.log(customTestConfig.export());
  } catch (error) {
    console.error('创建自定义配置失败:', error.message);
  }
  
  // 尝试创建无效配置
  try {
    const invalidConfig = configManager.createCustomConfig('production', {
      server: {
        port: 99999 // 无效端口
      }
    });
  } catch (error) {
    console.error('配置验证失败:', error.message);
  }
  
  // 显示所有配置实例
  console.log('所有配置实例:');
  configManager.getAllConfigs().forEach(({ key, config }) => {
    console.log(`${key}:`, config);
  });
}

configManagerExample();

原型模式与其它创建型模式的对比

原型模式 vs 工厂模式

javascript
// 原型模式 - 通过克隆创建对象
class PrototypeFactory {
  constructor(prototype) {
    this.prototype = prototype;
  }
  
  create() {
    return this.prototype.clone();
  }
}

// 工厂模式 - 通过实例化创建对象
class SimpleFactory {
  create(type) {
    switch (type) {
      case 'A': return new ProductA();
      case 'B': return new ProductB();
      default: throw new Error('未知产品类型');
    }
  }
}

原型模式 vs 构建者模式

javascript
// 原型模式 - 克隆现有对象
class Prototype {
  clone() {
    return new Prototype(this.property1, this.property2);
  }
}

// 构建者模式 - 分步骤构建对象
class Builder {
  setProperty1(value) { /* 设置属性1 */ }
  setProperty2(value) { /* 设置属性2 */ }
  build() { /* 返回构建的对象 */ }
}

原型模式的优缺点

优点

  1. 避免重复初始化:通过克隆避免重复执行昂贵的初始化操作
  2. 运行时动态创建:可以在运行时动态创建和克隆对象
  3. 简化对象创建:隐藏了复杂的创建逻辑
  4. 保持性能:对于创建成本高的对象,克隆比重新创建更高效

缺点

  1. 克隆复杂性:实现深拷贝可能比较复杂
  2. 违背开闭原则:需要修改原型类的代码来实现克隆
  3. 内存使用:可能会增加内存使用,因为需要存储原型对象
  4. 循环引用问题:处理循环引用的对象时需要特别注意

总结

原型模式是一种创建型设计模式,它通过克隆现有对象来创建新对象,避免了重复的初始化步骤。原型模式特别适用于创建成本较高的对象,或者需要动态创建对象的场景。

通过本章的学习,我们了解了:

  1. 原型模式的基本概念和核心思想
  2. 原型模式的实现方式(浅拷贝和深拷贝)
  3. 原型模式在实际开发中的应用场景(图形编辑器、游戏角色、配置管理)
  4. 原型模式与其他创建型模式的对比
  5. 原型模式的优缺点

原型模式在现代软件开发中应用广泛,特别是在需要高效创建对象或动态配置对象的场景中,它可以很好地支持系统的性能和灵活性。

在下一章中,我们将开始探讨结构型模式,首先是适配器模式。