zoukankan      html  css  js  c++  java
  • 原型模式

     

    一、原型模式的作用?

    1、基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例的话,就需要使用原型模式。

    2、用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。这个其实和C++的拷贝构造函数的作用是相似的(但不相同),实际上就是动态抽取 当前对象 运行时 的 状态。

    3、当然有的时候,如果我们并不需要基于现有的对象复制新的对象,或者我们需要的就是一个干净的空对象,那么我的首先还是工厂模式或者抽象工厂模式。

    二、为什么需要原型模式?

    1、为什么不用new直接新建对象,而要用原型模式?

    首先,用new新建对象不能获取当前对象运行时的状态,其次就算new了新对象,在将当前对象的值复制给新对象,效率也不如原型模式高。

    2、为什么不直接使用拷贝构造函数,而要使用原型模式?

    原型模式与拷贝构造函数是不同的概念,拷贝构造函数涉及的类是已知的,原型模式涉及的类可以是未知的(基类的拷贝构造函数只能复制得到基类的对象)。

    原型模式生成的新对象可能是一个派生类。拷贝构造函数生成的新对象只能是类本身。原型模式是描述了一个通用方法(或概念),它不管是如何实现的,而拷贝构造则是描述了一个具体实现方法。

    三、使用场景

    1、资源优化场景

    类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

    2、性能和安全要求的场景

    通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

    3、一个对象多个修改者的场景

    一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

    4、结合使用

    在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与Java融为浑然一体,大家可以随手拿来使用。

    四、缺点

    1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

    2、实现原型模式每个派生类都必须实现 Clone接口。

    3、逃避构造函数的约束。

    1.前言


    单例模式可以避免重复创建消耗资源的对象,但是却不得不共用对象。若是对象本身也不让随意访问修改时,怎么办?通常做法是备份到副本,其它对象操作副本,最后获取权限合并,类似git上的PR操作。

    2.概念


    原型模式用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。需要注意的关键字是,新的对象,类没变。Java正好提供了Cloneable接口,它标识的类可以调用Object中实现的clone()方法而不抛出异常,即运行时通知虚拟机可以安全使用clone()方法返回拷贝对象。由于它直接操作内存中的二进制流,当大量操作或操作复杂对象时,性能优势将会很明显。

    3.场景


    动物园中有一只羊,对它进行克隆,产生另外一只完全一样的羊,分别安排两位有孩子的管理员照顾。有一天,对克隆羊进行基因操作,观察变化。

    4.写法

    // 1.声明此类可以被clone
    public class Sheep implements Cloneable {
        
        private int age;
        private String sex;
        private Admin admin;
    
        public Sheep(int age, String sex, Admin admin) {
            super();
            this.age = age;
            this.sex = sex;
            this.admin = admin;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public Admin getAdmin() {
            return admin;
        }
    
        public void setAdmin(Admin admin) {
            this.admin = admin;
        }
    
        @Override
        public String toString() {
            return "Sheep [age=" + age + ", sex=" + sex + ", admin=" + admin + "]";
        }
        
        // 2.调用Object的clone方法
        public Sheep startClone() {
            Sheep sheep = null;
            try {
                sheep = (Sheep) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return sheep;
        }
    
    }
    
    public class Admin {
        
        private int age;
        private String sex;
        private Child child;
        public Admin(int age, String sex, Child child) {
            super();
            this.age = age;
            this.sex = sex;
            this.child = child;
        }
        
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public void setChild(Child child) {
            this.child = child;
        }
    
        @Override
        public String toString() {
            return "Admin [age=" + age + ", sex=" + sex + ", child=" + child + "]";
        }
        
    }
    
    public class Child {
    
    }
    
    public class Zoo {
    
        public static void main(String[] args) {
            Sheep old = new Sheep(2, "雄性", new Admin(25, "女", new Child()));
            System.out.println(old.toString());
            Sheep current = old.startClone();
            System.out.println(current.toString());
            
            // 对克隆羊做处理
            current.setAge(1);
            current.setSex("雌性");
            current.getAdmin().setAge(34);
            current.getAdmin().setSex("男");
            System.out.println(old.toString());
            System.out.println(current.toString());
        }
    
    }
    

    根据上面的代码,我们知道羊引用了管理员,管理员引用了孩子。当对内存中数据拷贝时,除了基本数据类型(包括封装类型)及String类型,其它的引用关系将直接传递给副本,并不是重新创建一个对象。所以当对克隆羊操作时,年龄和性别直接改变,而对管理员的操作将寻址到内存中对应部分进行修改,导致原型也被修改。孩子与管理员的关系就如同管理员与羊,通过哈希值可以知道,孩子始终就一个,没有拷贝成功。


     
    light clone.png

    上面的错误是由于只拷贝了最外层对象的原因,我们称之为浅拷贝。为了解决这个问题,需要对内部的引用类型进行拷贝(Java中大部分引用类型实现了Cloneable接口,可以方便的拷贝),具体如下:

    // 1.声明此类可以被clone
    public class Sheep implements Cloneable {
    
        // 前面省略
        
        // 2.调用Object的clone方法
        public Sheep startClone() {
            Sheep sheep = null;
            try {
                sheep = (Sheep) super.clone();
                
                // 3.调用Admin的clone方法
                sheep.admin = this.admin.startClone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return sheep;
        }
    
    }
    
    public class Admin implements Cloneable {
    
        // 前面省略
        
        public Admin startClone() {
            Admin admin = null;
            try {
                admin = (Admin) super.clone();
                admin.child = this.child.startClone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return admin;
        }
        
    }
    
    public class Child implements Cloneable {
    
        public Child startClone() {
            Child child = null;
            try {
                child = (Child) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return child;
        }
        
    }
    

    通过日志的打印,发现这种方式(深拷贝)起作用了。由1、2行可以知道,拷贝时引用类型已经重新创建了对象。由3、4行可以知道,修改其中一个对象不会再改变另一个了。


     
    deep clone.png

    5.总结


    原型模式通过Object的clone()方法实现,由于是内存操作,即二进制流的形式,无视构造方法和访问权限,直接获取新的对象。但对于引用类型,需使用深拷贝,其它浅拷贝即可。

    (1)浅拷贝:直接使用Object类的native本地clone方法,该方法是基于二进制形式拷贝的,前提是被拷贝的类必须实现了标记型接口Cloneable。

    (2)深拷贝:举例说明,A类中有B类对象引用属性,B类中有C类对象的引用属性,C类里面的属性全是基本类型(String也是基本类型)。如果想要深拷贝A类对象,那么要求A类中的B类对象引用属性也需要深拷贝一次,B类中C类对象引用属性也需要深拷贝一次。因此需要层层递归深拷贝,每一层都是深拷贝,除非是没有引用属性的类。所有引用属性都需要在clone方法中单独深拷贝一次。每层都需要实现深拷贝,即每层都实现了Cloneable接口和覆写了clone方法。

    #############################################################################################################################

    深拷贝举例。

    ----------------------------------------------------------------------------------------------------------------------------------------------------------

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------

    ---------------------------------------------------------------------------------------------------------------------------------------------------------------------




  • 相关阅读:
    HTML DOM教程 14HTML DOM Document 对象
    HTML DOM教程 19HTML DOM Button 对象
    HTML DOM教程 22HTML DOM Form 对象
    HTML DOM教程 16HTML DOM Area 对象
    ubuntu 11.04 问题 小结
    VC6.0的 错误解决办法 小结
    boot.img的解包与打包
    shell里 截取字符串
    从零 使用vc
    Imagemagick 对图片 大小 和 格式的 调整
  • 原文地址:https://www.cnblogs.com/igoodful/p/9438619.html
Copyright © 2011-2022 走看看