设计模式--原型模式

一、问题的提出
在应用程序中,有些对象比较复杂,其创建过程过于复杂,而且我们又需要频繁的利用该对象,如果这个时候我们按照常规思维new该对象,那么务必会带来非常多的麻烦,这个时候我们就希望可以利用一个已有的对象来不断对他进行复制就好了。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

二、原型模式
能够解决以上问题的模式称为原型模式。
原型模式:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
在原型模式中可以利用一个原型对象来指明要创建对象的类型,然后通过复制这个对象的方法来获得与该对象一模一样的对象实例。这就是原型模式的设计目的。
在原型模式中,所发动创建的对象通过请求原型对象来拷贝原型对象自己来实现创建过程,当然所发动创建的对象需要知道原型对象的类型。也就是说所发动创建的对象只需要知道原型对象的类型就可以获得更多的原型实例对象,至于这些原型对象时如何创建的根本不需要关心。

Tips:浅拷贝,深拷贝
先理解一下引用拷贝和对象拷贝
1、引用拷贝

Teacher teacher = new Teacher("Taylor",26); 
Teacher otherteacher = teacher; 
System.out.println(teacher); 
System.out.println(otherteacher);

输出结果:

blog.Teacher@355da254
blog.Teacher@355da254

结果分析:由输出结果可以看出,它们的地址值是相同的,那么它们肯定是同一个对象。teacher和otherteacher的只是引用而已,他们都指向了一个相同的对象Teacher(“Taylor”,26)。 这就叫做引用拷贝。


image.png

2、对象拷贝

Teacher teacher = new Teacher("Swift",26); 
Teacher otherteacher = (Teacher)teacher.clone(); 
System.out.println(teacher); 
System.out.println(otherteacher);

输出结果:

blog.Teacher@355da254
blog.Teacher@4dc63996

结果分析:由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。


image.png

深拷贝和浅拷贝都是对象拷贝
浅拷贝:对于基本类型的数据会进行拷贝,而对其他对象的引用仍然指向原来的对象。
深拷贝:基本类型和其他对象都会进行拷贝。

举个栗子:
1、浅拷贝

public class ShallowCopy { 
  public static void main(String[] args) throws CloneNotSupportedException { 
    Teacher teacher = new Teacher(); 
    teacher.setName("Delacey"); 
    teacher.setAge(29); 
    Student2 student1 = new Student2(); 
    student1.setName("Dream"); 
    student1.setAge(18); 
    student1.setTeacher(teacher); 
    Student2 student2 = (Student2) student1.clone(); 
    System.out.println("拷贝后"); 
    System.out.println(student2.getName());
    System.out.println(student2.getAge());
    System.out.println(student2.getTeacher().getName());
    System.out.println(student2.getTeacher().getAge());
    System.out.println("修改老师的信息后-------------"); // 修改老师的信息 
    teacher.setName("Jam");
    System.out.println(student1.getTeacher().getName());
    System.out.println(student2.getTeacher().getName()); 
  } 
} 
class Teacher implements Cloneable { 
  private String name; 
  private int age; 
  public String get...
  public void set...
} 
class Student2 implements Cloneable { 
  private String name; 
  private int age; 
  private Teacher teacher; 
  public String get...
  public void set...
  @Override 
  public Object clone() throws  CloneNotSupportedException { 
    Object object = super.clone(); return object; 
  } 
}

输出结果:

拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Jam

结果分析: 两个引用student1和student2指向不同的两个对象,但是两个引用student1和student2中的两个teacher引用指向的是同一个对象,所以说明是浅拷贝。


image.png

2、深拷贝

public class DeepCopy { 
  public static void main(String[] args) throws Exception { 
    Teacher2 teacher = new Teacher2(); 
    teacher.setName("Delacey"); 
    teacher.setAge(29); 
    Student3 student1 = new Student3(); 
    student1.setName("Dream"); 
    student1.setAge(18); 
    student1.setTeacher(teacher); 
    Student3 student2 = (Student3) student1.clone(); 
    System.out.println("拷贝后"); 
    System.out.println(student2.getName()); 
    System.out.println(student2.getAge());
    System.out.println(student2.getTeacher().getName());
    System.out.println(student2.getTeacher().getAge()); 
    System.out.println("修改老师的信息后-------------"); // 修改老师的信息 
    teacher.setName("Jam"); 
    System.out.println(student1.getTeacher().getName());
    System.out.println(student2.getTeacher().getName()); 
  }
}
class Teacher2 implements Cloneable { 
  private String name; 
  private int age; 
  public String get... 
  public void set...
  @Override 
  public Object clone() throws CloneNotSupportedException { 
    return super.clone(); 
  }
} 
class Student3 implements Cloneable { 
  private String name; 
  private int age; 
  private Teacher2 teacher; 
  public String get...
  public void set...
  @Override 
  public Object clone() throws CloneNotSupportedException {
    Student3 student = (Student3) super.clone(); // 将  Teacher对象复制一份并重新set进来 
    student.setTeacher((Teacher2) student.getTeacher().clone()); 
    return student; 
  } 
}

