设计模式之原型模式

设计模式之原型模式

1.克隆羊问题

现在有一只羊 tom,姓名为: tom, 年龄为:1,颜色为:白色,请编写程序创建和 tom羊属性完全相同的 5只羊。

传统方式

1.1 绘制UML类图

image-20200920214301948

1.2编写代码

public class Sheep {
    private Integer age;
    private String name;
    private String color;

    public Sheep(Integer age, String name, String color) {
        this.age = age;
        this.name = name;
        this.color = color;
    }
	//因为篇幅限制,这里省略getter和setter、有参构造方法以及toString的编写
}
public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep(1, "tom", "白色");
        Sheep sheep1 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
        Sheep sheep2 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
        Sheep sheep3 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
        Sheep sheep4 = new Sheep(sheep.getAge(), sheep.getName(), sheep.getColor());
        System.out.println(sheep);
        System.out.println(sheep1);
        System.out.println(sheep2);
        System.out.println(sheep3);
        System.out.println(sheep4);
    }
}

image-20200920214703863

1.3优缺点

  • 优点是比较好理解,简单易操作。
  • 在创建新的对象时,总是需要重新获取原始对象的属性,如果创建的对象比较复杂时,效率较低
  • 总是需要重新初始化对象,而不是动态地获得对象运行时的状态, 不够灵活

改进的思路分析:

​ 思路:Java 中 Object 类是所有类的根类,Object 类提供了一个 clone()方法,该方法可以将一个 Java 对象复制一份,但是需要实现

clone 的 Java 类必须要实现一个接口 Cloneable,该接口表示该类能够复制且具有复制的能力 =>原型模式

2.原型模式

2.1 概述

  • 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
  • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道创建的细节。
  • 工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即 对象.clone()

2.2 绘制UML类图

image-20200921075102380

UML类图说明:

  • Protptype:抽象原型类,实现Cloneable接口,也就是我们用户使用复制粘贴的功能。

  • ConcretePrototype: 具体的原型类, 实现一个克隆自己的操作,说明一下它有被克隆复制的功能。

  • Client:客户类提出创建对象的请求。

2.3 代码编写

//prototype抽象原型类
public abstract class Sheep implements Cloneable{
    private Integer age;
    private String name;
    private String color;
    private Sheep friend;//如果成员变量为对象,默认为浅拷贝
    public Sheep(Integer age, String name, String color) {
        this.age = age;
        this.name = name;
        this.color = color;
    }
    //重写clone方法
    @Override
    protected Sheep clone() throws CloneNotSupportedException {
        Sheep sheep =null;

        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }
//具体原型角色ConcretePrototype
 public class ConcretePrototype extends Sheep {
    public ConcretePrototype(Integer age, String name, String color) {
        super(age, name, color);
    }
}
//Client
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        ConcretePrototype concretePrototype = new ConcretePrototype(20,"汤姆","蓝色");
        concretePrototype.setFriend(new Sheep(21,"朋友","白色"));
        ConcretePrototype clone = (ConcretePrototype) concretePrototype.clone();
        ConcretePrototype clone1 = (ConcretePrototype) concretePrototype.clone();
        ConcretePrototype clone2 = (ConcretePrototype) concretePrototype.clone();
        ConcretePrototype clone3 = (ConcretePrototype) concretePrototype.clone();
        System.out.println(concretePrototype+"@@@"+concretePrototype.hashCode()+"@@@"+concretePrototype.getFriend().hashCode());
        System.out.println(clone+"@@@"+clone.hashCode()+"@@@"+clone.getFriend().hashCode());
        System.out.println(clone1+"@@@"+clone1.hashCode()+"@@@"+clone1.getFriend().hashCode());
        System.out.println(clone2+"@@@"+clone2.hashCode()+"@@@"+clone2.getFriend().hashCode());
        System.out.println(clone3+"@@@"+clone3.hashCode()+"@@@"+clone3.getFriend().hashCode());
    }
}

image-20200921085436835

我们可以看到,克隆出来的对象属性与之前的文件属性是一样的,但两个对象的HashCode是不一样的,也就是说创建了新的对象。

但是ConcretePrototype内的friend对象拷贝的是引用。因为Object.clone()方法默认是浅拷贝。

3.浅拷贝与深拷贝

