设计模式-原型模式

相关阅读:

设计模式简介

设计模式-简单工厂模式

设计模式-工厂方法模式

设计模式-抽象工厂模式

设计模式-单例模式


原型模式

原型模式是一种对象创建型模式

定义:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。

通俗的说,原型模式中有一个样板实例,该实例有内部属性,用户从样板实例中复制一个一模一样的实例,复制的过程,我们称之为克隆被复制的实例,称之为原型

这个原型是可以定制的,即可以定制其内部属性


举个例子:

公司需要制作工牌,现有一个工牌模板如下,模板中有内部属性:照片,姓名,部门,职务和编号,需要每位员工自行填写信息

每个员工,都要下载(克隆)该工牌模板(原型)到本地(文件A),贴上照片,填上个人相关信息

如果没有定制好模板,让每个员工创建一个工牌的话(创建的重复工牌对象),代价比较大,倒不如创建好一个原型,利用原型快速地生成和原型对象一样的实例(文件A),然后对文件A进行修改


模式中包含的角色及职责:

抽象原型(Prototype)角色:声明克隆方法的接口,是所有具体原型类的公共父类,它可是抽象类也可以是接口,甚至可以是具体实现类

具体原型(ConcretePrototype)角色:它实现抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象

客户(Client)角色:在客户类中,让一个原型对象克隆自身从而创建一个新的对象

原型模式结构图及核心:

原型模式的核心是克隆(Clone)方法,通过该方法对对象进行克隆,在原型模式中,克隆又分为浅克隆和深克隆

  • 浅克隆当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制
  • 深克隆除了对象本身被复制外,对象所包含的所有成员变量也将被复制

克隆的内部机制:

在java语言有一个Cloneable接口,Java中提供了一个Cloneable接口来标示这个对象是可以克隆的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被克隆,通过重写Object.clone方法进行对象的克隆,仅仅有实现了这个接口的类才干够被拷贝。否则在执行时会抛出CloneNotSupportedException异常。

关于Object类的clone方法,默认实现为“浅克隆”,重写Object类中的clone方法。Java中全部类的父类都是Object类,Object类中有一个clone方法。作用是返回对象的一个拷贝,可是其作用域是protected类型的,一般的类无法调用,因此Prototype类须要将clone方法的作用域改动为public类型。

原型模式实现过程:

1.创建抽象原型角色:创建一个实现Cloneable接口的类/抽象类/接口,重写clone方法(如果是具体实现类,跳过步骤2)

2.创建具体原型角色:扩展抽象原型角色的实体类

3.创建用户类

实例:

相关代码已上传到GitLab,地址:https://gitlab.com/louisvv/DesignPattern

背景:louisvv公司,想要给开发部门的职工制作工牌

目的:通过原型模式实现浅克隆和深克隆

工牌有如下属性:公司,部门,姓名,职位,员工编号,照片,其中公司是一个对象(为了区分深浅克隆)

浅克隆

1.首先定义一个StaffCard类,定义基本属性,实现Cloneable接口,实现clone方法

