【原型模式】结合框架学习设计模式

135 阅读4分钟

原型模式 Prototype Pattern

  • 属于创建型模式
  • 是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,不调用构造函数
  • 原型模式的核心在于实现一个clone方法,这个方法可以复制对象的状态,从而创建一个新的对象。新对象与原型对象具有相同的状态,但它们是两个独立的对象

原型模式通常包含以下几个角色:

  1. Prototype(抽象原型类) :声明一个克隆自身的接口。通常是一个抽象类或接口,定义了clone方法。
  2. ConcretePrototype(具体原型类) :实现clone方法,用于复制自身。具体的原型类,实现抽象原型类中定义的克隆方法。
  3. Client(客户端) :使用原型对象创建新的对象。客户端代码,通过调用原型对象的克隆方法来创建新的对象。

业务

类图

classDiagram
    class Shape {
        <<abstract>>
        -x: int
        -y: int
        +clone(): Shape
        +setPosition(int, int): void
        +draw(): void
    }
    class Circle {
        -radius: int
        +Circle(int, int, int)
        +clone(): Shape
        +draw(): void
    }
    class Rectangle {
        -width: int
        -height: int
        +Rectangle(int, int, int, int)
        +clone(): Shape
        +draw(): void
    }
    Shape <|-- Circle
    Shape <|-- Rectangle

代码

假设我们正在开发一个图形绘制软件,其中有各种图形对象,如圆形、矩形、三角形等。当用户需要创建多个相似的图形时,可以使用原型模式。

  1. 首先定义一个抽象图形类(抽象原型类):
abstract class Shape {
    protected int x;
    protected int y;

    public abstract Shape clone();

    public void setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public abstract void draw();
}
  1. 具体的圆形类(具体原型类):
class Circle extends Shape {
    private int radius;

    public Circle(int x, int y, int radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    @Override
    public Shape clone() {
        return new Circle(this.x, this.y, this.radius);
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle at (" + x + ", " + y + ") with radius " + radius);
    }
}
  1. 具体的矩形类(具体原型类):
class Rectangle extends Shape {
    private int width;
    private int height;

    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    @Override
    public Shape clone() {
        return new Rectangle(this.x, this.y, this.width, this.height);
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle at (" + x + ", " + y + ") with width " + width + " and height " + height);
    }
}
  1. 客户端使用:
public class Main {
    public static void main(String[] args) {
        Circle originalCircle = new Circle(10, 10, 5);
        Circle clonedCircle = (Circle) originalCircle.clone();
        clonedCircle.setPosition(20, 20);
        originalCircle.draw();
        clonedCircle.draw();

        Rectangle originalRectangle = new Rectangle(30, 30, 8, 6);
        Rectangle clonedRectangle = (Rectangle) originalRectangle.clone();
        clonedRectangle.setPosition(40, 40);
        originalRectangle.draw();
        clonedRectangle.draw();
    }
}

在这个场景中,原型模式使得创建相似图形对象变得非常方便。当用户需要创建新的图形时,只需要复制现有的图形对象,然后进行一些修改即可,而不需要重新创建一个全新的对象。

在框架中的使用

java: 实现克隆方法

通过硬编码复制对象

编写比较麻烦

@Data
public class User {
    
    private Long id;
    private String name;
    private String pwd;
    
    /**
     * 硬编码复制
     */
    public User copy() {
        User user = new User();
        user.setId(this.getId());
        user.setName(this.getName());
        return user;
    }
}
通过反射复制对象

本质也是 set、get

public static Object copy(Object prototype) {
    Class<?> clazz = prototype.getClass();
    Object returnValue;
    try {
        returnValue = clazz.newInstance();
        // 获取所有属性, 并设置可访问
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            field.set(returnValue, field.get(prototype));
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return returnValue;
}
浅拷贝

浅拷贝的特点

  • 对于基本类型是按值复制,不会因为原对象的基本类型值的改变而影响到拷贝后的对象
  • 对于引用类型,prototype 和 clone 对象引用同一个引用类型对象(复制引用对象的内存地址)

浅拷贝存在的问题

  • 修改 clone[prototype] 引用的对象后,会影响到 prototype[clone]
@Data
public class ConcretePrototype implements Cloneable {
    
    private String name;
    private int age;
    private List<String> hobbies;
    
    /**
     * 实现 Cloneable 接口, 底层使用字节码进行克隆
     */
    @Override
    public ConcretePrototype clone() {
        try {
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}
流实现深拷贝
@Data
public class DeepConcretePrototype implements Serializable
, Cloneable {
    
    private String name;
    private int age;
    private List<String> hobbies;
    
    @Override
    public DeepConcretePrototype clone(){
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (DeepConcretePrototype)ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
json字符串实现深拷贝

假设我们有一个简单的类Person

@Data
class Person {
    private String name;
    private int age;

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

使用 JSON 进行深拷贝的方法如下:

import com.alibaba.fastjson.JSON;

public class DeepCopyUsingJSON {
    public static void main(String[] args) {
        Person person1 = new Person("Alice", 30);
        String jsonStr = JSON.toJSONString(person1);
        Person person2 = JSON.parseObject(jsonStr, Person.class);
        person2.setName("Bob");
        person2.setAge(25);

        System.out.println("Person 1: " + person1.getName() + ", " + person1.getAge());
        System.out.println("Person 2: " + person2.getName() + ", " + person2.getAge());
    }
}

在这个例子中,首先将对象转换为 JSON 字符串,然后再从 JSON 字符串解析回对象,这样就实现了一个深拷贝的效果。新的对象和原对象是完全独立的,修改其中一个不会影响另一个。

spring

bean的单例、原型

总结

防止单例被原型模式破坏

1、单例类不实现 cloneable 接口 2、实现 cloneable,但是 clone 方法直接返回单例对象

使用场景

  • 类初始化消耗资源较多
  • new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  • 构造函数比较复杂
  • 循环体中生产大量对象

优点

  • 性能优良,Java 自带的原型模式是基于二进制流的拷贝,比直接 new 一个对象性能上提升了许多
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建过程

缺点

  • 必须匹配克隆方法
  • 当对已有类进行改造的时候,需要修改代码,违反了开闭原则
  • 深拷贝、浅拷贝需要运用得当