zoukankan      html  css  js  c++  java
  • 设计模式之原型模式(Prototype)详解及代码示例

    一、原型模式的定义与特点

      原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。它属于创建型设计模式,用于创建重复的对象,同时又能保证性能(用这种方式创建对象非常高效)。

      这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

    二、原型模式优点

    • 性能优良:原型模式是在内存二进制流的拷贝,要比new一个对象性能好很多,特别是在一个循环体类产生大量对象的时候更加明显。
    • 逃避构造函数的约束:这是优缺点共存的一点,直接在内存中拷贝,构造函数是不会执行的。

    三、原型模式的使用场景

    • 资源初始化场景:类初始化需要消耗非常多的资源的时候。
    • 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备和访问权限的时候。
    • 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而各个调用者可能都需要修改其值时考虑使用。

      实际项目中原型模式很少单独出现,一般和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。

    四、原型模式的结构与实现

      由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单,只需要实现Cloneable接口并重写clone()方法,简单程度仅次于单例模式和迭代器模式。

      原型模式包含以下主要角色。

    • 抽象原型类:规定了具体原型对象必须实现的接口。
    • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
    • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

      其结构图如图所示:

                  

       代码实现如下:

    //具体原型类
    class Realizetype implements Cloneable
    {
        Realizetype()
        {
            System.out.println("具体原型创建成功!");
        }
        public Object clone() throws CloneNotSupportedException
        {
            System.out.println("具体原型复制成功!");
            return (Realizetype)super.clone();
        }
    }
    
    //原型模式的测试类
    public class PrototypeTest
    {
        public static void main(String[] args)throws CloneNotSupportedException
        {
            Realizetype obj1=new Realizetype();
            Realizetype obj2=(Realizetype)obj1.clone();
            System.out.println("obj1==obj2?"+(obj1==obj2));
        }
    }

      结果如下:

    具体原型创建成功!    //此处构造方法在clone的时候并不会执行,因为对象是从内存以二进制流的方式进行拷贝,当然不会执行
    具体原型复制成功!
    obj1==obj2?false   //不是同一个对象

    五、补充说明

      1、构造方法clone时不会执行

      构造方法在clone的时候并不会执行,因为对象是从内存以二进制流的方式进行拷贝,当然不会执行,如上例子中所示。

      2、深拷贝、浅拷贝

      如下代码,就是浅拷贝:

    class Thing2 implements Cloneable {
        private ArrayList<String> list = new ArrayList<String>();
    
        @Override
        public Thing2 clone() {
            Thing2 thing = null;
            try {
                thing = (Thing2) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
                System.out.println("克隆失败");
            }
            return thing;
        }
    
        public void setValue(String value) {
            this.list.add(value);
        }
    
        public ArrayList getValue() {
            return this.list;
        }
    }
    
    public class SimpleClone {
        public static void main(String[] args) {
            Thing2 thing = new Thing2();
            thing.setValue("张三");
            Thing2 cloneThing= thing.clone();
            cloneThing.setValue("李四");
            System.out.println(thing.getValue());
        }
    }

      测试结果输出为:

    原始对象:[张三, 李四]
    克隆对象:[张三, 李四]

      原型模式克隆出来的对象应该是相互独立的,那么为什么会出现原始对象和克隆对象之间共享访问呢?

      这是因为object类的clone方法只拷贝本对象,其对象内部的数组,引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就是浅拷贝。两个对象共用一个私有变量。这是一种非常不安全的方式。

      原始类型会被拷贝(int,double,long。。。),String类型也会被拷贝;数组、引用类型不会被拷贝

      使用原型类型时,引用的成员变量必须满足两个条件才不会被拷贝:

    • 是类的成员变量,而不是方法内的变量
    • 必须是一个可变的引用对象,而不是一个原始类型或者不可变对象(比如final)

      如下为深拷贝:

    class Thing1 implements Cloneable {
        private ArrayList<String> list = new ArrayList<String>();
    
        @Override
        public Thing1 clone() {
            Thing1 thing = null;
            try {
                thing = (Thing1) super.clone();
                thing.list = (ArrayList) this.list.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
                System.out.println("克隆失败");
            }
            return thing;
        }
    
        public void setValue(String value) {
            this.list.add(value);
        }
    
        public ArrayList getValue() {
            return this.list;
        }
    }
    
    public class DeepClone {
        public static void main(String[] args) {
            Thing1 thing = new Thing1();
            thing.setValue("张三");
            Thing1 cloneThing= thing.clone();
            cloneThing.setValue("李四");
            System.out.println("原始对象:"+thing.getValue());
            System.out.println("克隆对象:"+cloneThing.getValue());
    
        }
    }

      输出结果为:

    原始对象:[张三]
    克隆对象:[张三, 李四]

      注意:

    • 深拷贝和浅拷贝要分开实现,不然会导致程序变得非常复杂
    • 带有final类型的变量是不可以进行拷贝的,这样是无法实现深拷贝。

      这是因为final关键字的特性

      对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。因此:要使用clone()方法,类的成员变量上不要增加final关键字。

      3、原型模式的扩展

      原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。其结构图如图所示:

                    

       代码如下:

    import java.util.*;
    interface Shape extends Cloneable
    {
        public Object clone();    //拷贝
        public void countArea();    //计算面积
    }
    class Circle implements Shape
    {
        public Object clone()
        {
            Circle w=null;
            try
            {
                w=(Circle)super.clone();
            }
            catch(CloneNotSupportedException e)
            {
                System.out.println("拷贝圆失败!");
            }
            return w;
        }
        public void countArea()
        {
            int r=0;
            System.out.print("这是一个圆,请输入圆的半径:");
            Scanner input=new Scanner(System.in);
            r=input.nextInt();
            System.out.println("该圆的面积="+3.1415*r*r+"
    ");
        }
    }
    class Square implements Shape
    {
        public Object clone()
        {
            Square b=null;
            try
            {
                b=(Square)super.clone();
            }
            catch(CloneNotSupportedException e)
            {
                System.out.println("拷贝正方形失败!");
            }
            return b;
        }
        public void countArea()
        {
            int a=0;
            System.out.print("这是一个正方形,请输入它的边长:");
            Scanner input=new Scanner(System.in);
            a=input.nextInt();
            System.out.println("该正方形的面积="+a*a+"
    ");
        }
    }
    class ProtoTypeManager
    {
        private HashMap<String, Shape>ht=new HashMap<String,Shape>(); 
        public ProtoTypeManager()
        {
            ht.put("Circle",new Circle());
            ht.put("Square",new Square());
        } 
        public void addshape(String key,Shape obj)
        {
            ht.put(key,obj);
        }
        public Shape getShape(String key)
        {
            Shape temp=ht.get(key);
            return (Shape) temp.clone();
        }
    }
    public class ProtoTypeShape
    {
        public static void main(String[] args)
        {
            ProtoTypeManager pm=new ProtoTypeManager();    
            Shape obj1=(Circle)pm.getShape("Circle");
            obj1.countArea();          
            Shape obj2=(Shape)pm.getShape("Square");
            obj2.countArea();     
        }
    }

      测试结果为:

    这是一个圆,请输入圆的半径:3
    该圆的面积=28.2735
    
    这是一个正方形,请输入它的边长:3
    该正方形的面积=9
  • 相关阅读:
    AX 2012 Security Framework
    The new concept 'Model' in AX 2012
    How to debug the SSRS report in AX 2012
    Using The 'Report Data Provider' As The Data Source For AX 2012 SSRS Report
    Deploy SSRS Report In AX 2012
    AX 2012 SSRS Report Data Source Type
    《Taurus Database: How to be Fast, Available, and Frugal in the Cloud》阅读笔记
    图分析理论 大纲小结
    一文快速了解Posix IO 缓冲
    #转载备忘# Linux程序调试工具
  • 原文地址:https://www.cnblogs.com/jing99/p/12596288.html
Copyright © 2011-2022 走看看