前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java设计模式--原型模式

Java设计模式--原型模式

作者头像
Frank909
发布2019-01-14 17:24:35
5250
发布2019-01-14 17:24:35
举报
文章被收录于专栏:Frank909Frank909

先上定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 Prototype原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

这些定义讲得对极了,但是对于初学者而言,我觉得是灾难。这种学术性的东西是用来总结的,而不是用来引导启蒙。而最好的方式,应该是大白话,举生动的例子,毕竟软件的出现就是为了解决特定场景的需求。

原型模式就是复制–粘贴

这是网络上很流行的一个比喻。说得对极了。下面我的文章也就是为了更进一步帮大家加深印象。

场景

中国人引以自豪的“四大发明”中有这么一项–活字印刷术。

活字印刷术是一种古代印刷方法,是中国古代劳动人民经过长期实践和研究才发明的。先制成单字的阳文反文字模,然后按照稿件把单字挑选出来,排列在字盘内,涂墨印刷,印完后再将字模拆出,留待下次排印时再次使用。

字模的出现,解放了生产力,节省了大量的物力和人力。而字模之间有什么差别呢?字模有同样的规格,同样的材料,唯一不同的就是字模上面的字不同。因此生产字模的时候可以用同一个模具,这里面就有原型模式的思想在里面。模具产生一个个几乎相同的字模,这相当于克隆,然后改变每个字模上面的字,于是字模就独一无二了。但归根结底,它们仍旧是同一类事物,通过模具克隆生产,比重新一个个特别制作要省时省心。

再来一个例子,在java世界中创建一个对象通常用new关键字就可以搞定。比如一个教师,它的属性有名字、地址、身高、课程。

代码语言:javascript
复制
class Teacher{
    String name;
    String addr;
    int height;
    ArrayList<String> courses;
    ...
}

我们创建一个教师实例自然是new的形式。

代码语言:javascript
复制
    Teacher a = new Teacher();
    Teacher b = new Teacher();
    Teacher c = new Teacher();
    Teacher d = new Teacher();

这似乎没有什么。 那好,要给教师设立属性,但是这些老师都是一个学校的,很多属性一样。有什么问题吗?没有什么问题

代码语言:javascript
复制
a.setName("zhangsan");
a.setAddr("shenzhen");
a.setSchool("xxx");
a.setHeight(170);

b.setName("lisi");
b.setAddr("shenzhen");
b.setSchool("xxx");
b.setHeight(170);

c.setName("wangwu");
c.setAddr("shenzhen");
c.setSchool("xxx");
c.setHeight(170);

d.setName("zhaoliu");
d.setAddr("shenzhen");
d.setSchool("xxx");
d.setHeight(170);

似乎还可以接受,这四个老师其它的属性都一样,连身高都一样。如果我再创建10个同样的老师对象呢?他们还是就名字不一样。你受得了吗?我可受不了,这个时候,主人公原型模式就出来了。

而在java中cloneable很容易实现。我们让Teacher类实现Cloneable接口即可。

代码语言:javascript
复制
class Teacher implements Cloneable{
    String name;
    String school;
    String city;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSchool() {
        return school;
    }
    public void setSchool(String school) {
        this.school = school;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }



    public Teacher(String name, String school, String city) {
        super();
        this.name = name;
        this.school = school;
        this.city = city;
    }

    @Override
    protected Object clone(){
        // TODO Auto-generated method stub
        Teacher teacher = null;

        try {
            teacher = (Teacher) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return teacher;
    }
//  
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", school=" + school + ", city="
                + city + "]";
    }

}

接下来,我们创建4个老师对象,只需要这样。

代码语言:javascript
复制
public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Teacher a = new Teacher("zhangsan","beida","beijing");
        Teacher b = (Teacher) a.clone();
        Teacher c = (Teacher) a.clone();
        Teacher d = (Teacher) a.clone();

        b.setName("lisi");
        c.setName("wangwu");
        d.setName("zhaoliu");


        System.out.println(a.toString());
        System.out.println(b.toString());
        System.out.println(c.toString());
        System.out.println(d.toString());
    }

}

结果如下

代码语言:javascript
复制
Teacher [name=zhangsan, school=beida, city=beijing]
Teacher [name=lisi, school=beida, city=beijing]
Teacher [name=wangwu, school=beida, city=beijing]
Teacher [name=zhaoliu, school=beida, city=beijing]

是不是很爽?尝到甜头了吧。 对于大量的重复的同一类对象,我们可以用原型模式解放自己(确实少敲了好多行代码)。系统也减少开销。但我们需要处理一种情况,比如Teacher中有一个列表是代表这个老师教了几种科目,小时候学校师资力量有限,一个老师经常同时教几门,不是经常说的小学的数学是体育老师教的么?那好,当存在集合对象的类进行原型克隆的时候会发生什么现象?

