漫画:什么是 “原型模式” ?

简介: 在Java语言中,Object类实现了Cloneable接口,一个对象可以通过调用Clone()方法生成对象,这就是原型模式的典型应用。但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口,却调用了clone()方法,就会报错。


640.png640.png640.png640.png640.png640.png640.png

————————————

640.png640.png640.png640.png640.png640.png640.png


假如有一天,小灰被外星人抓走了,外星人要拿小灰做实验,想了解小灰在吃得好、睡得好、玩得开心的场景下,与现实中小灰的生存状态有什么区别。

于是,外星人克隆了几个一模一样的小灰:

 

640.png

 

就这样,小灰的原型被留在现实中,而三个复制体分别提供了吃得好、睡得好、玩得开心三种不同环境,小灰的原型则不受三个复制体的影响。

过了一段时间,我们来观察一下本体与分身的生存状态:

 

640.png640.png640.png640.jpg640.png640.png

 

Java语言中,Object类实现了Cloneable接口,一个对象可以通过调用Clone()方法生成对象,这就是原型模式的典型应用。

但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口,却调用了clone()方法,就会报错。

 

 

// protected native Object clone() throws

 CloneNotSupportedException;protected Object clone() throws

CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException(

"Class " + getClass().getName() +

" doesn't implement Cloneable");
    }
    return internalClone();

}

// Native helper method for cloning.


private
native Object internalClone();

 

 640.png

Java中的数据类型,分为基本类型和引用类型。在一个方法里的变量如果是基本类型的话,变量就直接存储在这个方法的栈帧里,例如intlong等;而引用类型则在栈帧里存储这个变量的指针,指向堆中该实体的地址,例如StringArray等。深拷贝和浅拷贝是只针对引用数据类型的。

比如一个方法有一个基本类型参数和一个引用类型参数,在方法体里对参数重新赋值,会影响传入的引用类型参数,而不会影响基本类型参数,因为基本类型参数是值传递,而引用类型参数是引用传递。

先定义一个用户类:

 

// 这是一个非常简单的用户类

  public
class User {
   
private String name;
   
private int age;

   
public User(String name, int age) {
       
this.name=name;
       
this.age=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;
    }

    @Override

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

}

测试:

private int x=10;

  public
void updateValue(int value){
    value = 3 * value;
}

  private User
user= new User("大黄",20);

  public
