Java 设计模式——原型模式(Prototype Pattern)

一、简介

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。通熟一点就是对象的复制或克隆,在实现过程中我们也会用到Cloneable这个接口。原型模式根据实现方式的不同,分为浅拷贝和深拷贝.

  1. 浅拷贝:对象拷贝时只拷贝基本数据类型(String,Integer等)变量,不拷贝引用类型变量。
  2. 深拷贝:相对于浅拷贝,除了基本数据类型变量外,还拷贝引用类型变量。但引用类型变量里还存在新的引用类型,需要拷贝到哪一级需要根据需求自己判断。

克隆说白了就是对象的复制,复制后的对象和原对象应该存在以下关系条件:

  1. 对任何对象x,都有:x.clone()!=x。克隆对象与原对象不是同一个对象。
  2. 对任何对象x,都有:x.clone().getClass() == x.getClass(),克隆对象与原对象的类型一样。
  3. 对任何对象x,x.clone().equals(x) 不一定为true。
  4. 对任何对象x,都有:x.clone() 字段数据和x字段数据是"相同"的,注意这个相同打了引号,只是数值层面上的相同。

注意:equals() 方法默认是和 == 相同的,这说明在不重写equals()方法的情况下,默认只要两个对象引用地址相同,equals()方法就会返回true。如果重写了equals(),那就只能取决于实现方式了。

 

优点:

  1. 可以简化创建过程,避开构造函数的约束,同时也能够提高效率。
  2. 可以使用深克隆保持原型对象的数据。
  3. 原型模式提供了简化的创建结构。 

缺点:

  1. 深度克隆的时候可能代码会比较复杂。
  2. 深度克隆需要对每个类进行处理。

二、浅克隆 

假如现有一个复杂数据对象,我们需要在原数据的基础上做一些小的修改,而且还需要能查看原数据对象的数据,如果是new是不是很痛苦,你还得把原数据对象的数据一个一个的set到新的对象,但是如果我们直接clone呢,是不是so  easy。下面来试试:

有两个类,Team和Person,Team引用Person列表,都继承Cloneable且重写简单的clone方法

public class Team implements Cloneable {

    private Integer id;
    private String name;
    private Person leader;
    private List<Person> members;

