设计模式之原型模式

公司做游戏了

小明的公司赚的钱越来越多了,他们开始尝试新的机会,公司觉得游戏是一个不错的选择,所以公司准备做游戏了

小明负责游戏中的一个模块,这个模块是负责设计游戏中的各种小怪角色,小明拿觉得这个很简单啊,于是根据游戏策划对小怪的设计,开始写代码了:

class Monster {
    String color;
    int healthValue;

    public Monster(String color, int healthValue) {
        this.color = color;
        this.healthValue = healthValue;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getHealthValue() {
        return healthValue;
    }

    public void setHealthValue(int healthValue) {
        this.healthValue = healthValue;
    }
}

生成怪兽并使用

public class Client {
    public static void main(String[] args) {
        Monster monster = new Monster("green", 100);
        int i = 1;
        while (i < 10) {
            if (i % 2 == 0)
                monster.setColor("red");
            else
                monster.setColor("blue");
            monster.setHealthValue(i * 200);
            System.out.print(monster);
            i++;
        }
    }
}

主管说,你现在只实例化了10个怪兽,我现在需要100万个小怪兽,让游戏有一种特殊的效果,你的方式还可以吗?小明想了一下,如果100万个的话,这样效率太低了,小明想那我们用多线程吧,主管说,多线程就必须每次都生成新的对象,不能像上面那样共用一个对象,这又是一笔额外的开支,所以我们想想可以在生成对象的时候提高一下效率吗?小明问,可以吗?主管说,当然,这种情况我们使用原型模式再适合不过了,我们改造一下Monster:

public class Monster implements Cloneable {

    String color;
    int healthValue;

    public Monster(String color, int healthValue) {
        this.color = color;
        this.healthValue = healthValue;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getHealthValue() {
        return healthValue;
    }

    public void setHealthValue(int healthValue) {
        this.healthValue = healthValue;
    }

    @Override
    protected Object clone(){
        Monster monster = null;
        try {
            monster = (Monster) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return monster;
    }
}

可以看到,我们的Monster实现了Cloneable接口,并重载了clone()方法,然后我们使用新的怪兽类

public class Client {
    public static void main(String[] args) {
        Monster monster = new Monster("green", 100);
        int i = 1;
        while (i <= 1000000) {
            Monster cloneMonster = monster.clone();
            if (i % 2 == 0)
                cloneMonster.setColor("red");
            else
                cloneMonster.setColor("blue");
            cloneMonster.setHealthValue(i * 200);
            System.out.print(cloneMonster);
            i++;
        }
    }
}

可以看到,我们用了clone方法来生成新的对象,这样做有什么好处呢,如果之前那种方式在我们的while中有耗时操作的话,实例化的过程效率很低,如果我们实现Cloneable接口,我们可以通过clone方法来生成对象,它是一个本地方法,直接在内存中再复制一份我们对象的二进制生成一个新的对象,而不是通过再使用构造函数去生成,这样效率会提升很多。所以在需要重复地创建相似对象时可以考虑使用原型模式。比如需要在一个循环体内创建对象,假如对象创建过程比较复杂或者循环次数很多的话,使用原型模式不但可以简化创建过程,而且可以使系统的整体性能提高很多

原型模式

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

原型模式

使用原型模式的注意事项

使用原型模式复制对象不会调用类的构造方法

因为对象的复制是通过调用Object类的clone方法来完成的,它直接在内存中复制数据,因此不会调用到类的构造方法。不但构造方法中的代码不会执行,甚至连访问权限都对原型模式无效。还记得单例模式吗?单例模式中,只要将构造方法的访问权限设置为private型,就可以实现单例。但是clone方法直接无视构造方法的权限,所以,单例模式与原型模式是冲突的,在使用时要特别注意

深拷贝与浅拷贝

浅拷贝

被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝,然后一直基本数据类型会被拷贝

深拷贝

被拷贝对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍

明白了深拷贝和浅拷贝,我们在使用clone方法的时候就必须注意,因为默认的clone方法是浅拷贝,对于一些引用对象不会拷贝为新的对象,所以在需要时,我们必须对对象引用再做一次拷贝:

public class Monster implements Cloneable {

    String color;
    int healthValue;
    ArrayList<String> skills;

    @Override
    protected Monster clone() {
        Monster monster = null;
        try {
            monster = (Monster) super.clone();
            monster.skills = (ArrayList<String>) this.skills.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return monster;
    }
}

深拷贝与浅拷贝问题中,会发生深拷贝的有java中的8中基本类型以及他们的封装类型,另外还有String类型。其余的都是浅拷贝

坚持原创分享,您的支持将鼓励我不断前行!