JAVA架构师之路四:设计模式之原型模式

本文探讨了原型模式在JAVA中的应用,包括原型模式的定义、适用场景、优缺点。阐述了浅克隆和深克隆的概念,通过示例解释了两者的区别,并分析了深克隆的实现方式,如序列化和转JSON。同时,文章引发思考,一个类是否可以同时作为单例和原型模式的实现。

JAVA架构师之路三:设计模式之单例模式

苟日新,日日新,又日新。——《礼记》

我们看的电影,电视剧等,很多都有历史或者现世的原型。原型是什么?原型就是模板,原型就是祖先,原型就是人或者事最开始的样子。结合自然的规律,代码世界也产生了一种设计模式——原型模式

1. 原型模式

定义

指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
调用者不需要知道任何创建细节,不需要调用构造函数。
属于创建型模式

适用场景

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

优点

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

缺点

必须适配克隆(或者可拷贝)方法
当对已有类进行改造的时候,需要修改代码,违背了开闭原则
深拷贝,浅拷贝需要运用得当

思考一个问题:我们如何复制一个对象呢?

方法1:set、get方式

public class Book {

    private String id;

    private String name;

    private Double price;

    private String address;

    public Book copy(Book book){
        Book copy = new Book();
        copy.setId(book.getId());
        copy.setName(book.getName());
        copy.setPrice(book.getPrice());
        copy.setAddress(book.getAddress());
        return copy;
    }

    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 Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

方法2:利用反射的方式

public class BeanUtils {
    public static Object copy(Object prototype) {
        Class clazz = prototype.getClass();
        Object returnValue = null;
        try {
            returnValue = clazz.newInstance();
            for (Field field : clazz.getDeclaredFields()) {
                field.setAccessible(true);
                field.set(returnValue, field.get(prototype));
            }
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return returnValue;
    }
}

这两种方法本质上没有区别,都是set、get附值,方法2看起来比方法1优雅一点。方法2实际生产中经常用到。

方法3:原型模式

public interface IPrototype<T> {
    T clone();
}
public class User implements IPrototype {

    private int age;

    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public User clone() {
        User user = new User();
        user.setAge(this.age);
        user.setName(this.name);
        return user;
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
public class Client {

    public static void main(String[] args) {
        User user = new User();
        user.setAge(1);
        user.setName("Jack");
        System.out.println(user);
        User user1 = user.clone();
        System.out.println(user1);
    }
}
User{age=1, name='Jack'}
User{age=1, name='Jack'}

这里有一个问题:为什么不直接new就行了,为什么要clone?
假如一个类有上百个属性,要新创建10个对象,new 和 clone的区别就体现出来了,把new的过程封装成方法,就省去了大量 set、get的过程!

原型模式一般有两种写法:一种是浅克隆,一种是深克隆。

2. 浅克隆

我们知道,所有的类都默认继承Object,Object类有一个clone方法。是不是要克隆的对象都要实现类似于IPrototype的接口呢?其实JDK帮我们做了这个事请。如果一个类要克隆,那么它要实现Cloneable接口。

* @author  unascribed
 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}
protected native Object clone() throws CloneNotSupportedException;

代码

public class User implements Cloneable {

    private int age;

    private String name;

    private List<String> brother;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public List<String> getBrother() {
        return brother;
    }

