阅读 117

创建型设计模式之原型模式

模式介绍

原型模式(Prototype Pattern)简单来说就是程序界的复制粘贴,它可以实现对象的深拷贝。

我们创建一个对象,通常有3种方式:

  • new 关键字
  • 反射
  • 实现Cloneable接口的clone()方法

如果需要对已有的对象进行复制,且创建对象成本较高(例如需要较多的I/O),可以考虑使用原型模式。

在原型模式中,所发动创建的对象通过请求原型对象来拷贝原型对象自己来实现创建过程,当然所发动创建的对象需要知道原型对象的类型。这里也就是说所发动创建的对象只需要知道原型对象的类型就可以获得更多的原型实例对象,至于这些原型对象时如何创建的根本不需要关心。

模式优缺点

优点

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

2、可以使用深克隆保持对象的状态。

3、原型模式提供了简化的创建结构。

4、原型模式向客户隐藏了创建对象的复杂性。客户只需要知道要创建对象的类型,然后通过请求就可以获得和该对象一模一样的新对象,无须知道具体的创建过程。

缺点

1、在实现深克隆的时候可能需要比较复杂的代码。 我们虽然可以利用原型模式来获得一个新对象,但有时对象的复制可能会相当的复杂,比如深克隆。

2、需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。

实现

角色

Prototype:抽象原型类。声明克隆自身的接口。 ConcretePrototype:具体原型类。实现克隆的具体操作。 Client:客户类。让一个原型克隆自身,从而获得一个新的对象。

接口

  我们都知道Object是祖宗,所有的Java类都继承至Object,而Object类提供了一个clone()方法,该方法可以将一个java对象复制一份,因此在java中可以直接使用clone()方法来复制一个对象。但是需要实现clone的Java类必须要实现一个接口:Cloneable.该接口表示该类能够复制且具体复制的能力,如果不实现该接口而直接调用clone()方法会抛出CloneNotSupportedException异常。
复制代码

步骤

第一步:定义抽象原型。

package net.ijiangtao.tech.designpattern.prototype;

import lombok.*;

import java.util.HashMap;
import java.util.Map;

/**
 * 定义一个抽象原型
 *
 * @author ijiangtao
 * @create 2019-07-17 21:20
 **/
@Getter
@Setter
@NoArgsConstructor  //lombok : 无参构造
@AllArgsConstructor //lombok : 全参构造
@ToString
public class Prototype implements Cloneable{

    private Integer id;

    private String name;

    private Map<String, Double> scores;

    @Override
    protected Prototype clone() {
        Prototype filePrototype = null;
        try {
            //有了下面这句话,基本类型就能克隆了
            filePrototype = (FileConcretePrototype) super.clone();
            
            //下面要对每一个复杂对象进行分别克隆
            filePrototype.scores = (Map<String, Double>) ((HashMap)this.scores).clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return filePrototype;
    }

}
复制代码

第二步:定义具体原型。

package net.ijiangtao.tech.designpattern.prototype;

import java.util.Map;

/**
 * 定义具体原型
 *
 * @author ijiangtao
 * @create 2019-07-17 21:21
 **/
public class FileConcretePrototype extends Prototype {

    public FileConcretePrototype(Integer id, String name, Map<String, Double> scores) {
        super(id, name, scores);
    }

    public void show() {
        System.out.println(" File Data : ");
        System.out.println("file name : " + this.getName());
        System.out.println("file id : " + this.getId());
        System.out.println("file scores : " + this.getScores());
    }

}
复制代码

第三步:定义用户去模拟对象克隆。

package net.ijiangtao.tech.designpattern.prototype;

import java.util.HashMap;
import java.util.Map;

/**
 * client
 *
 * @author ijiangtao
 * @create 2019-07-17 21:27
 **/
public class Client {


    public static void main(String[] args) {

        String fileName = "scores文件";
        int fileId = 1;
        Map<String, Double> fileScores = new HashMap<>();
        fileScores.put("张三", 99.99);
        fileScores.put("李斯", 79.99);

        //第一步创建出一个实例对象
        FileConcretePrototype fileA = new FileConcretePrototype(fileId, fileName, fileScores);

        //第二步克隆出来几个
        FileConcretePrototype fileB = (FileConcretePrototype) fileA.clone();
        FileConcretePrototype fileC = (FileConcretePrototype) fileA.clone();

        //第三步:输出一下克隆出来的对象是否有变化
        fileB.show();
        fileC.show();

    }
}

复制代码

最后输出是:

 File Data : 
file name : scores文件
file id : 1
file scores : {李斯=79.99, 张三=99.99}
 File Data : 
file name : scores文件
file id : 1
file scores : {李斯=79.99, 张三=99.99}
复制代码

我们可以看到,克隆出来的两个文件和之前的文件是一样的,而且我们实现了深拷贝,对于数组、引用等对象同样的适用。

特点

Java中任何实现了Cloneable接口的类都可以通过调用clone()方法来复制一份自身然后传给调用者。一般而言,clone()方法满足:

(1) 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象。

//第一步创建出一个实例对象
FileConcretePrototype fileA = new FileConcretePrototype(fileId, fileName, fileScores);

//第二步克隆出来几个
FileConcretePrototype fileB = (FileConcretePrototype) fileA.clone();
FileConcretePrototype fileC = (FileConcretePrototype) fileA.clone();

//第三步:引用赋值
FileConcretePrototype fileA2 =fileA;

System.out.println(fileA==fileB);//输出false
System.out.println(fileA==fileA2);//输出true
复制代码

(2) 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。

//第一步创建出一个实例对象
FileConcretePrototype fileA = new FileConcretePrototype(fileId, fileName, fileScores);

//第二步克隆出来几个
FileConcretePrototype fileB = (FileConcretePrototype) fileA.clone();
FileConcretePrototype fileC = (FileConcretePrototype) fileA.clone();

//第三步:引用赋值
FileConcretePrototype fileA2 =fileA;

System.out.println(fileA.getClass()==fileC.getClass()); //输出true
复制代码

(3) 如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。

首先重写 equals 和 hashCode 方法:

@EqualsAndHashCode // lombok : over write equals and hashCode methods based on relevant fields.
public class FileConcretePrototype extends Prototype
复制代码

然后与克隆以后的对象比较:

//第一步创建出一个实例对象
FileConcretePrototype fileA = new FileConcretePrototype(fileId, fileName, fileScores);

//第二步克隆出来几个
FileConcretePrototype fileB = (FileConcretePrototype) fileA.clone();
FileConcretePrototype fileC = (FileConcretePrototype) fileA.clone();

//第三步:引用赋值
FileConcretePrototype fileA2 =fileA;

System.out.println(fileA.equals(fileB));//输出true
System.out.println(fileC.equals(fileB));//输出true
复制代码

相关链接

深入理解原型模式 ——通过复制生成实例

设计模式之原型模式

设计模式读书笔记-原型模式

java的浅拷贝和深拷贝

spring中的scope详解

spring的scope为prototype的bean的正确使用方法

BeanUtils.copyProperties()方法 && 关于BeanUtil.copyProperties性能


Wechat-westcall

关注下面的标签,发现更多相似文章
评论