Java设计模式之原型模式详解

一、前言

原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。

该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。在Java中,复制对象是通过clone()实现的,先创建一个原型类,通过实现Cloneable 接口

public class Prototype implements Cloneable {  

		public Object clone() throws CloneNotSupportedException {
			Prototype proto = (Prototype) super.clone();
			return proto;
		}
	}

只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,说明这个方法实现并不是使用java语言,是底层C实现阿达

至于cloneA或者cloneB名字可以任意取,是因为要你主动去调用的,所以你名字取成什么,你调用的时候就调用该名字就可以了

二、优点及适用场景

使用原型模式创建对象比直接new一个对象在性能上要好的多,因为上面我也提到过,Object类的clone()是native的,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。

使用原型模式的另一个好处是简化对象的创建,使得创建对象就像我们在编辑文档时的复制粘贴一样简单。

因为以上优点,所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多。

三、原型模式的注意事项

使用原型模式复制对象不会调用类的构造方法。因为对象的复制是通过调用Object类的clone()来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。

说到这里,就得顺便提一下单例模式,在单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意。

四、浅复制和深复制

另外还得知道两个特别重要的概念 : 浅复制   深复制

浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而数组、容器对象、引用对象等都不会拷贝,指向的还是原对象所指向的地址。浅拷贝实现 Cloneable,重写clone方法

深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。深拷贝是通过实现 Serializable 读取二进制流

五、浅复制demo演示

首先我们创建一个抽象原型类 Animal.class,实现了Cloneable接口,并且重写了clone方法

package cn.zygxsq.design.module.prototypePattern;

/**
 * Created by yjl on 2021/4/30.
 * 动物类
 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878
 */
public abstract class Animal implements Cloneable{
    private String id;
    public String name;

    abstract void shout();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    /**
     * 浅复制
     */
    public Object clone() throws CloneNotSupportedException {
        Animal clone = (Animal) super.clone();
        return clone;
    }
}

再创建两个实现类

Dog.class 和Cat.class

package cn.zygxsq.design.module.prototypePattern;

/**
 * Created by yjl on 2021/4/30.
 */
public class Dog extends Animal {
    public Dog(){
        name = "狗狗";
    }

    @Override
    public void shout() {
        System.out.println("我的叫声是:汪汪汪");
    }
}
package cn.zygxsq.design.module.prototypePattern;

/**
 * Created by yjl on 2021/4/30.
 */
public class Cat extends Animal {
    public Cat(){
        name = "猫猫";
    }

    @Override
    public void shout() {
        System.out.println("我的叫声是:喵喵喵");
    }
}

然后创建一个数据缓存类,用户存储从数据库中获取到的大对象数据或者曾经使用过的大对象数据

以后下一次想要再次对这个对象数据操作的时候,直接从缓存里获取并且clone一个

package cn.zygxsq.design.module.prototypePattern;

import com.google.common.collect.Maps;
import org.springframework.beans.factory.InitializingBean;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentMap;

/**
 * Created by yjl on 2021/4/30.
 * 缓存类 用于加载一些数据库的缓存的数据
 */
public class DataCache /*implements InitializingBean*/{
    //正常的情况是 实现 InitializingBean ,用于web服务启动的时候加载数据
    // 这里测试由于不是web服务,所以就模拟加载数据

    private static ConcurrentMap<String, Animal> animalCache = Maps.newConcurrentMap();

    public static Animal getAnimal(String id) throws Exception{
        Animal cache = animalCache.get(id);
        return (Animal) cache.clone();
    }

    public static void init(){
        Dog dog = new Dog();
        String dogid = "111";
        dog.setId(dogid);
        animalCache.put(dogid,dog);

        Dog dog2 = new Dog();
        String dogid2 = "222";
        dog2.setId(dogid2);
        animalCache.put(dogid2,dog2);

        Cat cat = new Cat();
        String catid = "333";
        cat.setId(catid);
        animalCache.put(catid,cat);

    }
}

最后咱们开始测试

先是从数据库里加载缓存,然后要从缓存里获取数据,并且的到的是一个个clone出来的对象

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
 * Created by yjl on 2021/4/30.
 * 测试主类 浅复制
 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878
 */