输出结果:

拷贝后
Dream
18
Delacey
29
修改老师的信息后-------------
Jam
Delacey

teacher姓名Delacey更改前:


image.png

teacher姓名Jam更改后


image.png

原型模式结构图
image.png

原型模式主要包含如下三个角色:
Prototype:抽象原型类。声明克隆自身的接口。
ConcretePrototype:具体原型类。实现克隆的具体操作。
Client:客户类。让一个原型克隆自身,从而获得一个新的对象。

Java中任何实现了Cloneable接口的类都可以通过调用clone()方法来复制一份自身然后传给调用者。一般而言,clone()方法满足:
(1) 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象。
(2) 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
(3) 如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。

三、举例实现
复制简历

public class Resume implements Cloneable {
    private String name;
    private String company;
    ...//其他需要的信息
    
    /**
     * 构造函数:初始化简历赋值姓名
     */
    public Resume(String name){
        this.name = name;
    }
    /**
     * @desc 设定工作经历
     * @param company 所在公司
     * @return void
     */
    public void setWorkExperience(String company){
        this.company = company;
    }
    ...//其他需要设置的信息
    /**
     * 克隆该实例
     */
    public Object clone(){
        Resume resume = null;
        try {
            resume = (Resume) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return resume;
    }
    
    public void display(){
        System.out.println("姓名:" + name);
        System.out.println("公司:" + company);
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
        //原型A对象
        Resume a = new Resume("小李子");
        a.setWorkExperience( "XX科技有限公司");
        
        //克隆B对象
        Resume b = (Resume) a.clone();

//克隆C对象,并修改其中的数据
        Resume c = (Resume) a.clone();
        c.setWorkExperience("XX大学");

        
        //输出A、B、C对象
        System.out.println("----------------A--------------");
        a.display();
        System.out.println("----------------B--------------");
        b.display();
System.out.println("----------------C--------------");
        c.display();

        
        /*
         * 测试A==B?
         * 对任何的对象x,都有x.clone() !=x,即克隆对象与原对象不是同一个对象
         */
        System.out.print("A==B?");
        System.out.println( a == b);
        
        /*
         * 对任何的对象x,都有x.clone().getClass()==x.getClass(),即克隆对象与原对象的类型一样。
         */
        System.out.print("A.getClass()==B.getClass()?");
        System.out.println(a.getClass() == b.getClass());
    }
}

运行结果:

-----------------A-----------------
姓名:小李子
公司:XX科技有限公司
-----------------B-----------------
姓名:小李子
公司:XX科技有限公司
-----------------C-----------------
姓名:小李子
公司:XX大学
A==B? false
A.getClass()==B.getClass()?true

可以看到B完全复制了A的内容,C也复制了A的内容,但是C有自身的变化,但并没有影响到A和B。

四、原型模式的优缺点
优点:
1、如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
2、可以使用深克隆保持对象的状态。
3、原型模式提供了简化的创建结构。
缺点:
1、在实现深克隆的时候可能需要比较复杂的代码。
2、需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。

五、原型模式使用场景
1、如果创建新对象成本较大,我们可以利用已有的对象进行复制来获得。
2、如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式来应用。相反,如果对象的状态变化很大,或者对象占用的内存很大,那么采用状态模式会比原型模式更好。
3、需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容

  • 定义 原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建...
    步积阅读 1,301评论 0 2
  • 目录 本文的结构如下: 引言 什么是原型模式 浅克隆和深克隆 clone() 模式的结构 典型代码 代码示例 优点...
    w1992wishes阅读 485评论 0 0
  • 在阎宏博士的《JAVA与模式》一书中开头是这样描述原型(Prototype)模式的:原型模式属于对象的创建模式。通...
    Ant_way阅读 397评论 0 0
  • 原型模式 介绍 在许多面向对象的应用程序中,有些对象的创建代价过大或者过于复杂。要是可以重建相同的对象并作轻微的改...
    666真666阅读 431评论 0 2
  • 今天我看了一篇文章,特别有意思:大概讲得是月薪5000的男子力挺女友辞职,并说我养你,结果局面一度很尴尬。 我想这...
    Hey是我呀阅读 1,083评论 1 5