Java设计模式之原型模式

Wesley13
• 阅读 761

原型模式简介

原型模式实际上不算一种设计模式,应该说是一种技巧吧。当我们需要创建与已有对象一样的对象时,我们通常可以有两种容易想到的方法,一种是将已有对象指向另外一个重新创建的对象,如

//将old赋给new
Object newObject=oldObject;

这种做法是相当于newObject还是指向oldObject的地址,也就是说,二者实际上是一样的,未来也是一样的,随便对哪个对象进行更改,二者都会保持一致,因为可以把它们看做两个相同的“指针”;另外一种常见的做法是,重新创建一个对象,用new来实例化,这样就创建了另外一个对象,即向内存中再写入了一个对象,虽然内容一样,但地址不一样,但是这种做法费力,如果对象比较复杂的话。

原型模式在这种需求下就诞生了,我们知道Object乃一切对象的父类(超类),并且Object有一个原生的clone方法,但是该方法的调用必须要求类实现了Cloneable接口,虽然Cloneable接口只是一个摆设,里面空空荡荡,姑且就当Cloneable接口是clone方法实现的一个标志吧!我们可以创建一个类实现Cloneable即可,在覆写clone方法即可完成该类的克隆了。

原型模式的代码实现

下面写一个Dog类,该类实现了Cloneable接口,并且覆写了超类的clone方法,里面使用了super.clone,表示调用超类的原生代码即可。注意,Dog类有一个非基本数据类型的变量eye,下面会介绍这个点。

浅复制

package com.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Dog implements Cloneable,Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -2050795770781171788L;
    
    private String name;
    
    Eye eye;
    
    public Dog(Eye eye) {
        this.eye=eye;
    }

    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name=name;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Dog dog;
        dog=(Dog) super.clone();
        return dog;
    }    
}
class Eye implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = -2723012171722328322L;
    String name;
    public Eye(String name) {
        this.name=name;
    }
}

原型模式中的复制方法

在上面的原型模式代码中,我们覆写了clone方法,下面我们来进行一个测试:

测试代码一:

package com.prototype;

/**
 * @author zzw922cn
 *
 */
public class Test1 {

    public static void main(String[] args) throws CloneNotSupportedException {
        Dog dog = new Dog(new Eye("红眼睛"));
        dog.setName("狗一");
        
        Dog object1 = (Dog) dog.clone();
        
        object1.eye.name="绿眼睛";
        object1.setName("狗二");
        
        System.out.println(dog.eye.name);
        System.out.println(object1.eye.name);
        
        System.out.println(dog.getName());
        System.out.println(object1.getName());
        
        System.out.println(object1.equals(dog));
        System.out.println(object1==dog);
        System.out.println(object1.getClass().equals(dog.getClass()));
    }
}

在上面的代码中可以看到,object1是dog的克隆对象,当我们克隆完成以后,再对object1进行调用相关设置,改变其Eye类型的变量以及String类型的变量,会发生什么呢?dog是否会发生变化呢?并且object1与dog是否一样呢(equals和==)?它们是否属于同样的类呢?最后一个问题是毋庸置疑的,肯定是同一个类。

运行结果

绿眼睛
绿眼睛
狗一
狗二
false
false
true

从运行结果中可以看到,在object1修改了eye对象以后,dog的eye对象的name也自动由红眼睛变为绿眼睛,但是object1修改了String类型的name对象后,dog却保持原有的name对象。这二者有什么联系或区别吗?联系是String和Eye都是非基本数据类型,Java的八大基本数据类型有char,byte,int,short,long,float,double,boolean。区别是既然同属非基本数据类型,但是一个跟随克隆对象变化而变化,另外一个却保持不变,这是很奇怪的。因为它是String类型,String是一个例外。因此,总结一下clone方法,我们得知克隆对象的基本数据类型字段是原有对象字段的复制,但是非基本类型(String除外)并没有复制,而是对原有对象的非基本类型的一个引用罢了,这种情况正如博文一开始中的newObject与oldObject两者的关系。而String类型则是一个例外,它是对原有对象的一个复制,并非指向原有的String对象的地址。

这种clone一般称为浅复制,即并没有完全复制!

测试代码二

下面来进行一次深复制,即完全复制。我使用流的方式来进行深度复制,即完全拷贝。

深复制

package com.prototype;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Dog implements Cloneable,Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = -2050795770781171788L;
    
    private String name;
    
    Eye eye;
    
    public Dog(Eye eye) {
        this.eye=eye;
    }

    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name=name;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Dog dog;
        dog=(Dog) super.clone();
        return dog;
    }
    
    /* 深复制 */  
    public Object deepClone() throws IOException, ClassNotFoundException {  
  
        /* 写入当前对象的二进制流 */  
        ByteArrayOutputStream bos = new ByteArrayOutputStream();  
        ObjectOutputStream oos = new ObjectOutputStream(bos);  
        oos.writeObject(this);  
  
        /* 读出二进制流产生的新对象 */  
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());  
        ObjectInputStream ois = new ObjectInputStream(bis);  
        return ois.readObject();  
    } 
    
}
class Eye implements Serializable{
    /**
     * 
     */
    private static final long serialVersionUID = -2723012171722328322L;
    String name;
    public Eye(String name) {
        this.name=name;
    }
}

