原型模式,一键克隆,告别重复造轮子

“在编程的世界里,最痛苦的事情莫过于重复造轮子。而原型模式,就是那个让你按下Ctrl+C、Ctrl+V的魔法!”

什么是原型模式?

想象一下,你在玩乐高积木,花了2小时搭建了一个超酷的城堡。这时朋友来了也想要一个一模一样的,你会怎么办?

  • 笨方法:重新花2小时再搭一遍(相当于new一个新对象)
  • 聪明方法:直接复制你已经搭好的城堡(原型模式)

原型模式就是这个道理:通过复制现有对象来创建新对象,而不是重新构造

核心思想

  • 有一个"原型"对象作为模板
  • 通过克隆这个原型来创建新实例
  • 避免复杂的初始化过程

什么时候用原型模式?

生活中处处都有"克隆"的需求,编程也一样:

经典应用场景

1. 对象创建成本很高

// 比如这个复杂的游戏角色创建
public class GameCharacter {
    private String name;
    private List<Skill> skills;          // 需要从数据库加载
    private Equipment equipment;         // 需要复杂计算
    private Map<String, Integer> attributes;  // 需要AI算法生成
    
    // 构造函数超级复杂,耗时很长
    public GameCharacter(String name) {
        this.name = name;
        this.skills = loadSkillsFromDatabase();      // 数据库查询 500ms
        this.equipment = calculateBestEquipment();   // 复杂计算 300ms  
        this.attributes = generateByAI();            // AI计算 1000ms
        // 总共创建一个角色需要1.8秒!
    }
}

2. 需要创建大量相似对象

// 文档模板场景
Document template = createComplexTemplate();  // 创建一次模板
Document doc1 = template.clone();  // 快速克隆
Document doc2 = template.clone();  // 快速克隆
Document doc3 = template.clone();  // 快速克隆

3. 对象状态多变,需要保存快照

// 游戏存档场景
GameState currentState = game.getCurrentState();
GameState savePoint = currentState.clone();  // 保存游戏进度

4. 数据库查询结果缓存

// 用户信息缓存
User userTemplate = userDao.findById(123);  // 数据库查询
User user1 = userTemplate.clone();  // 快速克隆,避免重复查DB
User user2 = userTemplate.clone();

原型模式怎么实现?

Java提供了Cloneable接口和clone()方法来支持原型模式:

基础实现

/**
 * 原型模式基础实现
 * 实现Cloneable接口,重写clone方法
 */
public class Prototype implements Cloneable {
    private String name;
    private int age;
    