public class StaffCard implements Cloneable{
    //  创建company实例
    Company company=new Company();
    //  部门
    private String department;
    //  姓名
    private String name;
    //  职位
    private String job;
    //  员工编号
    private int id;
    //  显示员工信息
    public void show(){
        System.out.println(name + ",编号:"+id+",在"+getCompany().getName()+department+"担任:"+job);
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public String getName() {
        return name;
    }

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

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    //实现clone方法
    public StaffCard clone(){
        try {
            return (StaffCard) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

2.创建Company类

public class Company {
    private String name;

    public String getName() {
        return name;
    }

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

3.编写客户端,用于验证通过克隆方式创建的对象,是否为同一个对象

public class Client {
    public static void main(String[] args) {
        StaffCard staffCard=new StaffCard();
        staffCard.setId(100);
        staffCard.setDepartment("开发部");
        staffCard.setJob("java开发工程师");
        staffCard.company.setName("louisvv公司");
        staffCard.setName("Tom");

        StaffCard staffCard2=staffCard.clone();
        staffCard2.setName("Jerry");
        staffCard2.setJob("前端开发工程师");
        staffCard2.setId(101);
        //  打印员工信息
        staffCard.show();
        staffCard2.show();
        System.out.println("--------------");
        //  打印对象哈希码,来判断是否是同一个对象
        System.out.println(staffCard);
        System.out.println(staffCard2);
        System.out.println("--------------");
        //  打印company对象的哈希码
        System.out.println(staffCard.getCompany().hashCode());
        System.out.println(staffCard2.getCompany().hashCode());
    }
}

结果如下:

通过克隆的方式,创建的对象为一个新的对象,与原型不为同一个对象

Tom,编号:100,在louisvv公司开发部担任:java开发工程师
Jerry,编号:101,在louisvv公司开发部担任:前端开发工程师
--------------
Prototype.StaffCard@74a14482
Prototype.StaffCard@1540e19d
--------------
1735600054
1735600054

但是由于是浅克隆,引用的company对象,为同一个引用地址,当修改staffCard原型的company对象

修改staffCard原型中company类的属性

staffCard.company.setName("balabala公司");

打印出工牌信息

staffCard.show();
staffCard2.show();

结果如下:

如果修改了原型中Company对象的属性,那么通过浅复制的克隆对象中Company的引用也会进行改变

Tom,编号:100,在balabala公司运维部担任:java开发工程师
Jerry,编号:101,在balabala公司运维部担任:前端开发工程师

但是,如果Tom员工跳槽去了另一家公司,修改了company信息,Jerry员工的company信息也要修改,这当然是不符合需求的,所以,需要进行深克隆!

深克隆

1.修改Company类,实现Cloneable接口,实现clone方法

public class Company implements Cloneable{
    private String name;

    public String getName() {
        return name;
    }

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

    public Company clone(){
        try {
            return (Company) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

2.创建一个DeepStaffCard类,定义基本属性,实现Cloneable接口,实现clone方法

与浅克隆不同,clone方法中,需要对引用类型,即Company类,也进行克隆

public class DeepStaffCard implements Cloneable {
    //  创建company类
    Company company = new Company();
    //  部门
    private String department;
    //  姓名
    private String name;
    //  职位
    private String job;
    //  员工编号
    private int id;

    //  显示员工信息
    public void show() {
        System.out.println(name + ",编号:" + id + ",在" + getCompany().getName() + department + "担任:" + job);
    }

    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public String getName() {
        return name;
    }

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

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    //  实现clone方法
    public DeepStaffCard clone() {
        try {
            DeepStaffCard deepStaffCard = (DeepStaffCard) super.clone();
            deepStaffCard.company = company.clone();
            return deepStaffCard;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

3.编写客户端类,进行测试:

public class Client {
    public static void main(String[] args) {
        DeepStaffCard deepStaffCard = new DeepStaffCard();
        deepStaffCard.company.setName("louisvv公司");
        deepStaffCard.setDepartment("开发部");
        deepStaffCard.setName("Tom");
        deepStaffCard.setJob("java开发工程师");
        deepStaffCard.setId(102);

        DeepStaffCard deepStaffCard2 = deepStaffCard.clone();
        deepStaffCard2.setId(103);
        deepStaffCard2.setName("Jerry");
        deepStaffCard2.setJob("前端开发工程师");

        deepStaffCard.show();
        deepStaffCard2.show();
        System.out.println("--------------");
        System.out.println(deepStaffCard.getCompany().hashCode());
        System.out.println(deepStaffCard2.getCompany().hashCode());
        
    }
}

结果如下:

发现原型对象和克隆对象的company引用类型哈希码不同,说明不是同一个引用,即为深克隆

Tom,编号:102,在louisvv公司开发部担任:java开发工程师
Jerry,编号:103,在louisvv公司开发部担任:前端开发工程师
--------------
1956725890
356573597

修改deepStaffCard原型对象的company对象信息,并打印工牌信息

deepStaffCard.company.setName("balabala公司");
deepStaffCard.show();
deepStaffCard2.show();

结果:

在修改原型的company对象信息后,对克隆对象的company对象没有影响!满足了需求!

Tom,编号:102,在balabala公司开发部担任:java开发工程师
Jerry,编号:103,在louisvv公司开发部担任:前端开发工程师

总结

结合实例,对原型模式进行总结

优点:

1、如果创建新的对象比较复杂时,利用原型模式简化对象的创建过程,提高效率

2、使用深克隆可以保持对象的状态,使用深克隆将对象复制一份并将其状态保存起来,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作

3、原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式不需要这样,原型模式中产品的复制是通过封装在类中的克隆方法实现的,无需专门的工厂类来创建产品

缺点:

1.需要为每一个类配置一个克隆方法,而且该克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则

2.实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多个引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦

应用场景:

1.类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等

2.一个对象多个修改者的场景

3.一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用

应用实例:

SparkConf的设计

在某些情况下,同一个SparkConf实例中的配置信息需要被Spark中的多个组件共用,就运用到了原型模式

例如,组件A中存在一个SparkConf实例a,组件B中也需要实例a中的配置信息,我们可以将SparkConf实例定义为全局变量或者通过参数传递给其他组件,可以新建一个SparkConf实例b,并将实例a中的配置信息拷贝到b中。

于是SparkConf继承了Cloneable特质,并实现了clone方法,clone方法通过实现Cloneable特质提高了代码的可复读行。

  override def clone: SparkConf = {
    val cloned = new SparkConf(false)
    settings.entrySet().asScala.foreach { e =>
      cloned.set(e.getKey(), e.getValue(), true)
    }
    cloned
  }


看完了,记得个赞哦

赫墨拉

我是一个喜爱大数据的小菜鸡,这里是我分享我的成长和经历的博客

You may also like...

发表评论

电子邮件地址不会被公开。