接着写一个类似的测试代码

package com.prototype;

import java.io.IOException;

public class Test2 {
/**
 * equal强调内容是否相同
 * =强调地址是否相同
 * @param args
 * @throws CloneNotSupportedException
 * @throws IOException 
 * @throws ClassNotFoundException 
 */
    public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
        Dog dog = new Dog(new Eye("红眼睛"));
        dog.setName("狗一");
        
        System.out.println("-----------------深复制--------------");
        Dog object2 = (Dog) dog.deepClone();
        object2.eye.name="绿眼睛";
        object2.setName("狗二");
        
        System.out.println(dog.eye.name);
        System.out.println(object2.eye.name);
        
        System.out.println(dog.getName());
        System.out.println(object2.getName());
        
        System.out.println(object2.equals(dog));
        System.out.println(object2==dog);
        System.out.println(object2.getClass().equals(dog.getClass()));
        
        
    }
}

运行测试结果:

-----------------深复制--------------
红眼睛
绿眼睛
狗一
狗二
false
false
true

我们看到深度复制,二者便“分道扬镳”了,除了复制之初两者是一样的之外,后续的任何变化都不会对彼此产生任何影响了。这就是深复制。

equals与==的区别

前面我们看到克隆对象与原始对象的equals和==都返回false,无论是浅复制还是深复制。那么equals和==到底是什么关系呢?

对于基本数据类型,==比较的是它们的值。而对于非基本类型的对象,==比较是它们在内存中的地址,如之前的oldObject与newObject二者的地址相同;而equals方法,如果它没有被子类覆写,它最原始的也是比较对象在内存中的地址,如果被子类覆写了,就不好说了。例如在String类型中,equals方法比较的是字符串的“表面值”,它并不是比较对象在内存中的地址,而==比较的是两个字符串在内存中的地址是否一样。例如String str1="Java",String str2=new String("Java");那么二者的关系是str1!=str2,str1.equals(str2)=true。原因是str1存在于字符串常量池中,str2存在于Java堆中,二者地址当然不同;但是二者的表面值是一样的,都是Java,因此equals方法返回true。

点赞
收藏
评论区
推荐文章
happlyfox happlyfox
3年前
笑说设计模式-小白逃课被点名
关于我简介工厂模式(FactoryPattern)是最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,而是通过使用一个共同的接口来指向新创建的对象。分类工厂模式可以分为三种,其中简单工厂一般不被认为是一种设计模式,可以将其看成是工厂方法的一种特殊
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
灯灯灯灯 灯灯灯灯
3年前
「超全超细」Java设计模式图文详解!!!
java设计模式—原型模式Java原型模式1、概述  啥是原型模式?  原型模式属于设计模式中的创建型中的一员,  原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象!  说大白话就是自己复制自己,通过原生对象复制出一个新的对象,这两个对象结构相同且相似;  需要注意的是,原型对象自己不仅是个对象还是个工厂!并且通过克隆方式创
菜园前端 菜园前端
1年前
前端学习重点-原型与原型链
原文链接:什么是原型?每一个函数都包含一个prototype属性,这是一个指针指向"原型对象"(prototypeobject),也就是我们平时说的原型。每一个函数都包含不同的原型对象。当将函数用作构造函数的时候,新创建的对象(实例)会从原型对象上继承属性
Stella981 Stella981
3年前
JavaScript面向对象编程的15种设计模式
在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。在JavaScript中并没有类这种概念,面向对象编程不是基于类,而是基于原型去面向对象编程,JS中的函数属于一等对象,而基于JS中闭包与弱类型等特性,在实现一些设计模式的方式上与众不同。ps:本文之讲述面向对象编程的设计模式策略,JavaScript原型的基础请参考阮一峰面向
Wesley13 Wesley13
3年前
Java中23种设计模式详解
Java中23种设计模式1\.设计模式31.1创建型模式41.1.1工厂方法41.1.2抽象工厂61.1.3建造者模式101.1.4单态模式131.1.5原型模式151.2结构型模式171.2.1适配器模式171.2.2桥接模式191.2.3组合
Wesley13 Wesley13
3年前
Java原型模式
原型模式  原型模式也称克隆模式。原型模式jianmingzhiyi,就是先创造出一个原型,然后通过类似于Java中的clone方法,对对象的拷贝,克隆类似于new,但是不同于new。new创造出来的对象采用的是默认值。克隆出来的对象与原型对象相同,同时不会影响原型对象,然后在修改克隆出来的对象。实现  继承Cloneable接口,重写cl
Wesley13 Wesley13
3年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Wesley13 Wesley13
3年前
23种设计模式(面向对象语言)
一、设计模式的分类总体来说设计模式分为三大类:创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。创建型模式是用来创建对象的模式,抽象了实例化的过程,帮助一个系统独立于其他关联对象的创建、组合和表示方式。所有的创建型模式都有两个主要功能:  1.将系统所使用的具体类的信息封装起来  2.隐藏
Wesley13 Wesley13
3年前
C++ 常用设计模式(学习笔记)
设计模式1、工厂模式在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式作为一种创建模式,一般在创建复杂对象时,考虑使用;在创建简单对象时,建议直接new完成一个实例对象的创建。1.1、简单工厂模式主要特点是需要在工厂类中做判断,从而创造相应的产品,当