设计模式(创建型模式)原型模式

目录

    • 一、简介
    • 二、原型模式
      • 2.1、不使用原型模式
      • 2.2、使用原型模式(浅拷贝)
      • 2.3、使用原型模式(深拷贝)
    • 三、原型模式

一、简介

   原型模式(Prototype Pattern) 是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需向客户端暴露对象创建的细节。这种模式涉及使用原型实例作为要创建对象的种类,并通过复制这些原型来创建新的对象。

  在原型模式中,客户端通过请求复制(克隆)原型实例来创建一个新的对象,而不是通过标准的对象构造方法。这使得创建新对象更加高效,因为它避免了创建对象的常规创建过程,如实例化和初始化。在实现上,原型模式通常需要目标类实现一个能够克隆自身的接口或方法(例如clone()方法),以便能够通过复制现有对象来生成新对象。

  当涉及到对象的创建时,原型模式允许我们通过复制现有对象来创建新对象。原型模式一般是通过深浅拷贝来实现的。我们假设有个动物类 Animal 和食物类 Food

Animal.java

package com.alian.prototype;

import java.io.Serializable;

public abstract class Animal implements Serializable {

    public String name;

    public String sex;

    public abstract void sleep();

    public Animal() {

    }

    public Animal(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

Food.java

package com.alian.prototype;

import java.io.Serializable;

public class Food implements Serializable {

    private String name;

    private int num;

    public Food() {

    }

    public Food(String name, int num) {
        this.name = name;
        this.num = num;
    }

    public String getName() {
        return name;
    }

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

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

  接下来我们看看没有使用原型模式,和使用原型模式的区别。

二、原型模式

2.1、不使用原型模式

  假设我们有一个狗类 Dog

Dog.java

package com.alian.prototype;

import java.io.Serializable;

public class Dog extends Animal implements Serializable {

    private int leg;

    private String breeds;

    public Dog() {

    }

    public Dog(String name, String sex, int leg, String breeds) {
        super(name, sex);
        this.leg = leg;
        this.breeds = breeds;
    }

    public int getLeg() {
        return leg;
    }

    public void setLeg(int leg) {
        this.leg = leg;
    }

    public String getBreeds() {
        return breeds;
    }

    public void setBreeds(String breeds) {
        this.breeds = breeds;
    }

    @Override
    public void sleep() {
        System.out.println("睡觉要吹空调");
    }

    public static void main(String[] args) {
        Dog chineseRuralDog = new Dog("大毛", "雄性", 4, "中华田园犬");
        System.out.println(chineseRuralDog);
        // 如果需要创建新的用户对象,需要再次使用构造函数创建
        Dog labrador = new Dog("二毛", "雄性", 4, "拉布拉多");
        System.out.println(labrador);
    }
}

  没有使用原型的时候每次都需要 new 一个对象来实现。

2.2、使用原型模式(浅拷贝)

  实现浅拷贝的一个简单方式就是实现了 Cloneable 接口,并重写了 clone() 方法。我们用一个猫类 Cat 来说明浅拷贝。

package com.alian.prototype;

import java.io.Serializable;

public class Cat extends Animal implements Cloneable, Serializable {

    private int leg;

    private String breeds;

    private Food food;

    public Cat() {

    }

    public Cat(String name, String sex, int leg, String breeds) {
        super(name, sex);
        this.leg = leg;
        this.breeds = breeds;
    }

    public int getLeg() {
        return leg;
    }

    public void setLeg(int leg) {
        this.leg = leg;
    }

    public String getBreeds() {
        return breeds;
    }

    public void setBreeds(String breeds) {
        this.breeds = breeds;
    }

    public Food getFood() {
        return food;
    }

    public void setFood(Food food) {
        this.food = food;
    }

    @Override
    public void sleep() {
        System.out.println("睡觉要吹空调");
    }

    @Override
    public Cat clone() throws CloneNotSupportedException {
        return (Cat) super.clone();
    }

    public static void main(String[] args) {
        Cat cat = new Cat("三毛", "雌性", 4, "金吉拉猫");
        cat.setFood(new Food("鱼", 2));
        System.out.println("------------------------原对象属性-------------------------");
        System.out.println("原对象名称:" + cat.getName());
        System.out.println("原对象性别:" + cat.getSex());
        System.out.println("原对象脚的数量:" + cat.getLeg());
        System.out.println("原对象品种:" + cat.getBreeds());
        System.out.println("原对象口粮:" + cat.getFood().getName());
        System.out.println("原对象数量:" + cat.getFood().getNum());
        // 如果需要创建新的用户对象,需要再次使用构造函数创建
        try {
            // 使用原型模式创建新的用户对象,通过克隆方法复制现有用户对象
            Cat newCat = cat.clone();
            newCat.setName("四毛");
            newCat.setBreeds("曼基康猫");
            Food food2 = newCat.getFood();
            food2.setName("猫粮");
            food2.setNum(3);
            System.out.println("------------------------克隆对象属性-------------------------");
            System.out.println("克隆对象名称:" + newCat.getName());
            System.out.println("克隆对象性别:" + newCat.getSex());
            System.out.println("克隆对象脚的数量:" + newCat.getLeg());
            System.out.println("克隆对象品种:" + newCat.getBreeds());
            System.out.println("克隆对象口粮:" + newCat.getFood().getName());
            System.out.println("克隆对象数量:" + newCat.getFood().getNum());
            System.out.println("------------------------原对象属性-------------------------");
            System.out.println("原对象名称:" + cat.getName());
            System.out.println("原对象性别:" + cat.getSex());
            System.out.println("原对象脚的数量:" + cat.getLeg());
            System.out.println("原对象品种:" + cat.getBreeds());
            System.out.println("原对象口粮:" + cat.getFood().getName());
            System.out.println("原对象数量:" + cat.getFood().getNum());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:鱼
原对象数量:2
------------------------克隆对象属性-------------------------
克隆对象名称:四毛
克隆对象性别:雌性
克隆对象脚的数量:4
克隆对象品种:曼基康猫
克隆对象口粮:猫粮
克隆对象数量:3
------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:猫粮
原对象数量:3

  通过运行结果我们看到调用 clone() 方法后,然后重新设置了 Food 对象的属性,然后就发现拷贝对象是变了,同时也影响到了原对象的属性 Food (原对象的口粮从 变成 猫粮 ,数量 2 份变成 3 份)。这是为什么呢?

  在Java语言中, clone() 方法执行的是浅拷贝,只会复制对象的基本数据类型(如int,String等,本例中就都没有改变),以及所引用的对象的内存地址,比如我们这里的 Food 的内存地址( 因为改变拷贝对象结果原对象也变了 ),不会递归地复制所引用的对象本身。

2.3、使用原型模式(深拷贝)

  我们还是用上面的示例,不过一定要注意的是深拷贝一般父子类都要实现序列化接口 Serializable 。我们利用字节输入输出流来实现,代码如下:

CloneObject.java

package com.alian.prototype;

import java.io.*;

public class CloneObject {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Cat cat = new Cat("三毛", "雌性", 4, "金吉拉猫");
        cat.setFood(new Food("鱼", 2));
        System.out.println("------------------------原对象属性-------------------------");
        System.out.println("原对象名称:" + cat.getName());
        System.out.println("原对象性别:" + cat.getSex());
        System.out.println("原对象脚的数量:" + cat.getLeg());
        System.out.println("原对象品种:" + cat.getBreeds());
        System.out.println("原对象口粮:" + cat.getFood().getName());
        System.out.println("原对象数量:" + cat.getFood().getNum());
        // 如果需要创建新的用户对象,需要再次使用构造函数创建
        // 使用原型模式创建新的用户对象,通过克隆方法复制现有用户对象
        Cat newCat = clone(cat);
        newCat.setName("四毛");
        newCat.setBreeds("曼基康猫");
        Food food2 = newCat.getFood();
        food2.setName("猫粮");
        food2.setNum(3);
        System.out.println("------------------------克隆对象属性-------------------------");
        System.out.println("克隆对象名称:" + newCat.getName());
        System.out.println("克隆对象性别:" + newCat.getSex());
        System.out.println("克隆对象脚的数量:" + newCat.getLeg());
        System.out.println("克隆对象品种:" + newCat.getBreeds());
        System.out.println("克隆对象口粮:" + newCat.getFood().getName());
        System.out.println("克隆对象数量:" + newCat.getFood().getNum());
        System.out.println("------------------------原对象属性-------------------------");
        System.out.println("原对象名称:" + cat.getName());
        System.out.println("原对象性别:" + cat.getSex());
        System.out.println("原对象脚的数量:" + cat.getLeg());
        System.out.println("原对象品种:" + cat.getBreeds());
        System.out.println("原对象口粮:" + cat.getFood().getName());
        System.out.println("原对象数量:" + cat.getFood().getNum());

    }

    private static <T extends Serializable> T clone(T obj) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (T) ois.readObject();
    }
}

运行结果:

------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:鱼
原对象数量:2
------------------------克隆对象属性-------------------------
克隆对象名称:四毛
克隆对象性别:雌性
克隆对象脚的数量:4
克隆对象品种:曼基康猫
克隆对象口粮:猫粮
克隆对象数量:3
------------------------原对象属性-------------------------
原对象名称:三毛
原对象性别:雌性
原对象脚的数量:4
原对象品种:金吉拉猫
原对象口粮:鱼
原对象数量:2

  从结果看到深拷贝对象的属性完美的复制的,并且也没有影响原对象的属性,但是深拷贝明显要比浅拷耗时多,耗内存多。

三、原型模式

  原型模式的优点和缺点如下:

优点:

  • 对象复制: 允许在运行时复制现有对象,无需知道其具体实现。它可以在不影响客户端的情况下生成新对象。
  • 减少资源消耗: 避免了对象的重新创建,提高了性能,特别是当创建对象的成本很高时。
  • 简化对象创建: 通过克隆现有实例来创建新对象,避免了复杂的初始化步骤。适用于对象的初始化操作比较复杂的情况。

缺点:

  • 深拷贝实现困难: 当对象中含有引用类型属性时,需要进行深拷贝才能保证拷贝对象与原始对象的属性完全独立。实现深拷贝可能比较困难。
  • 破坏封装性: 原型模式需要对象实现克隆接口,这可能暴露对象的细节,破坏封装性。
  • 需要理解对象的结构: 使用原型模式需要了解对象的内部结构,因为克隆会直接拷贝对象的字段。
  • 可能产生多个相似对象: 如果拷贝出的对象过多,导致内存消耗增加。需要谨慎使用,避免滥用导致系统资源浪费。

  原型模式适用于需要创建大量相似对象且创建过程复杂的场景,通过复制现有对象来提高效率。但在使用时需要注意深拷贝、克隆方式选择等问题,以避免潜在的性能问题和对象状态的不一致。

你可能感兴趣的:(设计模式,设计模式,原型模式)