    public Team(Integer id, String name, Person leader, List<Person> members) {
        this.id = id;
        this.name = name;
        this.leader = leader;
        this.members = members;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Person getLeader() {
        return leader;
    }

    public void setLeader(Person leader) {
        this.leader = leader;
    }

    public List<Person> getMembers() {
        return members;
    }

    public void setMembers(List<Person> members) {
        this.members = members;
    }

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

public class Team implements Cloneable {

    private Integer id;
    private String name;
    private Person leader;
    private List<Person> members;

    public Team(Integer id, String name, Person leader, List<Person> members) {
        this.id = id;
        this.name = name;
        this.leader = leader;
        this.members = members;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Person getLeader() {
        return leader;
    }

    public void setLeader(Person leader) {
        this.leader = leader;
    }

    public List<Person> getMembers() {
        return members;
    }

    public void setMembers(List<Person> members) {
        this.members = members;
    }

    @Override
    protected Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}



    /**
     * 创建新的Team对象并初始化数据
     *
     * @return
     */
    private static Team createOriginalPerson() {
        Person leader = new Person("Original Team Leader");
        List<Person> members = new ArrayList<Person>();
        members.add(new Person("Original Team Member 1"));
        Team originalTeam = new Team(1, "Original Team", leader, members);
        return originalTeam;
    }

    /**
     * 打印对比结果
     * @param originalTeam
     * @param cloneTeam
     */
    private static void printCompare(Team originalTeam, Team cloneTeam) {
        System.out.println("cloneTeam == originalTeam:" + (cloneTeam == originalTeam));
        System.out.println("cloneTeam.getId() == originalTeam.getId():" + (cloneTeam.getId() == originalTeam.getId()));
        System.out.println("cloneTeam.getName() == originalTeam.getName():" + (cloneTeam.getName() == originalTeam.getName()));
        System.out.println("cloneTeam.getLeader() == originalTeam.getLeader():" + (cloneTeam.getLeader() == originalTeam.getLeader()));
        System.out.println("cloneTeam.getMembers() == originalTeam.getMembers():" + (cloneTeam.getMembers() == originalTeam.getMembers()));
    }

接下来我们创建一个Team对象,并初始化一些数据,然后再从这个对象clone一个新对象,注意文明调用的是简单的clone方法没有进行重写,看看会得到怎样一个新对象:

        Team originalTeam = createOriginalPerson();
        //super.clone() 浅复制
        Team cloneTeam = originalTeam.clone();
        printCompare(originalTeam,cloneTeam);

输出结果:

cloneTeam == originalTeam:false
cloneTeam.getId() == originalTeam.getId():true
cloneTeam.getName() == originalTeam.getName():true
cloneTeam.getLeader() == originalTeam.getLeader():true
cloneTeam.getMembers() == originalTeam.getMembers():true
cloneTeam.getTags() == originalTeam.getTags():true

从输出结果我们可以看出,cloneTeam == originalTeam:false表示通过clone()之后,确实生成了两个不同的对象,对象里面的引用似乎还是原来的对象。我们再来做进一步的验证一下是否是燕来的引用对象,修改cloneTeam引用对象的值,看看originalTeam里面引用(字段)对象的值是否发生变化,如果是同一个引用对象,修改cloneTeam中引用对象的值,originalTeam中引用对象的值肯定会发生变化,如果不是则不会发生变化。修改cloneTeam的值:

    /**
     * 修改克隆对象的值
     *
     * @param cloneTeam
     */
    private static void modifyCloneTeam(Team cloneTeam) {
        List<Person> cloneTeamMembers = cloneTeam.getMembers();
        Person cloneTeamLeader = cloneTeam.getLeader();
        String[] tags = cloneTeam.getTags();
        //修改原始数据,对比cloneTeam和originalTeam的数据
        cloneTeam.setId(2);
        cloneTeam.setName("Clone Team");
        cloneTeamLeader.setName("Clone Team Leader");
        //修改cloneTeamMembers
        cloneTeamMembers.add(new Person("Clone Team new member"));
        cloneTeamMembers.get(0).setName("Clone Team Member");
        tags[0] = "Clone Tag 1";
    }

    public static void main(String[] args) {
        System.out.println("==============Simple clone start===============");
        //Init data
        Team originalTeam = createOriginalPerson();
        //输出原始数据
        //super.clone() 浅复制
        Team cloneTeam = originalTeam.clone();
        printCompare(originalTeam, cloneTeam);
        System.out.println("Original team :" + originalTeam);
        //修改原生数据
        modifyCloneTeam(cloneTeam);
        //输出修改后的数据
        System.out.println("After modify clone team, Original Team:" + originalTeam);
        System.out.println("After modify clone team, Clone Team:" + cloneTeam);
        System.out.println("==============Simple clone end===============\n\n");

    }
 

输出结果:

==============Simple clone start===============
cloneTeam == originalTeam:false
cloneTeam.getId() == originalTeam.getId():true
cloneTeam.getName() == originalTeam.getName():true
cloneTeam.getLeader() == originalTeam.getLeader():true
cloneTeam.getMembers() == originalTeam.getMembers():true
cloneTeam.getTags() == originalTeam.getTags():true
Original team :Team{id=1, name='Original Team', tags=[Original Tag 1], leader=Person{name='Original Team Leader'}, members=[Person{name='Original Team Member 1'}]}
After modify clone team, Original Team:Team{id=1, name='Original Team', tags=[Clone Tag 1], leader=Person{name='Clone Team Leader'}, members=[Person{name='Clone Team Member'}, Person{name='Clone Team new member'}]}
After modify clone team, Clone Team:Team{id=2, name='Clone Team', tags=[Clone Tag 1], leader=Person{name='Clone Team Leader'}, members=[Person{name='Clone Team Member'}, Person{name='Clone Team new member'}]}
==============Simple clone end===============

在代码中,修改了cloneTeam中每个引用的值,在List<Person>引用变量中,不仅修改了原有的Person对象的值,还新增了一个新对象,对比一下数据,在修改cloneTeam对象后,原对象originalTeam中除字段id和name之外的数据都发生了变化,变成cloneTeam修改之后的数据了,为什么呢,再看看字段数据类型,id和name分别是Integer和String类型(基础数据类型)其它的字段都是引用数据类型,注意tags字段是String[]也属于引用数据类型。

结论:浅克隆(也就是直接调用Object的clone()方法)得到的克隆对象中,能克隆(产生新的对象)的只有基本数据类型,而引用数据类型不会被克隆,克隆新生成的对象中继续引用源对象里的对象。

思考:为什么基本数据类型 cloneTeam.getName() == originalTeam.getName()返回的是true呢,这就要从Java中数据的存储方式说起,Java中基本数据类型存储在栈中,而引用数据类型存储在堆中,引用数据类型的引用也存储在栈中。如果基本数据类型如:int a =3; int b = 3;指向的是同一个地址,所以返回true。更深入的了解,自行Google吧。

三、深克隆

继续上面的问题,我们使用浅克隆的结果是可能有多个对象同时引用同一个引用变量,如果其中一个对象修改了引用变量中的值,其它变量的值也会受到影响,在某些情况下这显然不满足我们的需求,那么怎样才能满足我们的需求呢,那么我们就需要进行深克隆。深克隆需要我们重写clone()方法,这里便于区分,我使用deepClone()替代。

首先,我们重写Team的clone 方法:

Team.java

/**
     * 深度克隆,只是demo,暂时没做非空判断,优秀的代码,是需要时刻考虑异常情况的
     *
     * @return
     */
    public Team deepClone() {
        try {
            Team team = (Team) super.clone();
            Person cloneLeader = leader.clone();
            team.setLeader(cloneLeader);
            List<Person> cloneMembers = new ArrayList<Person>();
            for (Person person : members) {
                cloneMembers.add(person.clone());
            }
            team.setMembers(cloneMembers);
            String[] cloneTags = new String[tags.length];
            System.arraycopy(tags, 0, cloneTags, 0, tags.length);
            team.setTags(cloneTags);
            return team;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

同之前操作,继续修改cloneTeam参数,并打印输出:

    public static void main(String[] args) {
        //Init data
        Team originalTeam = createOriginalPerson();
        //输出原始数据
        System.out.println("==============Deep clone start===============");

        //super.clone() 浅复制
        Team cloneTeam = originalTeam.deepclone();
        printCompare(originalTeam, cloneTeam);
        System.out.println("Original team :" + originalTeam);
        //修改原生数据
        modifyCloneTeam(cloneTeam);
        //输出修改后的数据
        System.out.println("After modify clone team, Original Team:" + originalTeam);
        System.out.println("After modify clone team, Clone Team:" + cloneTeam);
        System.out.println("==============Deep clone end===============\n\n");

    }

输出结果为:

==============Deep clone start===============
cloneTeam == originalTeam:false
cloneTeam.getId() == originalTeam.getId():true
cloneTeam.getName() == originalTeam.getName():true
cloneTeam.getLeader() == originalTeam.getLeader():false
cloneTeam.getMembers() == originalTeam.getMembers():false
cloneTeam.getTags() == originalTeam.getTags():false
Original team :Team{id=1, name='Original Team', tags=[Original Tag 1], leader=Person{name='Original Team Leader'}, members=[Person{name='Original Team Member 1'}]}
After modify clone team, Original Team:Team{id=1, name='Original Team', tags=[Original Tag 1], leader=Person{name='Original Team Leader'}, members=[Person{name='Original Team Member 1'}]}
After modify clone team, Clone Team:Team{id=2, name='Clone Team', tags=[Clone Tag 1], leader=Person{name='Clone Team Leader'}, members=[Person{name='Clone Team Member'}, Person{name='Clone Team new member'}]}
==============Deep clone end===============

由此可见,在我们重写了clone()之后,引用对象的 == 都为false了,修改cloneTeam的数据后,也不会再影响originalTeam的数据了。其实从代码里面也可以看出,在deepClone()中,我们相当于为每个引用类型的字段重新创建了一个对象,只是考虑到字段庞大的情况下,我们还是继续采用clone()来处理,不适用new,如果你适用new还是能达到同样的效果的,但是如果字段多的话,工作量可想而知。同理,如果在引用对象Person中又存在其它的引用对象呢?也需要同样的操作。

结论:深克隆能保证源对象和克隆对象之间数据的独立性,但是需要引用类型字段做单独的克隆处理,代码复杂度也会随之增加。

最后,附上完整的Demo源码地址供大家参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值