代码语言:javascript
复制
class Teacher implements Cloneable{
    String name;
    String school;
    String city;
    ArrayList<String> courses;

    public ArrayList<String> getCourses() {
        return courses;
    }
    public void setCourses(ArrayList<String> courses) {
        this.courses = courses;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSchool() {
        return school;
    }
    public void setSchool(String school) {
        this.school = school;
    }
    public String getCity() {
        return city;
    }
    public void setCity(String city) {
        this.city = city;
    }



    public Teacher(String name, String school, String city) {
        super();
        this.name = name;
        this.school = school;
        this.city = city;
    }

    @Override
    protected Object clone(){
        // TODO Auto-generated method stub
        Teacher teacher = null;

        try {
            teacher = (Teacher) super.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return teacher;
    }
    @Override
    public String toString() {
        return "Teacher [name=" + name + ", school=" + school + ", city="
                + city + ", courses=" + courses + "]";
    }


}

我们再来原型拷贝

代码语言:javascript
复制
public class Test {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Teacher a = new Teacher("zhangsan","beida","beijing");
        ArrayList<String> courses = new ArrayList<>();
        courses.add("math");
        courses.add("chinese");
        a.setCourses(courses);

        Teacher b = (Teacher) a.clone();
        Teacher c = (Teacher) a.clone();
        Teacher d = (Teacher) a.clone();

        b.setName("lisi");
        c.setName("wangwu");
        d.setName("zhaoliu");

        b.getCourses().add("music");

        System.out.println(a.toString());
        System.out.println(b.toString());
        System.out.println(c.toString());
        System.out.println(d.toString());
    }


}

我们预想中的是a、c、d课程一样,b不同,b要多一个music。那好,看结果

代码语言:javascript
复制
Teacher [name=zhangsan, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=lisi, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=wangwu, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=zhaoliu, school=beida, city=beijing, courses=[math, chinese, music]]

吓了一跳!!!是不是?怎么回事呢? 这里要解释一下,涉及到两个概念,浅拷贝和深拷贝。

浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。 深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。

原型模式一般都是对引用的赋值,基础数据还好,但如果存在如ArrayList这种集合变量时,复制过去的只是它的引用,这样就会造成,对一处改变,其它拥有相同引用的对象也都改变,所以上面所有的课程都变成了一样。

解决方案

很简单,重写clone()方法的时候,将ArrayList变量也进行深拷贝。如下

代码语言:javascript
复制
@Override
    protected Object clone(){
        // TODO Auto-generated method stub
        Teacher teacher = null;

        try {
            teacher = (Teacher) super.clone();
            teacher.courses = (ArrayList<String>) this.courses.clone();
        } catch (CloneNotSupportedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return teacher;
    }

再看结果

代码语言:javascript
复制
Teacher [name=zhangsan, school=beida, city=beijing, courses=[math, chinese]]
Teacher [name=lisi, school=beida, city=beijing, courses=[math, chinese, music]]
Teacher [name=wangwu, school=beida, city=beijing, courses=[math, chinese]]
Teacher [name=zhaoliu, school=beida, city=beijing, courses=[math, chinese]]

正常了。

Android中的原型模式

Intent

代码语言:javascript
复制
public class Intent implements Parcelable, Cloneable {
        ......

     public Intent(Intent o) {
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;
        this.mFlags = o.mFlags;
        if (o.mCategories != null) {
            this.mCategories = new HashSet<String>(o.mCategories);
        }
        if (o.mExtras != null) {
            this.mExtras = new Bundle(o.mExtras);
        }
        if (o.mSourceBounds != null) {
            this.mSourceBounds = new Rect(o.mSourceBounds);
        }
        if (o.mSelector != null) {
            this.mSelector = new Intent(o.mSelector);
        }
        if (o.mClipData != null) {
            this.mClipData = new ClipData(o.mClipData);
        }
    }

    @Override
    public Object clone() {
        return new Intent(this);
    }
    ......
}

可以看到Intent实现了Cloneable接口,并重写了clone()方法,并且进行的是深度克隆。基础类型的变量直接赋值。需要尝试复制的则直接创建新对象,避免克隆出来的对象和自身共用同一个变量的引用。

总结

原型模式适合的场景,大都是用来创建重复的同类型但属性稍微不同的对象,但缺点就是要考虑深克隆的场景,如果一个类很复杂,里面有许多需要深度克隆的变量引用时,那么对于这个类而言,进行克隆的时候要妥善处理好变量中的深度克隆。 实际开发中要结合实际情况而定。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年03月28日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景
    • 解决方案
    • Android中的原型模式
      • Intent
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档