void updateUser(User student){
    student.setName("
小灰");
    student.setAge(18);

}

 

 

  public void test(){
    System.out.println("
调用前x的值:"+x);
    updateValue(
x);
    System.out.println("
调用后x的值:"+x);
    System.out.println("
调用前user的值:"+user.toString());
    updateUser(
user);
    System.out.println("
调用后user的值:"+user.toString());

}

 

Log打印结果如下:

 

调用前x的值:10

调用后x的值:10

调用前user的值:User{name='大黄, age=20}

调用后user的值:User{name='小灰, age=18}

 

传递基本类型的方法(updateValue())流程图:

 640.png

传递引用类型的方法(updateUser())流程图:

 640.png

这其中也包含着例外,比如String类型和大小不超过127Long类型,虽然也是引用类型,却像基本类型一样不受影响。这是因为它们会先比较常量池维护的值,这涉及VM的内容,今天不做过多讨论。

浅拷贝是在按位(bit)拷贝对象,这个对象有着原始对象属性值的一份精确拷贝。我们结合应用场景分析一下,还是刚才的User类,我们增加一个存放地址的内部类Address,我们需要用户信息可以被其他module查询,但是不允许它们被其他module修改,新增代码如下:

 

// 这是一个稍微复杂的、支持拷贝的用户类

  public
class User implements Cloneable {

// ……省略上文代码……
 
    private Address address;

    @NonNull
    @NotNull
    @Override

   
public User clone() {
       
try{
           
return (User)super.clone();

        }
catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
       
return null;
    }

   
public class Address{
       
// 地市
       
public String city;
       
// 区县
       
public String county;
       
// 乡镇街道
       
public String street;
    }
}

 

 

 

640.png640.png640.png

// 这是一个更复杂的、支持深拷贝的用户类

  public
class User implements Cloneable {

 
    // ……省略上文代码……
    @NonNull
    @NotNull
    @Override
   
public User clone() {
       
try{
            User newUser = (User)
super.clone();
            newUser.setName(
this.name);
            newUser.setAddress(
this.address.clone());
           
return newUser;
        }
catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
       
return null;
    }

   
public class Address implements Cloneable{
       
// ……省略上文代码……
        @NonNull
        @NotNull
        @Override
       
public Address clone() {
           
try{
                Address newAddress = (Address)
super.clone();
                newAddress.
city = this.city;
                newAddress.
county = this.county;
                newAddress.
street = this.street;
                return newAddress;
            }
catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
           
return null;
        }
    }
}

 

需要注意的是,上面代码的深拷贝其实并不彻底,因为彻底的深拷贝几乎是不可能实现的,那样不但可能存在引用关系非常复杂的情况,也可能存在引用链的某一级上引用了一个没有实现Cloneable接口的第三方对象的情况。

绝大多数设计模式都是牺牲性能提升开发效率的,原型模式则是为数不多的牺牲开发效率提升性能的设计模式。

 640.png640.png640.png640.png640.png640.png


private User user= new User("大黄",20);

  public
void testNew(){

    User user1 =
new User("小灰",18);

}


 
public void testClone(){

    User user2 =
user.clone();

}

 

通过ASM工具查看bytecode,可以看出二者对栈资源的消耗:

 

// access flags 0x1

  public  testNew()V

   ……省略……

    MAXSTACK  = 4

     MAXLOCALS = 2

 

  // access  flags 0x1

  public  testClone()V

   ……省略……

    MAXSTACK  = 1

     MAXLOCALS = 2

 640.png

@Override

  public Object clone() {

 
    return new Intent(this);

 
}

 640.png640.png

最后我们来总结一下原型模式的核心用途:

1.解决构建复杂对象的资源消耗问题,提升创建对象的效率。

2.保护性拷贝,防止外部对只读对象进行需修改。

640.png640.png640.png640.png640.png

相关文章
|
7月前
|
设计模式 Java
小谈设计模式(10)—原型模式
小谈设计模式(10)—原型模式
|
7月前
|
设计模式 Java Spring
|
设计模式 Java 领域建模
23种设计模式漫画版系列—原型模式
23种设计模式漫画版系列—原型模式
166 0
|
设计模式
设计模式-原型模式(简历复印)
设计模式-原型模式(简历复印)
83 0
GOF之建造者模式(创建型模式)附图详解 ✨ 每日积累
GOF之建造者模式(创建型模式)附图详解 ✨ 每日积累
GOF之建造者模式(创建型模式)附图详解 ✨ 每日积累
漫画:什么是 “建造者模式” ?
首先,我们来定义一个Product类:接下来,我们定义抽象的Builder类:然后,是具体的Builder实现类:
126 0
漫画:什么是 “建造者模式” ?
|
XML 设计模式 Java
漫画:什么是 “抽象工厂模式” ?
抽象工厂模式: 抽象工厂模式把产品子类进行分组,同组中的不同产品由同一个工厂子类的不同方法负责创建,从而减少了工厂子类的数量。
202 0
漫画:什么是 “抽象工厂模式” ?
|
安全 Java 编译器
漫画:什么是单例模式?(整合版)
为什么这样写呢?我们来解释几个关键点: 1.要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。 2.instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。 3.getInstance是获取单例对象的方法。
177 0
漫画:什么是单例模式?(整合版)
|
设计模式
漫画:设计模式之 “工厂模式”
假设我们的业务代码当中,有一个被广泛引用的“口罩类”,这个类实例需要在许多地方被创建和初始化,而初始化的代码也比较复杂。
174 0
漫画:设计模式之 “工厂模式”
漫画:什么是 “代理模式” ?
在上面的代码中,代理类和业务类继承了相同的接口,并且重写了添加/删除学生的方法。 在重写的方法中,我们不仅可以调用业务类的原有方法,并且在调用的前后可以进行额外的处理,比如加上日志、事务等等。 这样一来,在客户端当中,我们只要创建了代理类,就可以像使用业务类一样使用它,非常方便:
160 0
漫画:什么是 “代理模式” ?