    public Prototype(String name, int age) {
        this.name = name;
        this.age = age;
        // 模拟复杂的初始化过程
        System.out.println("正在创建原型对象,这个过程很耗时...");
        try {
            Thread.sleep(1000);  // 模拟耗时操作
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public Prototype clone() {
        try {
            return (Prototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("克隆失败", e);
        }
    }
    
    // getter和setter省略...
}

使用示例

public class PrototypeTest {
    public static void main(String[] args) {
        System.out.println("=== 传统方式创建对象 ===");
        long start1 = System.currentTimeMillis();
        Prototype obj1 = new Prototype("张三", 25);
        Prototype obj2 = new Prototype("李四", 30);
        long end1 = System.currentTimeMillis();
        System.out.println("传统方式耗时:" + (end1 - start1) + "ms");
        
        System.out.println("\n=== 原型模式创建对象 ===");
        long start2 = System.currentTimeMillis();
        Prototype template = new Prototype("模板", 0);
        Prototype obj3 = template.clone();
        obj3.setName("王五");
        obj3.setAge(28);
        Prototype obj4 = template.clone();
        obj4.setName("赵六");
        obj4.setAge(32);
        long end2 = System.currentTimeMillis();
        System.out.println("原型模式耗时:" + (end2 - start2) + "ms");
    }
}

原型模式工作流程

原型模式,一键克隆,告别重复造轮子_第1张图片

深克隆 vs 浅克隆

这是原型模式中最重要的概念!很多人都在这里栽过跟头。

浅克隆(Shallow Clone)

/**
 * 浅克隆示例 - 对象内部的引用类型不会被克隆
 */
public class ShallowStudent implements Cloneable {
    private String name;
    private int age;
    private List<String> hobbies;  // 引用类型
    
    public ShallowStudent(String name, int age) {
        this.name = name;
        this.age = age;
        this.hobbies = new ArrayList<>();
    }
    
    @Override
    public ShallowStudent clone() {
        try {
            // 这里只是浅克隆!
            return (ShallowStudent) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
    
    // getter和setter省略...
}

// 测试浅克隆的问题
public class ShallowCloneTest {
    public static void main(String[] args) {
        ShallowStudent original = new ShallowStudent("张三", 20);
        original.getHobbies().add("篮球");
        original.getHobbies().add("游戏");
        
        ShallowStudent clone = original.clone();
        clone.setName("李四");  // 基本类型,独立修改
        clone.getHobbies().add("足球");  // 引用类型,会影响原对象!
        
        System.out.println("原对象爱好:" + original.getHobbies());  // [篮球, 游戏, 足球]
        System.out.println("克隆对象爱好:" + clone.getHobbies());    // [篮球, 游戏, 足球]
        // 坑!两个对象共享同一个hobbies列表!
    }
}

深克隆(Deep Clone)

/**
 * 深克隆示例 - 递归克隆所有引用类型
 */
public class DeepStudent implements Cloneable {
    private String name;
    private int age;
    private List<String> hobbies;
    private Address address;  // 自定义对象
    
    public DeepStudent(String name, int age) {
        this.name = name;
        this.age = age;
        this.hobbies = new ArrayList<>();
        this.address = new Address();
    }
    
    @Override
    public DeepStudent clone() {
        try {
            DeepStudent cloned = (DeepStudent) super.clone();
            
            // 深克隆引用类型
            cloned.hobbies = new ArrayList<>(this.hobbies);
            cloned.address = this.address.clone();
            
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

/**
 * 地址类也需要支持克隆
 */
public class Address implements Cloneable {
    private String city;
    private String street;
    
    @Override
    public Address clone() {
        try {
            return (Address) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
    
    // getter和setter省略...
}

使用序列化实现深克隆

/**
 * 通过序列化实现深克隆 - 最简单的方式
 */
public class SerializableStudent implements Serializable {
    private String name;
    private int age;
    private List<String> hobbies;
    private Address address;
    
    // 构造函数省略...
    
    /**
     * 通过序列化实现深克隆
     */
    public SerializableStudent deepClone() {
        try {
            // 序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            oos.close();
            
            // 反序列化
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            SerializableStudent cloned = (SerializableStudent) ois.readObject();
            ois.close();
            
            return cloned;
        } catch (Exception e) {
            throw new RuntimeException("深克隆失败", e);
        }
    }
}

深克隆 vs 浅克隆对比

原型模式,一键克隆,告别重复造轮子_第2张图片

原型模式的最佳实践

1. 原型管理器

在实际项目中,我们通常会创建一个原型管理器来统一管理原型对象:

/**
 * 原型管理器 - 管理和提供各种原型对象
 */
public class PrototypeManager {
    private Map<String, Cloneable> prototypes = new HashMap<>();
    
    // 单例模式
    private static PrototypeManager instance = new PrototypeManager();
    
    private PrototypeManager() {
        // 初始化常用原型
        prototypes.put("user", new User("默认用户", 0));
        prototypes.put("product", new Product("默认商品", 0.0));
        prototypes.put("order", new Order());
    }
    
    public static PrototypeManager getInstance() {
        return instance;
    }
    
    /**
     * 获取原型的克隆
     */
    public Object getPrototype(String key) {
        Cloneable prototype = prototypes.get(key);
        if (prototype == null) {
            throw new IllegalArgumentException("原型不存在: " + key);
        }
        
        try {
            Method cloneMethod = prototype.getClass().getMethod("clone");
            return cloneMethod.invoke(prototype);
        } catch (Exception e) {
            throw new RuntimeException("克隆失败", e);
        }
    }
    
    /**
     * 注册新的原型
     */
    public void registerPrototype(String key, Cloneable prototype) {
        prototypes.put(key, prototype);
    }
}

// 使用示例
public class PrototypeManagerTest {
    public static void main(String[] args) {
        PrototypeManager manager = PrototypeManager.getInstance();
        
        // 快速创建用户对象
        User user1 = (User) manager.getPrototype("user");
        user1.setName("张三");
        
        User user2 = (User) manager.getPrototype("user");
        user2.setName("李四");
    }
}

2. 抽象原型类

/**
 * 抽象原型类 - 定义克隆接口
 */
public abstract class AbstractPrototype implements Cloneable {
    
    @Override
    public AbstractPrototype clone() {
        try {
            return (AbstractPrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException("克隆失败", e);
        }
    }
    
    /**
     * 深克隆方法 - 子类需要重写
     */
    public abstract AbstractPrototype deepClone();
}

/**
 * 具体原型类
 */
public class ConcretePrototype extends AbstractPrototype {
    private String data;
    private List<String> list;
    
    public ConcretePrototype(String data) {
        this.data = data;
        this.list = new ArrayList<>();
    }
    
    @Override
    public ConcretePrototype deepClone() {
        ConcretePrototype clone = (ConcretePrototype) this.clone();
        clone.list = new ArrayList<>(this.list);
        return clone;
    }
    
    // getter和setter省略...
}

3. 使用建造者模式增强原型模式

/**
 * 结合建造者模式的原型对象
 */
public class ConfigurablePrototype implements Cloneable {
    private String type;
    private Map<String, Object> properties;
    
    private ConfigurablePrototype(Builder builder) {
        this.type = builder.type;
        this.properties = new HashMap<>(builder.properties);
    }
    
    @Override
    public ConfigurablePrototype clone() {
        try {
            ConfigurablePrototype cloned = (ConfigurablePrototype) super.clone();
            cloned.properties = new HashMap<>(this.properties);
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static class Builder {
        private String type;
        private Map<String, Object> properties = new HashMap<>();
        
        public Builder setType(String type) {
            this.type = type;
            return this;
        }
        
        public Builder addProperty(String key, Object value) {
            this.properties.put(key, value);
            return this;
        }
        
        public ConfigurablePrototype build() {
            return new ConfigurablePrototype(this);
        }
    }
}

// 使用示例
ConfigurablePrototype template = new ConfigurablePrototype.Builder()
    .setType("game-character")
    .addProperty("level", 1)
    .addProperty("health", 100)
    .build();

ConfigurablePrototype character1 = template.clone();
ConfigurablePrototype character2 = template.clone();

原型模式的注意事项

1. 循环引用问题

/**
 * 循环引用的问题
 */
public class Node implements Cloneable {
    private String name;
    private Node parent;
    private List<Node> children;
    
    @Override
    public Node clone() {
        try {
            Node cloned = (Node) super.clone();
            // 这里会导致无限递归!
            cloned.children = new ArrayList<>();
            for (Node child : this.children) {
                cloned.children.add(child.clone());  // 危险!
            }
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

解决方案:

public Node clone() {
    return clone(new HashMap<>());
}

private Node clone(Map<Node, Node> clonedNodes) {
    if (clonedNodes.containsKey(this)) {
        return clonedNodes.get(this);  // 避免重复克隆
    }
    
    try {
        Node cloned = (Node) super.clone();
        clonedNodes.put(this, cloned);
        
        cloned.children = new ArrayList<>();
        for (Node child : this.children) {
            cloned.children.add(child.clone(clonedNodes));
        }
        return cloned;
    } catch (CloneNotSupportedException e) {
        throw new RuntimeException(e);
    }
}

2. 性能考虑

/**
 * 性能优化建议
 */
public class OptimizedPrototype implements Cloneable {
    private String immutableData;  // 不可变对象可以共享
    private List<String> mutableData;  // 可变对象需要克隆
    
    @Override
    public OptimizedPrototype clone() {
        try {
            OptimizedPrototype cloned = (OptimizedPrototype) super.clone();
            // 不可变对象可以共享,节省内存
            // cloned.immutableData = this.immutableData;  // 这行其实不需要
            
            // 只克隆可变对象
            cloned.mutableData = new ArrayList<>(this.mutableData);
            return cloned;
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

面试热点问题

Q1:原型模式和工厂模式有什么区别?

答:

  • 创建方式不同:工厂模式通过工厂方法创建新对象,原型模式通过克隆现有对象创建
  • 性能不同:原型模式避免了复杂的初始化过程,性能更好
  • 使用场景不同:工厂模式用于创建不同类型的对象,原型模式用于创建相似对象

Q2:深克隆和浅克隆的区别?如何实现深克隆?

答:

  • 浅克隆:只复制对象本身,引用类型字段仍指向原对象
  • 深克隆:递归复制所有引用类型字段,创建完全独立的对象副本

深克隆实现方法:

  1. 重写clone方法,递归克隆所有引用类型
  2. 使用序列化/反序列化
  3. 使用JSON序列化工具(如Jackson、Gson)

Q3:原型模式有什么缺点?

答:

  1. 复杂性增加:每个类都需要实现克隆方法
  2. 循环引用问题:对象间存在循环引用时克隆复杂
  3. 深克隆性能:深克隆可能比直接创建对象更耗时
  4. 维护成本:当对象结构变化时,克隆方法也需要维护

Q4:在什么情况下不适合使用原型模式?

答:

  • 对象创建本身就很简单快速
  • 对象很少被复制
  • 对象包含大量不可序列化的资源(如文件句柄、网络连接)
  • 对象存在复杂的循环引用关系

Q5:Spring框架中如何使用原型模式?

答:
Spring中Bean的scope可以设置为prototype

@Component
@Scope("prototype")  // 每次获取都创建新实例
public class PrototypeBean {
    // ...
}

// 或者在XML中配置
<bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype"/>

每次从容器获取都会创建新的实例,类似于原型模式的效果。

总结

原型模式虽然看起来简单,但要用好需要注意很多细节:

适用场景

  • 对象创建成本高:数据库查询、复杂计算、网络请求等
  • 大量相似对象:游戏中的NPC、文档模板等
  • 对象状态保存:游戏存档、撤销操作等

最佳实践

  • 明确深浅克隆需求:根据业务场景选择合适的克隆策略
  • 使用原型管理器:统一管理和提供原型对象
  • 注意循环引用:复杂对象图需要特殊处理
  • 考虑性能影响:深克隆不一定比new对象更快

记住

原型模式的精髓是"复制"而非"创建"。它解决的是对象创建成本高的问题,而不是对象创建复杂度的问题。


“复制粘贴虽好,但要记住:复制的是结构,粘贴的是新生命!”

你可能感兴趣的:(原型模式)