【设计模式】原型模式

229 阅读5分钟

又是一个相对简单的模式,原型模式属于对象创建型模式。他主要作用就是通过一个原型对象克隆出多个一模一样的对象。

在我个人看来,设计模式作用方面可以分为两类:一类是用来优化代码结构的,比如系统解耦,让系统可扩展等等。另一类的模式对系统结构并无帮助,他不是结构型的,而是功能型的,就像单例,原型这些。

模式背景

在某些情况下,我们需要完整的复制某一个对象。比如游戏中的小兵,我的世界里面的土块,他们都是以一个模板为原型的相同的对象。如何方便高效的完成对象的复制,就是原型模式要解决的。

UML

原理

让原型类自己提供一个clone方法,使用者直接调用原型类对象的该clone方法,就可以克隆出一个与该原型对象一样的克隆对象。

注意克隆后的对象是全新的对象,是新创建的内存空间,不是引用这种。他和原来的原型对象相互独立,修改各自的属性不会相互影响。

基本要素

  • 一个代表原型模式的接口,申明clone方法。
  • 一个实现了接口的原型类。
  • 提供clone方法。

实现

根据不同的语言的特性,会有一些不同的实现方式。这里只说Java的实现和一种通用的实现。

通用实现

通用的实现就是自己创建一个新对象,然后将原型对象的属性重新赋值给这个新对象。这种方式是所有面向对象语言都通用的。

定义一个Prototype接口,表明原型模式:

public interface Prototype {
    Prototype clone();
}

原型类通用实现

public class BasePrototypeDemo implements Prototype {
    private String attr;
    public String getAttr() {
        return attr;
    }
    public void setAttr(String attr) {
        this.attr = attr;
    }
    //内部克隆一个新的对象
    @Override
    public Prototype clone() {
        BasePrototypeDemo instance = new BasePrototypeDemo();
        instance.setAttr(this.attr);
        return instance;
    }
}

优点

  • 适用于所有的语言,都可以实现。

缺点

  • 代码较为复杂。通过new再赋值的方式,如果属性很多势必要大量代码,效率也不会太高。

使用Java的 Cloneable

Java的Object里面有个clone方法,可以实现对象的赋值(想当于Java语言自身特性),但是使用clone的类需要实现Cloneable接口。否则会抛CloneNotSupportedException异常。

Java的实现方式如下:

public class JavaPrototypeDemo implements Cloneable {
    @Override
    protected JavaPrototypeDemo clone() {
        try {
            return (JavaPrototypeDemo) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

优点

  • Java实现起来很方便

缺点

  • 只支持Java
  • 只是浅复制(对象内组合的对象还是原来的对象,复制只是复制了引用,只会复制基本数据类型,引用数据类型只复制地址)

使用场景

原型模式主要可以简化我们创建对象的过程。它适合于来创建对象成本较大的情况。当我们需要一个比较复杂的对象的一个副本做操作的时候,我们可以试着考虑一下原型模式。

深复制&浅复制

原型模式携带着一个问题:就是上面提到的浅复制问题。

  • 浅复制:原型对象的属性也是一个对象,复制的时候如果复制的是该属性对象引用,那么这就是浅复制。
  • 深复制:原型对象的属性也是一个对象,复制的时候将该属性对象也复制一份到克隆对象上,那么这就是深复制。

也就是说,深复制才是真正的完全克隆。浅复制只是复制了最外面的一层壳。

实现深复制

深复制需要将内部的引用数据类型的属性也要复制一份全新的对象。我们可以递归式的向下找出每个引用对象然后复制一份再赋值,但是那样太麻烦了,效率也太差了。我们一般可以使用序列化的方式来实现:将对象二进制流写到内存中,然后再从流中反序列化这个对象。

// 原型类
@Data
class SerialObj implements Serializable {
    private String name;
    private Attachment attachment;
    public SerialObj deepClone() throws IOException, ClassNotFoundException {
        // 将对象写入流中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);
        // 从流中取出对象
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (SerialObj) ois.readObject();
    }
}
// 内置组合对象
class Attachment implements Serializable {
    private String name;

    public String getName() {
        return name;
    }

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

优点

  • 可以深复制

缺点

  • 代码量大些,但必要用时候只能用这个。

原型管理器

将多个原型对象存储在一个集合中给客户端使用。相当于是一个工厂,这个工厂里面已经有了一堆的模具,谁要对象和这个工厂一提,工厂不会吧自己的模具给你,而是以这个模具克隆一份给你。原型管理器就相当于原型类的工厂。

简而言之,就是创建一个原型管理器,里面存放着一堆原型对象,外界通过原型管理器去克隆原型对象,而不是直接操作原型对象。

UML

基本要素

  1. 原型对象的统一接口
  2. 原型对象
  3. 原型对象管理器类,这个工厂一般肯定是单例的,因为不是单例也没啥意义。

实现

//1: 一个该管理器管理原型类的公共接口
interface IObj extends Cloneable {
    IObj clone();
    void say();
}

//2: 原型类定义,需要自己实现clone
class Obj1 implements IObj {
    @Override
    public IObj clone() {
        try {
            return (IObj) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public void say() {
        System.out.println("i am 1");
    }
}

class Obj2 implements IObj {
    @Override
    public IObj clone() {
        try {
            return (IObj) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
    @Override
    public void say() {
        System.out.println("i am 2");
    }
}

//3: 原型对象管理器
class PrototypeManager {
    /*使用饿汉式单例。*/
    private HashMap<String, IObj> hm = new HashMap<>();
    private static final PrototypeManager manager = new PrototypeManager();
    private PrototypeManager() {
        hm.put("1", new Obj1());
        hm.put("2", new Obj2());
    }
    public IObj getObj(String key) {
        return (hm.get(key)).clone();
    }
    public static PrototypeManager getManager() {
        return manager;
    }
}

相关代码:github.com/zhaohaoren/…

如有代码和文章问题,还请指正!感谢!