public class TestPrototype {
    public static void main(String[] args) {
        DataCache.init(); // 模拟加载数据到缓存中

        try {
            Animal animal = DataCache.getAnimal("111");
            System.out.println(animal.getName()+"---"+JSON.toJSONString(animal));

            Animal animal222 = DataCache.getAnimal("222");
            System.out.println(animal222.getName()+"---"+JSON.toJSONString(animal222));

            Animal animal333 = DataCache.getAnimal("333");
            System.out.println(animal333.getName()+"---"+JSON.toJSONString(animal333));

            animal.shout();
            animal222.shout();
            animal333.shout();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

运行结果:

小伙伴们看的时候,好像没什么问题,确实没什么问题,但是细细一看,还是有一定的问题的

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
 * Created by yjl on 2021/4/30.
 * 浅复制遇到的问题
 */
public class TestCloneProblem {

    public static void main(String[] args) {
        //做完TestPrototype的main方法后,好像觉得浅复制没有什么问题
        //那么可以看一下下面的a1的name 和 克隆后的name指向的是同一个地址
        DataCache.init(); // 模拟加载数据到缓存中

        try {
            Animal a1 = DataCache.getAnimal("111");
            Animal a2 = (Animal)a1.clone();
            System.out.println(a1==a2);
            System.out.println(a1.name == a2.name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行结果:

大家踩一下 a1的name 和 克隆后的name是什么样的关系呢

大家可以看到,a1的name和a2的name是一样的,由于他们的类型是String,所以他们指向的是同一个地址,名称为“狗狗”的引用地址,大概的样子可以看下图

那怎么样才能不让a1.name 和a2.name不相同呢,也就是完完全全的复制,这个就得用到深复制了

深复制其实用到的就是流复制

可以在clone()的方法定义一个深复制的方法,比如deepClone()

六、深复制demo演示

记住,深复制的时候,方法一定得实现可序列化,Serializable

package cn.zygxsq.design.module.prototypePattern;

import java.io.*;

/**
 * Created by yjl on 2021/4/30.
 * 动物类
 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878
 */
public abstract class Animal implements Cloneable, Serializable{
    private String id;
    public String name;

    abstract void shout();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    /**
     * 浅复制
     */
    public Object clone() throws CloneNotSupportedException {
        Animal clone = (Animal) super.clone();
        return clone;
    }

    /**
     * 深复制
     */
    public Object deepClone() {
        ByteArrayOutputStream byteArrayOutputStream = null;
        ObjectOutputStream objectOutputStream = null;
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            // 序列化
            byteArrayOutputStream = new ByteArrayOutputStream();
            objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(this);/*将当前对象以对象流的方式输出*/
            //反序列化
            byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            objectInputStream = new ObjectInputStream(byteArrayInputStream);
            Animal deepProtoType = (Animal) objectInputStream.readObject();
            return deepProtoType;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                byteArrayOutputStream.close();
                objectOutputStream.close();
                byteArrayInputStream.close();
                objectInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }
}

测试一下结果

package cn.zygxsq.design.module.prototypePattern;

import com.alibaba.fastjson.JSON;

/**
 * Created by yjl on 2021/4/30.
 * 测试主类 深复制
 * 原型模式:博文介绍链接:https://blog.csdn.net/qq_27471405/article/details/116309878
 */
public class TestPrototypeDeepClone {
    public static void main(String[] args) {
        DataCache.init(); // 模拟加载数据到缓存中

        try {
            Animal a1 = DataCache.getAnimal("111");
            Animal a2 = (Animal)a1.deepClone();
            System.out.println(a1==a2);
            System.out.println(a1.name == a2.name);
            System.out.println(a1.name);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

运行结果:

这就是深复制和浅复制以及原型模式的使用

到此这篇关于Java设计模式之原型模式详解的文章就介绍到这了,更多相关Java原型模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解JAVA 原型模式

    原型模式 原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆.当直接创建对象的代价比较大时,则采用这种模式.例如,一个对象需要在一个高代价的数据库操作之后被创建.我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用. 介绍 意图: 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.

  • 23种设计模式(3) java原型模式

    23种设计模式第三篇:java原型模式 定义: 通过复制现有的对象实例来创建新的对象实例. 实现: 实现Cloneable接口: Cloneable接口的作用是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法.在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常. 重写Object类中的clone方法: Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的

  • Java设计模式之命令模式详解

    命令模式 定义:将请求封装成对象,这可以让你使用不同的请求.队列.或者日志来参数化其他对象. 何时使用命令模式?当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式. 在被解耦的两者之间是通过命令对象进行沟通的.命令对象封装了接收者和一个或一组动作. 调用者通过调用命令对象的execute()方法发出请求,这会使接收者的动作被调用. 调用者可以接收命令当作参数,甚至在运行时动态地进行. 优点: 1.降低了系统耦合度. 2.新的命令可以很容易添加到系统中去. 缺点:使用命令模式可能会导致

  • Java设计模式之抽象工厂模式详解

    一.什么是抽象工厂模式 为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类,这称之为抽象工厂模式(Abstract Factory).我们并不关心零件的具体实现,而是只关心接口(API).我们仅使用该接口(API)将零件组装称为产品. 二.示例程序   1.抽象的零件:Item类 package com.as.module.abstractfactory; /** * 抽象的零件 * @author Andy * @date 2021/4/29 23:16 */ public

  • 讲解Java设计模式编程中的建造者模式与原型模式

    建造者模式 定义 又叫生成器模式,它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象. 当创建复杂对象的算法应该独立于该对象的组成部分时,而且构造过程必须允许被构造的对象有不同的表示时.我们可以考虑使用建造者模式. 实现 1. Builder为创建一个Product对象的各个部件指定抽象接口.通常包含创建产品和返回产品的抽象方法,也可以是具体方法,把创建过程放到ConcreteBuilder类中. 2. ConcreteBuilder 实

  • Java设计模式之Prototype原型模式

    一.场景描述 创建型模式中,从工厂方法模式,抽象工厂模式,到建造者模式,再到原型模式,我的理解是,创建对象的方式逐步从编码实现转向内存对象处理. 例如,在"仪器数据采集器"的子类/对象"PDF文件数据采集器"和"Excel文件数据采集器"的创建过程中, 工厂模式下定义各子类,并由(抽象)工厂类Factory创建,因此各子类可在类定义中定义各自的属性: 建造者模式下,通过不同的创建者类Builder创建不同的子对象,此时不再定义子类: 而原型模式下

  • Java设计模式之原型模式(Prototype模式)介绍

    Prototype模式定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建. 如何使用原型模式 因为Java中的提供clone()方法来实现对象的克隆,所以Prototype模式实现一下子变得很简单.以勺子为例: 复制代码 代码如下: public abstract cl

  • 详解Java的设计模式编程中的原型模式

    定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 类型:创建类模式 类图: 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype.Prototype类需要具备以下两个条件: 实现Cloneable接口.在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法.在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedExcepti

  • 设计模式之原型模式_动力节点Java学院整理

    定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. 类型:创建类模式 类图: 原型模式主要用于对象的复制,它的核心是就是类图中的原型类Prototype.Prototype类需要具备以下两个条件: 实现Cloneable接口.在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法.在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedExcepti

  • Java设计模式之装饰模式详解

    一.装饰模式引入例子 一个快餐店计算价格问题举例: 快餐店有炒面.炒饭这些快餐,可以额外附加鸡蛋.火腿.培根这些配菜,加配菜需要额外加钱,并且每个配菜的价钱不一样,计算快餐价格如何实现? 1.1 一般设计 1.2 使用继承方式的一般设计存在的问题 横向扩展性不好:如果要再加一种配料(火腿肠),我们就会发现需要给FriedRice和FriedNoodles分别定义一个子类.如果要新增一个快餐品类(炒河粉)的话,就需要定义更多的子,会出现类爆炸的问题. 继承适合于纵向扩展 二.装饰模式 2.1 装饰

  • Java设计模式之观察者模式(Observer模式)

    一.观察者模式是什么? 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern).当一个对象被修改时,则会自动通知依赖它的对象.观察者模式属于行为型模式. 人话: 就像微信公众号推送消息,订阅的人能收到,没订阅的收不到,还可以取消/添加订阅 二.模式分析 2.1 四个角色 抽象主题(抽象被观察者角色):也就是一个抽象主题,它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有任意数量的观察者.抽象主题提供一个接口,可以增加和删除观察者角色.一般用一个抽象类和接口来实现

随机推荐