    public void setBrother(List<String> brother) {
        this.brother = brother;
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", brother=" + brother +
                '}';
    }
}
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {
        User user = new User();
        user.setAge(1);
        user.setName("Jack");
        List<String> brother = new ArrayList<>();
        brother.add("小明");
        brother.add("小强");
        user.setBrother(brother);
        System.out.println("原型:" + user);
        User user1 = user.clone();
        System.out.println("克隆:" + user1);
        System.out.println(user == user1);
        user1.setAge(2000);
        user1.setName("James");
        System.out.println("原型:" + user);
        System.out.println("克隆:" + user1);
        user1.getBrother().add("小武");
        System.out.println("原型:" + user);
        System.out.println("克隆:" + user1);
        System.out.println(user.getBrother() == user1.getBrother());
    }
}
原型:User{age=1, name='Jack', brother=[小明, 小强]}
克隆:User{age=1, name='Jack', brother=[小明, 小强]}
false
原型:User{age=1, name='Jack', brother=[小明, 小强]}
克隆:User{age=2000, name='James', brother=[小明, 小强]}
原型:User{age=1, name='Jack', brother=[小明, 小强, 小武]}
克隆:User{age=2000, name='James', brother=[小明, 小强, 小武]}
true

出现问题了:当我们对克隆对象user1的int型的age,string型的name附值的时候,结果是我们想要的,只修改了user1,可是当我们对list类型的brother附值的时候,原型对象user也变化了。这就会出大问题了。因为list是引用对象,我们猜测,user,user1指向的是同一块内存地址,最后输出的true也证明了我们的猜想。这就是浅克隆。

JDK 中也有一些类本身实现了浅克隆,我们来看一下:
ArrayList

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList 首先实现了Cloneable,Serializable接口。

public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

HashMap:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap首先实现了Cloneable,Serializable接口。

    @Override
    public Object clone() {
        HashMap<K,V> result;
        try {
            result = (HashMap<K,V>)super.clone();
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
        result.reinitialize();
        result.putMapEntries(this, false);
        return result;
    }

3. 深克隆

由于浅克隆存在引用对象指向同一块内存地址造成修改克隆对象会同时修改原型对象的问题,那么深克隆就会解决这个问题。我们来看深克隆代码:

public class User implements Cloneable, Serializable {

    private int age;

    private String name;

    private List<String> brother;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public List<String> getBrother() {
        return brother;
    }

    public void setBrother(List<String> brother) {
        this.brother = brother;
    }

    @Override
    protected User clone() throws CloneNotSupportedException {
        return (User) super.clone();
    }

    public User deapClone() 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 (User)ois.readObject();
    }

    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", brother=" + brother +
                '}';
    }
}

同样的User对象,加入了一个deapClone() 方法,这个方法就是深克隆,很明显是序列化的过程。上一章JAVA架构师之路三:设计模式之单例模式. 文章最后我有详细讲到。由于User没有写readResolve() 方法,所以它不会返回本身那个实例,obj = desc.isInstantiable() ? desc.newInstance() : null; 会返回一个新的实例。

public class Client {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User();
        user.setAge(1);
        user.setName("Jack");
        List<String> brother = new ArrayList<>();
        brother.add("小明");
        brother.add("小强");
        user.setBrother(brother);
        System.out.println("原型:" + user);
        User user1 = user.deapClone();
        System.out.println("克隆:" + user1);
        System.out.println(user == user1);
        user1.setAge(2000);
        user1.setName("James");
        System.out.println("原型:" + user);
        System.out.println("克隆:" + user1);
        user1.getBrother().add("小武");
        System.out.println("原型:" + user);
        System.out.println("克隆:" + user1);
        System.out.println(user.getBrother() == user1.getBrother());
    }
}
原型:User{age=1, name='Jack', brother=[小明, 小强]}
克隆:User{age=1, name='Jack', brother=[小明, 小强]}
false
原型:User{age=1, name='Jack', brother=[小明, 小强]}
克隆:User{age=2000, name='James', brother=[小明, 小强]}
原型:User{age=1, name='Jack', brother=[小明, 小强]}
克隆:User{age=2000, name='James', brother=[小明, 小强, 小武]}
false

缺点

性能不好
占用IO
写法不太常见

深克隆还有另外一种方式,这种方式比较常用,就是把原型对象转化成JSON字符串,然后再把字符串转化成对象。

4. 总结

  1. 实现Cloneable的都是浅克隆
  2. 如何实现深克隆?
    a. 序列化
    b. 转JSON

思考一个问题:一个类可以即是单例,又是原型吗?

感谢您阅读本文,如果您觉得文章写的对您有用的话,请您点击上面的“关注”,点个赞,这样您就可以持续收到《JAVA架构师之路》的最新文章了。文章内容属于自己的一点点心得,难免有不对的地方,欢迎在下方评论区探讨,你们的关注是我创作优质文章的动力。

JAVA架构师之路五:设计模式之建造者模式

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值