浅拷贝

  • 对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象

  • 对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是

    该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在

    一个对象中修改该成员变量会影响到另一个对象的该成员变量值。

  • 我们上述案例就是浅拷贝,浅拷贝是使用默认的clone()方法来实现

深拷贝

  • 复制对象的所有基本数据类型的成员变量值

  • 为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就

    是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝。

实现方式:

  1. 重写clone方法来实现深拷贝

    			2. 通过对象序列化方式实现深拷贝(推荐)
    
public class A implements Cloneable, Serializable {
    //必须显式重写
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
public class Sheep implements Cloneable,Serializable{
    private Integer age;
    private String name;
    private String color;
    private A a;//如果成员变量为对象,默认为浅拷贝
    public Sheep(Integer age, String name, String color) {
        this.age = age;
        this.name = name;
        this.color = color;
    }
    //方式一:重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Sheep sheep =null;
        try {
            //这里完成对基本数据类型和String的拷贝
            sheep = (Sheep) super.clone();
            //对引用类型的属性,进行单独处理
            A a1 = (A) a.clone();
            sheep.setA(a1);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }
    //方式二:通过对象的序列化方式实现(推荐)
    public Object deepClone(){
        //创建流对象
        ByteArrayOutputStream bos =null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;
        Sheep obj = null;
        try {
            //序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            //将当前这个对象以对象流的方式输出
            oos.writeObject(this);
            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);
            obj = (Sheep)ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        finally {
            try {
                if(bos!=null){
                    bos.close();
                }
                if(oos!=null){
                    oos.close();
                }
                if(bis!=null){
                    bis.close();
                }
                if(ois!=null){
                    ois.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return obj;
    }
//省略getter、setter和toString
}

public class ConcretePrototype extends Sheep implements Cloneable , Serializable {
    public ConcretePrototype(Integer age, String name, String color) {
        super(age, name, color);
    }
}

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //方式一:重写clone方法
        /*ConcretePrototype concretePrototype = new ConcretePrototype(20,"汤姆","蓝色");
        concretePrototype.setA(new A());
        ConcretePrototype clone = (ConcretePrototype) concretePrototype.clone();
        ConcretePrototype clone1 = (ConcretePrototype) concretePrototype.clone();
        ConcretePrototype clone2 = (ConcretePrototype) concretePrototype.clone();
        ConcretePrototype clone3 = (ConcretePrototype) concretePrototype.clone();
        System.out.println(concretePrototype+"@@@"+concretePrototype.hashCode());
        System.out.println(clone+"@@@"+clone.hashCode());
        System.out.println(clone1+"@@@"+clone1.hashCode());
        System.out.println(clone2+"@@@"+clone2.hashCode());
        System.out.println(clone3+"@@@"+clone3.hashCode());*/
        //方式二:序列化
        ConcretePrototype concretePrototype = new ConcretePrototype(20,"汤姆","蓝色");
        concretePrototype.setA(new A());
        ConcretePrototype clone = (ConcretePrototype) concretePrototype.deepClone();
        ConcretePrototype clone1 = (ConcretePrototype) concretePrototype.deepClone();
        ConcretePrototype clone2 = (ConcretePrototype) concretePrototype.deepClone();
        ConcretePrototype clone3 = (ConcretePrototype) concretePrototype.deepClone();
        System.out.println(concretePrototype+"@@@"+concretePrototype.hashCode());
        System.out.println(clone+"@@@"+clone.hashCode());
        System.out.println(clone1+"@@@"+clone1.hashCode());
        System.out.println(clone2+"@@@"+clone2.hashCode());
        System.out.println(clone3+"@@@"+clone3.hashCode());
    }
}

运行结果:

方式一:

image-20200921102451165

方式二:

image-20200921105301691

注意:

  • A类,Sheep类,ConcretePrototype类都需要实现Cloneable和Serializable接口
  • 需要显式重写A类的clone方法,因为Object中的clone方法是protected修饰的,而protected修饰的方法只允许同包下的类和子类访问,Sheep类就调用不了A的clone方法,所以必须显示重写A类的方法。

4.注意事项

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率

  • 不用重新初始化对象,而是动态地获得对象运行时的状态

  • 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码

  • 在实现深克隆的时候可能需要比较复杂的代码

  • 缺点:需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了 ocp 原则,

posted @ 2020-09-21 11:05  毕竟是曾经  阅读(174)  评论(0编辑  收藏  举报