栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

设计模式之原型模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

设计模式之原型模式

前言

今天就不想开头了,直接来吧。如果想要创建几个和某对象一模一样的新对象,我们很容易想到new对象,在构造器里面进行复制即可。但是今天就看一个新模式,原型模式。

创建对象的正常方法(蠢方法) 背景

新建一个Sheep类,并在客户端Client里面创建一个sheep对象,那我们还想要多添加几个克隆羊,即和sheep对象一模一样的几个对象。我们很容易想到的是,直接new方法,再通过构造方法,将sheep里面的几个参数进行赋值,具体代码如下。

代码

Sheep类:

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

    public Sheep(String name, int age, String color) {
 this.name = name;
 this.age = age;
 this.color = color;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public int getAge() {
 return age;
    }

    public void setAge(int age) {
 this.age = age;
    }

    public String getColor() {
 return color;
    }

    public void setColor(String color) {
 this.color = color;
    }

    @Override
    public String toString() {
 return "Sheep{" +
  "name='" + name + ''' +
  ", age=" + age +
  ", color='" + color + ''' +
  '}';
    }

}

Client类:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
 Sheep sheep = new Sheep("tom", 1, "白色");

 Sheep sheep1=new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());
 Sheep sheep2=new Sheep(sheep.getName(),sheep.getAge(),sheep.getColor());

 System.out.println(sheep1.toString());
 System.out.println(sheep2.toString());
 System.out.println(sheep.hashCode()+","+sheep1.hashCode()+","+sheep2.hashCode());
    }
}

运行结果:

优点

比较好理解,简单易操作,傻白甜操作。

缺点

1.在创建新的对象时,总是需要获取原始对象的值,如果创建的对象比较复杂,效率较低。

2.总是需要重新初始化对象,而不是动态的获取对象运行时的状态,不够灵活

引入原型模式 官方概念

用原型实例(原来的对象)来指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。

白话文

就是通过一个旧对象,创建一个新对象,即克隆羊多利。打个比方,咱能单个复制多利身上的部位,然后拼起来,但是这种方法麻烦且低效,部位的增加可能导致越来越低能。我们也能通过某种方法,直接复制所有。某种方法映射到设计模式里面就是原型模式。

创建对象的新方法(clone) 方法概念描述

原型类需要具备以下两个条件:

  • 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。
  • 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,原型类需要将clone方法的作用域修改为public类型。
代码

Sheep类:(与之前的Sheep区别就是实现了Cloneable接口及重写了clone方法)

public class Sheep implements Cloneable   {
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
 this.name = name;
 this.age = age;
 this.color = color;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public int getAge() {
 return age;
    }

    public void setAge(int age) {
 this.age = age;
    }

    public String getColor() {
 return color;
    }

    public void setColor(String color) {
 this.color = color;
    }

    @Override
    public String toString() {
 return "Sheep{" +
  "name='" + name + ''' +
  ", age=" + age +
  ", color='" + color + ''' +
  '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
 Sheep otherSheep=(Sheep)super.clone();
 return otherSheep;
    }
}

Client类:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
 Sheep sheep = new Sheep("tom", 1, "白色");

 Sheep sheep1 = (Sheep) sheep.clone();
 System.out.println(sheep.toString());
 System.out.println(sheep1.toString());
 System.out.println(sheep.hashCode()+","+sheep1.hashCode());
    }
}

运行结果:

插曲(为什么要实现Cloneable接口,可不可以不实现?)

在上面的方法描述中我们可以知道了要实现Cloneable接口,但是我们看看如果不实现,有什么问题?

Sheep类:

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

   //构造器,setter/getter,toString就先略去了,看重点

    @Override
    protected Object clone() {
 Sheep otherSheep=null;
 try{
      otherSheep=(Sheep)super.clone();
     return otherSheep;
 }catch(Exception e){
     e.printStackTrace();
 }
 return  otherSheep;
    }
}

test类一样,我就不贴了,直接来看运行结果:

我们可以看到直接抛出了异常,这是因为如果对象的类不支持Cloneable接口,重写clone方法的子类也可以引发此异常以指示无法克隆实例。所以啊,都要写,不要偷懒。

浅拷贝

举个例子,如果羊对象里面有个house这种的复杂对象,我们看能不能拷贝成功。

Sheep类:

public class Sheep implements Cloneable{
    private String name;
    private int age;
    private String color;
    private House house;

    public Sheep(String name, int age, String color,House house) {
 this.name = name;
 this.age = age;
 this.color = color;
 this.house=house;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public int getAge() {
 return age;
    }

    public void setAge(int age) {
 this.age = age;
    }

    public String getColor() {
 return color;
    }

    public void setColor(String color) {
 this.color = color;
    }

    public House getHouse() {
 return house;
    }

    public void setHouse(House house) {
 this.house = house;
    }

    @Override
    public String toString() {
 return "Sheep{" +
  "name='" + name + ''' +
  ", age=" + age +
  ", color='" + color + ''' +
  ", house=" + house +
  '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
return super.clone();
    }
}

House类:

public class House implements Cloneable {
    private String name;
    private String address;

