【设计模式-2】原型模式的原理、代码实现及类图展示

 我们一定对类的实例化比较熟悉,前面我们说的单例、还有3种工厂模式都是通过new关键字来创建对象,下面我们来了解一种新的对象创建的方式。

1. 定义

 原型模式也是一种创建型的设计模式,实现和原理总体比较简单,一句话总结呢,就是可以实现用已有的对象创建新的对象,而不是用类来实例化对象,这样可以起到提升效率的目的。

 众所周知,类的实例化可以创建对象,但其实这是一个比较耗时耗力的工作,尤其是在大量实例化对象的业务场景下,可能会对系统的性能造成很大的影响。这时候,原型模式就可以很好的解决问题,用已有的对象来生成对象的副本,这里已有的对象就是原型对象,副本对象就是拷贝对象。这样对于那些有非常复杂的初始化的操作,或者是需要消耗大量资源的情况,原型模式的优势就体现出来了。

2. 代码实现

 我们来举一个飞机大战的游戏例子,游戏的场是在手机屏幕上方,飞下来很多敌机,而我方战机只有一架,其中敌机的飞行轨迹必须是呈上下直线型的,我方战机可以上下左右移动。因为本篇我们学习原型模式,所以重点关照的是敌机,我方战机其实可以通过前面讲的单例模式实现。

2.1 实例化方式

 假设游戏过程有500架敌机出现,通常情况下,使用实例化创建对象的方式,代码实现可以这样。

public class EnemyPlain {
    // x坐标
    private int x;
    // y坐标
    private int y;
    // 敌机固定x坐标,只能上下移动
    public EnemyPlain(int x) {
        this.x = x;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
    // 手柄每调用一次setY方法,Y坐标加一
    public void setY() {
        y ++ ;
    }
}

 客户端获取敌机的方法如下:

public class Client {
    public static void main(String[] args) {
        // 500架敌机集合
        List<EnemyPlain> enemyPlains = new ArrayList<>();
        // 实例化500架敌机
        for (int i = 0; i < 500; i++) {
            // 随机出现在0~200的坐标内
            EnemyPlain enemyPlain = new EnemyPlain(RandomUtil.randomInt(200));
            enemyPlains.add(enemyPlain);
        }
    }
}

 这种代码实现的方式是很常见的,但要命的是这500个对象在客户端初始化的时候就会被创建出来,500个对象会占用大量的堆内存空间,这还是定义的对象只有两个属性的前提下。另外,CPU本身就是很宝贵的资源,一次性实例化500个对象,本身也会消耗系统很大的系统资源,极端的情况下会造成游戏界面卡顿,造成不友好的用户体验,下面我们用原型模式来试一下。

2.2 原型模式

 原型模式的代码实现,首先把原型类实现java.lang.Clone接口,接着实现 clone()方法。

// 1.实现java.lang.Clone接口
public class EnemyPlain implements Cloneable {
    // x坐标
    private int x;
    // y坐标
    private int y;
    // 敌机固定x坐标,只能上下移动
    public EnemyPlain(int x) {
        this.x = x;
    }
    public int getX() {
        return x;
    }
    public int getY() {
        return y;
    }
    // 手柄每调用一次setY方法,Y坐标加一
    public void setY() {
        y ++ ;
    }
    public void setX(int x) {
        this.x = x;
    }
    // 重写克隆 clone 方法
    @Override
    public EnemyPlain clone() throws CloneNotSupportedException {
        return (EnemyPlain)super.clone();
    }
}

 客户端获取原型拷贝副本代码实现:

public class ClientAno {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建原型对象
        EnemyPlain enemyPlain = new EnemyPlain(100);
        // 存放500架敌机
        List<EnemyPlain> list = new ArrayList<>();
        // 克隆500架敌机
        for (int i = 0; i < 500; i++) {
            EnemyPlain clonePlain = enemyPlain.clone();
            // 设置横坐标
            clonePlain.setX(RandomUtil.randomInt(200));
            list.add(clonePlain);
        }
    }
}

 这里需要特别说明,clone()方法并不是从Cloneable接口实现来的,而是继承自java.lang.Object对象。另外,一般在获取克隆对象的时候,可以借助工厂模式一块实现。

3. UML类图

 下面,就以上面飞机大战这个游戏的这个例子,画一个原型模式的UML类图。

在这里插入图片描述

4. 深拷贝和浅拷贝

 想使用原型模式,就必须涉及到深拷贝和浅拷贝这两个概念,也是我们在开发中使用这个设计模式最容易出问题的地方,那这两者有什么区别呢?

 Java中的变量分为原始类型和引用类型的,浅拷贝只会拷贝原始类型的值,引用类型的值只是拷贝了引用的地址,而深拷贝则会拷贝原始类型的值,并且给引用类型的属性从新克隆一个新的对象,这个对象在堆中地址都会不一样了。我们来举个例子,有a这个原型对象,克隆了b这个副本对象,两个对象都有同一个 Date 类型的属性,如果修改了a对象的 x 属性的值,那么b对象的这个值也会被改变,这在某些场景中可能会出问题,所以要提防这个坑。使用深拷贝,可以参考hutool工具包的api,方便我们开发。

在这里插入图片描述

5. 总结

 最后来一点总结:从类创建对象叫做实例化,从对象生成对象叫做克隆。究其本质,克隆操作时,java虚拟机会进行内存操作,通过拷贝原型对象的数据流生成新的副本对象,就不会触发一些复杂的操作(比如类加载、初始化等),所以效率是远远高于通过new关键字所触发的实例化操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值