原型模式

简介: 主要内容有:该设计模式的详细介绍,包括:引子,意图(大白话解释)类图,时序图(理论规范)该模式的代码示例:熟悉该模式的代码长什么样子该模式的优缺点:不可以滥用模式该模式的实际使用案例:了解它在哪些重要的源码中出现过


前言



主要内容有:

  • 该设计模式的详细介绍,包括:
  • 引子,意图(大白话解释)
  • 类图,时序图(理论规范)
  • 该模式的代码示例:熟悉该模式的代码长什么样子
  • 该模式的优缺点:不可以滥用模式
  • 该模式的实际使用案例:了解它在哪些重要的源码中出现过


原型模式 Prototype



引子

还记得深克隆和浅克隆的区别吗?其实这里说的克隆,就是原型模式。

原型模式要求对象实现一个可以克隆自身的接口(类型)。这样一来,通过原型实例创建新的对象。

原型模式也属于创建型模式。


意图

原型模式有两种表现形式:

  • 简单形式
  • 登记形式

他们的区别在于:第二种登记模式中,多了一个原型管理器(PrototypeManager)角色,该角色的作用是:创建具体原型类的对象,并记录每一个被创建的对象。

如果需要创建的原型对象数目较少而且比较固定的话,可以采取简单形式。在这种情况下,原型对象的引用可以由客户端自己保存。

否则,你可以使用登记形式。原型管理器的作用:

在登记形式下,客户端不保存对原型对象的引用,这个任务被交给原型管理器角色。在克隆一个对象之前,客户端可以查看管理员对象是否已经有一个满足要求的原型对象。如果有,可以从原型管理器角色中取得这个对象引用;如果没有,客户端就需要自行复制此原型对象。


类图



简单形式

  • 客户(Client):客户类提出创建对象的请求;
  • 抽象原型(Prototype):这是一个抽象角色,通常是一个Java接口或者抽象类。此角色定义了的具体原型类所需的实现的方法。
  • 具体原型(Concrete Prototype):此角色需要实现抽象原型角色要求的克隆相关的接口。

登记形式

多出了:

  • 原型管理器(PrototypeManager):客户端client直接调用原型管理器


时序图


使用举例和实际使用场景举例



本文将使用举例和实际使用场景举例放在一起来讨论,是由于原型模式最典型的例子就是Java的深克隆和浅克隆 (clone方法),我们直接使用深克隆和浅克隆的代码来熟悉原型模式。

clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。一般而言,clone()方法满足以下的描述:

