zoukankan      html  css  js  c++  java
  • Java设计模式:Prototype(原型)模式

    概念定义

    使用原型实例指定待创建对象的种类,并通过拷贝该原型来创建新的对象。Prototype模式允许一个原型对象克隆(复制)出多个与其相同的对象,而无需知道任何如何创建的细节。

    应用场景

    • 对象的创建过程较为复杂且需要频繁创建
    • 期望根据现有的实例来生成新的实例,例如:
      • 对象种类繁多而无法整合到一个类时
      • 难以通过指定类名生成实例时
      • 希望解耦框架与生成的实例时

    在实际应用中,Prototype模式很少单独出现。经常与其他模式混用。

    原型实现

    所有Java类都继承自java.lang.Object,而Object类提供clone()方法来克隆对象。因此,Java类实现Cloneable接口并重写clone()方法后,即可实现Prototype模式

    实现Prototype模式的示例代码如下:

    // 原型类(也可定义为interface Prototype extends Cloneable)
    abstract class Prototype implements Cloneable {
        private String name;
        public void setName(String name) {this.name = name;}
        public String getName() {return this.name;}
    
        public abstract void doSomething();
    
        @Override
        public Object clone() { // clone()方法用public修饰
            Object object = null;
            try {
                object = super.clone(); // 调用父类clone()方法
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return object;
        }
    }
    
    // 具体实现类(继承自Prototype类自动具有克隆功能)
    class ConcretePrototype extends Prototype{
        public ConcretePrototype() { setName("ConcretePrototype"); }
    
        @Override
        public void doSomething() { System.out.println("I'll clone myself!"); }
    }
    
    public class PrototypeDemo {
        public static void main(String[] args) {
            ConcretePrototype cp = new ConcretePrototype();
            cp.doSomething();
    
            for(int i=0; i< 5; i++){
                ConcretePrototype cloned = (ConcretePrototype)cp.clone();
                System.out.println("I'm cloned by " + cloned.getName());
            }
        }
    }
    

    执行后输出如下:

    I'll clone myself!
    I'm cloned by ConcretePrototype
    I'm cloned by ConcretePrototype
    I'm cloned by ConcretePrototype
    I'm cloned by ConcretePrototype
    I'm cloned by ConcretePrototype
    

    模式优缺点

    Prototype模式的优点如下:

    • 根据客户端要求实现运行时动态创建对象,客户端不需要知道对象的创建细节,便于代码的维护和扩展。
    • 当对象的创建较为复杂或重复创建大量相似对象时,可简化对象创建,且性能比直接new对象更高(new会自动调用构造链中的所有构造方法,但clone不会调用任何类构造方法)。
    • Prototype模式类似工厂模式,但没有工厂模式中的抽象工厂和具体工厂的层级关系,代码结构更清晰和简单。

    缺点如下:

    • 需要为每个类实现Cloneable接口并重写clone()方法,改造已有类时必须修改其源码,违背"开闭原则"。
    • 单例模式与工厂模式、Prototype模式是冲突的,尽量不要混用。
    • 在实现深拷贝(深克隆)时需要编写较为复杂的代码。

    注意事项

    clone()方法只拷贝对象中的基本数据类型,而不会拷贝数组、容器对象、引用对象等。若要实现深拷贝,必须将Prototype模式中的数组、容器对象、引用对象等另行拷贝
    例如,深拷贝一个对象时,该对象必须实现Cloneable接口并重写clone()方法,并在clone()方法内部将该对象引用的其他对象也克隆一份。同理,被引用的对象也要做同样的处理。
    注意,Boolean、Byte、Character、Class、Double、Float、Integer、Long、Short、String以及大部分的Exception的子类均为不可变类,其对象没有必要实现深拷贝。此外,大部分的Java容器类都已实现克隆功能。

    相较而言,clone()方法更适合数组深拷贝。但需要注意以下两点:
    (1) 基本类型的一维数组可通过诸如(int[])data.clone()的形式克隆,而基本类型的"二维数组"(实际上是类型诸如int[]的一维数组)需要按维克隆。例如:

    public static int[][] twoDimensionArrayClone (int[][] tdArray){
        int[][] copy = tdArray.clone();
        for (int i = 0; i < tdArray.length; i++) {
            copy[i] = tdArray[i].clone();
        }
        return copy;
    }
    

    (2) 当数组元素为普通Java对象时,克隆数组后还要克隆数组中包含的对象。以下示例详细展示了数组对象的不同浅拷贝和深拷贝方式:

    class Person implements Cloneable {
        public String name;
        public Person(String name) {this.name = name;}
    
        @Override
        public Object clone() {
            Object object = null;
            try {
                object = super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return object;
        }
    }
    
    public class ArrayPrototype {
        public static void main(String[] args) {
            Person[] origin = new Person[] { new Person("Mike"), new Person("Jack"), new Person("Jason") };
            Person[] copyOf = Arrays.copyOf(origin, origin.length); // 浅拷贝(内部调用System.arraycopy,返回新数组)
            Person[] arrayCopy = new Person[origin.length]; // 浅拷贝(可拷贝部分数组)
            System.arraycopy(origin, 0, arrayCopy, 0, origin.length);
            Person[] clonedCopy = origin.clone(); // 浅拷贝
            System.out.println("origin=copyOf=arrayCopy=clonedCopy=" +
                    (origin[0] == copyOf[0] && copyOf[1] == arrayCopy[1] && arrayCopy[2] == clonedCopy[2]));
            clonedCopy[0].name = "Lily";
            System.out.println("Shallow Person[0]: " + origin[0].name + " -> " + clonedCopy[0].name);
    
            Person[] deepCopy = new Person[origin.length]; // 深拷贝
            for(int i = 0; i < origin.length; i++) { deepCopy[i] = (Person) origin[i].clone(); }
            deepCopy[1].name = "Lily";
            System.out.println("Deep Person[1]: " + origin[1].name + " -> " + clonedCopy[1].name);
            Person[] deepCopy2 = Arrays.stream(origin).map(Person::clone).toArray(Person[]::new); // 深拷贝
            deepCopy2[2].name = "Lucy";
            System.out.println("Deep Person[2]: " + origin[2].name + " -> " + deepCopy2[2].name);
            //origin=copyOf=arrayCopy=clonedCopy=true
            //Shallow Person[0]: Lily -> Lily
            //Deep Person[1]: Jack -> Jack
            //Deep Person[2]: Jason -> Lucy
        }
    }
    

    业界实践

    • Action对象(Struts2)
    • prototype创建的bean实例(Spring)
  • 相关阅读:
    Jenkins使用三:管理slave节点(配置SSH公钥和私钥)
    Jenkins使用二:新建任务
    Jenkins使用一:CentOS7安装Jenkins
    CentOS安装MongoDB
    测开之路七十八:shell之函数和参数
    测开之路七十七:shell之if、case、for、while
    测开之路七十六:linux变量和环境变量
    测开之路七十五:linux常用命令
    linux暴露端口可以被外部访问
    WEB框架概述(译)
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/11600486.html
Copyright © 2011-2022 走看看