    public House(String name, String address) {
 this.name = name;
 this.address = address;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public String getAddress() {
 return address;
    }

    public void setAddress(String address) {
 this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
 return super.clone();
    }
}

Client类:

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
 House house=new House("羊羊家","地球上");
 Sheep sheep = new Sheep("tom", 1, "白色",house);

 Sheep sheep1 = (Sheep) sheep.clone();

 System.out.println(sheep.getHouse().hashCode()+","+sheep1.getHouse().hashCode());
    }
}

运行结果:

我们可以看到两个Sheep对象中house的hashCode并没有变化,说明他们实际上指向的是同一片空间,即为浅拷贝,那我们之前的例子都是浅拷贝,因为Sheep对象中的各个属性的hashCode是一样的,他们指向的是同一片内存空间。那我们应该如何让里面对象的值也不一样呢?那就是下面的深拷贝。

深拷贝(复杂对象) 方法一(使用自己子对象的clone方法)

Sheep类:(与之前的区别只是方法上的区别,所以就不贴重复代码啦)

@Override
    protected Object clone() throws CloneNotSupportedException {
 Sheep otherSheep=(Sheep) super.clone();

 otherSheep.setHouse((House)house.clone());
 return  otherSheep;
    }

方法二(序列化和反序列化)

public class Sheep implements Serializable, Cloneable {
    private String name;
    private int age;
    private String color;
    private House house;

 //setter/getter方法,构造器,toString方法都略去了

    public Object clone2() {
 Sheep sheep = null;

 ByteArrayOutputStream bos = null;
 ObjectOutputStream oos = null;
 ByteArrayInputStream bis = null;
 ObjectInputStream ois = null;
 try {
     //序列化
     bos = new ByteArrayOutputStream();
     oos = new ObjectOutputStream(bos);
     oos.writeObject(this);

     bis = new ByteArrayInputStream(bos.toByteArray());
     ois = new ObjectInputStream(bis);
     sheep = (Sheep) ois.readObject();
 } catch (Exception e) {
     e.printStackTrace();
 } finally {
     //关闭流
     try {
  bos.close();
  oos.close();
  bis.close();
  ois.close();
     } catch (Exception e) {
  e.printStackTrace();
     }
 }
 return sheep;
    }
} 

public class House implements Serializable,Cloneable {
    private String name;
    private String address;

  //setter/getter方法,构造器,toString方法都略去了
}

优点

在内存中二进制流的拷贝,比直接new一个对象的性能要好,毕竟人家是native方法,可以直接操作内存,是亲儿子。

缺点

没错,亲儿子也有缺点。当处理简单类的时候,他的性能还不如new,但当处理复杂类的时候,性能就比new高出一大截啦。

咱来试试,普通的House类,有两个基本类型的参数,分别是name和address,我们看new和利用原型模式分别用了多少秒?

public class House implements Serializable,Cloneable {
    private String name;
    private String address;

    public House(String name, String address) {
 this.name = name;
 this.address = address;
    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public String getAddress() {
 return address;
    }

    public void setAddress(String address) {
 this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
 return super.clone();
    }
}

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
 long startTime=System.currentTimeMillis();
 for(int i=0;i<100000000;i++){
     new House("羊羊家","地球上");
 }
 System.out.println(System.currentTimeMillis()-startTime);

 startTime=System.currentTimeMillis();
 House house=new House("羊羊家","地球上");
 for(int i=0;i<100000000;i++){
     house.clone();
 }
 System.out.println(System.currentTimeMillis()- startTime);
    }
} 

运行结果:

从上图我们可以看到采用new方法,只用了13秒,而采用原型模式,却花了接近900秒,但是将House类的构造方法做一点点改变,我们就能发现差别,如下:

public class House implements Serializable,Cloneable {
    private String name;
    private String address;

    public House(String name, String address) {
 this.name = name.substring(1);
 this.address = address;

    }

    public String getName() {
 return name;
    }

    public void setName(String name) {
 this.name = name;
    }

    public String getAddress() {
 return address;
    }

    public void setAddress(String address) {
 this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
 return super.clone();
    }
}

运行结果:

从上述代码可以看到,采用原型模式的时间比new方法的时间少了一半左右。

结语

原型模式属于创建型模式,能产生新的对象。

其对复制功能进行优化,采用Java提供的native方法clone,前提是实现cloneable接口。

使用默认的clone方法,是浅拷贝,只是复制值,实际上地址并没有变化。如果想要深拷贝,可以重写clone方法或采用序列化和反序列化。

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号