(1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型一样。
(3)如果对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
复制代码

在JAVA语言的API中,凡是提供了clone()方法的类,都满足上面的这些条件。JAVA语言的设计师在设计自己的clone()方法时,也应当遵守着三个条件。一般来说,上面的三个条件中的前两个是必需的,而第三个是可选的。

浅复制 clone()

当进浅复制时,clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不同的堆空间。同时,复制出来的对象具有与原对象一致的状态。

此处对象一致的状态是指:复制出的对象与原对象中的属性值完全相等==。

代码示例:

我们复制一本书

1.定义Book类和Author类:

class Author {
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
复制代码
class Book implements Cloneable {
    private String title;
    private int pageNum;
    private Author author;
    public Book clone() {
        Book book = null;
        try {
            book = (Book) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return book;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public Author getAuthor() {
        return author;
    }
    public void setAuthor(Author author) {
        this.author = author;
    }
}
复制代码

2.测试:

package com.qqyumidi;
public class PrototypeTest {
    public static void main(String[] args) {
        Book book1 = new Book();
        Author author = new Author();
        author.setName("corn");
        author.setAge(100);
        book1.setAuthor(author);
        book1.setTitle("好记性不如烂博客");
        book1.setPageNum(230);
        Book book2 = book1.clone();
        System.out.println(book1 == book2);  // false
        System.out.println(book1.getPageNum() == book2.getPageNum());   // true
        System.out.println(book1.getTitle() == book2.getTitle());        // true
        System.out.println(book1.getAuthor() == book2.getAuthor());        // true
    }
}
复制代码

虽然复制出来的对象重新在堆上开辟了内存空间,但是,对象中各属性确保持相等。对于基本数据类型很好理解,但对于引用数据类型来说,则意味着此引用类型的属性所指向的对象本身是相同的, 并没有重新开辟内存空间存储。换句话说,引用类型的属性所指向的对象并没有复制。

由此,我们将其称之为浅复制。

而当复制后的对象的引用类型的属性所指向的对象也重新得以复制,此时,称之为深复制。


深复制 deepclone()

Java中的深复制一般是通过对象的序列化和反序列化得以实现。序列化时,需要实现Serializable接口。

从输出结果中可以看出,深复制不仅在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,重新开辟了堆空间存储。

深复制的两种实现方式

在Java语音中,想要实现深度拷贝其实有两种方式:

  • 通过序列化
  • 重写clone,调用子成员的clone方法

代码示例:通过 序列化 实现深复制

public  Object deepClone() 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 ois.readObject();
    }
复制代码

代码示例:通过 重写clone 实现深复制

1.定义Book类和Author类(注意:不仅Book类需要实现Serializable接口,Author同样也需要实现Serializable接口!!):

class Author implements Serializable{
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
复制代码
class Book implements Serializable {
    private String title;
    private int pageNum;
    private Author author;
    public Book deepClone() 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 (Book) ois.readObject();
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public int getPageNum() {
        return pageNum;
    }
    public void setPageNum(int pageNum) {
        this.pageNum = pageNum;
    }
    public Author getAuthor() {
        return author;
    }
    public void setAuthor(Author author) {
        this.author = author;
    }
}
复制代码

2.测试:

public class PrototypeTest {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        Book book1 = new Book();
        Author author = new Author();
        author.setName("corn");
        author.setAge(100);
        book1.setAuthor(author);
        book1.setTitle("好记性不如烂博客");
        book1.setPageNum(230);
        Book book2 = book1.deepClone();
        System.out.println(book1 == book2);  // false
        System.out.println(book1.getPageNum() == book2.getPageNum());   // true
        System.out.println(book1.getTitle() == book2.getTitle());        // false
        System.out.println(book1.getAuthor() == book2.getAuthor());        // false
    }
}
复制代码

深复制不仅在堆内存上开辟了空间以存储复制出的对象,甚至连对象中的引用类型的属性所指向的对象也得以复制,重新开辟了堆空间存储。


该模式的优缺点



优点

原型模式允许在运行时动态改变具体的实现类型。原型模式可以在运行期间,有客户来注册符合原型接口的实现类型,也可以动态的改变具体的实现类型,看起来接口没有任何变化,但是其实运行的已经是另外一个类实体了。因为克隆一个原型对象就类似于实例化一个类。

缺点

缺点就是...你需要自己写一个clone方法


参考



juejin.cn/post/684490…

www.cnblogs.com/java-my-lif…

www.jianshu.com/p/b0e13717b…

相关文章
|
6月前
|
Java 数据库
原型模式
原型模式
38 1
|
设计模式
2023-6-14-第五式原型模式
2023-6-14-第五式原型模式
70 0
|
7月前
|
缓存 数据库 C++
创建型 原型模式
创建型 原型模式
54 1
|
设计模式 Java 关系型数据库
原型模式解读
原型模式解读
|
安全 Java
创建型模式-原型模式
创建型模式-原型模式
119 0
原型模式必知必会
原型模式必知必会
106 0
原型模式必知必会
|
设计模式 JSON JavaScript
我学会了,原型模式
原型模式属于创建型模式,这个类型的设计模式是将 对象的创建和使用解耦了,花式的去创建对象。
148 0
我学会了,原型模式
|
JSON Java uml
原型模式 与 建造者模式(1)
原型模式 与 建造者模式(1)
214 0
原型模式 与 建造者模式(1)
|
算法 uml
原型模式 与 建造者模式(4)
原型模式 与 建造者模式(4)
156 0
原型模式 与 建造者模式(4)
|
Java 测试技术 API
原型模式 与 建造者模式(2)
原型模式 与 建造者模式(2)
134 0
原型模式 与 建造者模式(2)