当前位置:首页>编程知识库>后端开发知识>设计模式是什么鬼(原型)
设计模式是什么鬼(原型)
阅读 1
2018-07-24
点击下方阅读原文,查看更多关于设计模式的文章
原型(Prototype)是什么意思?工业生产中通常是指在量产之前研发出的概念实现,如果可行性满足即可参照原型进行量产。有人说了,那不就是印章?其实这并不怎么贴切,印章并不是最终实例,我更愿意称其为“类”!
呃……僵尸脸花泽类经世名言:想哭的时候就道理,这样眼泪就不会流出来了。(尼玛,都流脑子里了吧!)
言归正传,大家一定见过这种印章吧,就是皮带轮可以转动,可随意调整成自己需要的文字,其实跟我们的四大发明活字印刷同出一辙,我们填完表格签好字,行政人员拿这个往上一盖,一个日期便出现在落款出。
其实当行政人员调整好了文字,照纸上盖下去那一刹那,其实就类似于实例化的过程了,new Stamp();每个盖出的印都可以不一样,例如我们更换了日期,那么每天都有不同日期的实例了,那有人意识到了,同一天的那些实例们,其实是完全一模一样的实例拷贝,那这就比较麻烦,每个文档都要用章子(类)去盖(实例化)一下。
好了,让我们忘掉盖章实例化模式吧。通常我们都是怎样做协议书的呢?搞一个Word文档吧,写好后复制给别人修改就好了。
注意了,行政人员要新建一个word文档了,这个过程其实是在实例化,我们暂且叫它“零号”文件,那当写好了文档后,把这个文件复制给其他公司员工去填写,那么这个零号文件我们就称之为“原型”。
想必我们已经搞明白了,原型模式,实际上是从原型实例复制克隆出新实例,而绝不是从类去实例化,这个过程的区别一定要搞清楚!OK,那开始我们的实战部分。
假设我们要做一个打飞机游戏,游戏设定位纵版移动,单打。
既然是单打,那我们的主角飞机当然只有一架,于是我们写一个单例模式(设计模式是什么鬼(单例)),此处我们省略主角代码。那么敌机呢?当然有很多架了,好,为了说明问题我们去繁就简,先写一个敌机类。
%201public%20class%20EnemyPlane%20%7B%0A%202%20%20%20%20private%20int%20x;//%E6%95%8C%E6%9C%BA%E6%A8%AA%E5%9D%90%E6%A0%87%0A%203%20%20%20%20private%20int%20y%20=%200;//%E6%95%8C%E6%9C%BA%E7%BA%B5%E5%9D%90%E6%A0%87%0A%204%0A%205%20%20%20%20public%20EnemyPlane(int%20x)%20%7B//%E6%9E%84%E9%80%A0%E5%99%A8%0A%206%20%20%20%20%20%20%20%20this.x%20=%20x;%0A%207%20%20%20%20%7D%0A%208%0A%209%20%20%20%20public%20int%20getX()%20%7B%0A10%20%20%20%20%20%20%20%20return%20x;%0A11%20%20%20%20%7D%0A12%0A13%20%20%20%20public%20int%20getY()%20%7B%0A14%20%20%20%20%20%20%20%20return%20y;%0A15%20%20%20%20%7D%0A16%0A17%20%20%20%20public%20void%20fly()%7B//%E8%AE%A9%E6%95%8C%E6%9C%BA%E9%A3%9E%0A18%20%20%20%20%20%20%20%20y ;//%E6%AF%8F%E8%B0%83%E7%94%A8%E4%B8%80%E6%AC%A1%EF%BC%8C%E6%95%8C%E6%9C%BA%E9%A3%9E%E8%A1%8C%E6%97%B6%E7%BA%B5%E5%9D%90%E6%A0%87%EF%BC%8B1%0A19%20%20%20%20%7D%0A20%7D%0A
代码第5行,初始化只接收x坐标,因为敌机一开始是从顶部出来所以纵坐标y必然是0。此类只提供getter而没有setter,也就是说只能在初始化时确定敌机的横坐标x,后续是不需要更改坐标了,只要连续调用第17行的fly方法即可让飞机跟雨点一样往下砸。
好了,我们开始绘制敌机动画了,先实例化出50架吧。
%201public%20class%20Client%20%7B%0A%202%20%20%20%20public%20static%20void%20main(String%5B%5D%20args)%20%7B%0A%203%20%20%20%20%20%20%20%20List<EnemyPlane>%20enemyPlanes%20=%20new%20ArrayList<EnemyPlane>();%0A%204%0A%205%20%20%20%20%20%20%20%20for%20(int%20i%20=%200;%20i%20<%2050;%20i )%20%7B%0A%206%20%20%20%20%20%20%20%20%20%20%20%20//%E6%AD%A4%E5%A4%84%E9%9A%8F%E6%9C%BA%E4%BD%8D%E7%BD%AE%E4%BA%A7%E7%94%9F%E6%95%8C%E6%9C%BA%0A%207%20%20%20%20%20%20%20%20%20%20%20%20EnemyPlane%20ep%20=%20new%20EnemyPlane(new%20Random().nextInt(200));%0A%208%20%20%20%20%20%20%20%20%20%20%20%20enemyPlanes.add(ep);%0A%209%20%20%20%20%20%20%20%20%7D%0A10%0A11%20%20%20%20%7D%0A12%7D%0A
注意代码第7行,觉不觉得每个迭代都实例化new出一个对象存在性能问题呢?答案是肯定的,这个实例化的过程是得不偿失的,构造方法会被调用50次,cpu被极大浪费了,内存被极大浪费了,尤其对于游戏来说性能瓶颈绝对是大忌,这会造成用户体验问题,谁也不希望玩游戏会卡帧吧。
那到底什么时候去new?游戏场景初始化就new敌机(如以上代码)?这关会出现500个敌机那我们一次都new出来吧?浪费内存!那我们实时的去new,每到一个地方才new出来一个!浪费CPU!如果敌机线程过多造成CPU资源耗尽,每出一个敌机游戏会卡一下,试想一下这种极端情况下,游戏对象实例很多的话就是在作死。
解决方案到底是什么呢?好,原型模式Prototype!上代码!我们把上面的敌机类改造一下,让它支持原型拷贝。
%201public%20class%20EnemyPlane%20implements%20Cloneable%7B//%E6%AD%A4%E5%A4%84%E5%AE%9E%E7%8E%B0%E5%85%8B%E9%9A%86%E6%8E%A5%E5%8F%A3%0A%202%20%20%20%20private%20int%20x;//%E6%95%8C%E6%9C%BA%E6%A8%AA%E5%9D%90%E6%A0%87%0A%203%20%20%20%20private%20int%20y%20=%200;//%E6%95%8C%E6%9C%BA%E7%BA%B5%E5%9D%90%E6%A0%87%0A%204%0A%205%20%20%20%20public%20EnemyPlane(int%20x)%20%7B//%E6%9E%84%E9%80%A0%E5%99%A8%0A%206%20%20%20%20%20%20%20%20this.x%20=%20x;%0A%207%20%20%20%20%7D%0A%208%0A%209%20%20%20%20public%20int%20getX()%20%7B%0A10%20%20%20%20%20%20%20%20return%20x;%0A11%20%20%20%20%7D%0A12%0A13%20%20%20%20public%20int%20getY()%20%7B%0A14%20%20%20%20%20%20%20%20return%20y;%0A15%20%20%20%20%7D%0A16%0A17%20%20%20%20public%20void%20fly()%7B//%E8%AE%A9%E6%95%8C%E6%9C%BA%E9%A3%9E%0A18%20%20%20%20%20%20%20%20y ;//%E6%AF%8F%E8%B0%83%E7%94%A8%E4%B8%80%E6%AC%A1%EF%BC%8C%E6%95%8C%E6%9C%BA%E9%A3%9E%E8%A1%8C%E6%97%B6%E7%BA%B5%E5%9D%90%E6%A0%87%EF%BC%8B1%0A19%20%20%20%20%7D%0A20%0A21%20%20%20%20//%E6%AD%A4%E5%A4%84%E5%BC%80%E6%94%BEsetX%EF%BC%8C%E4%B8%BA%E4%BA%86%E8%AE%A9%E5%85%8B%E9%9A%86%E5%90%8E%E7%9A%84%E5%AE%9E%E4%BE%8B%E9%87%8D%E6%96%B0%E4%BF%AE%E6%94%B9x%E5%9D%90%E6%A0%87%0A22%20%20%20%20public%20void%20setX(int%20x)%20%7B%0A23%20%20%20%20%20%20%20%20this.x%20=%20x;%0A24%20%20%20%20%7D%0A25%0A26%20%20%20%20//%E4%B8%BA%E4%BA%86%E4%BF%9D%E8%AF%81%E9%A3%9E%E6%9C%BA%E9%A3%9E%E8%A1%8C%E7%9A%84%E8%BF%9E%E8%B4%AF%E6%80%A7%0A27%20%20%20%20//%E8%BF%99%E9%87%8C%E6%88%91%E4%BB%AC%E5%85%B3%E9%97%ADsetY%E6%96%B9%E6%B3%95%EF%BC%8C%E4%B8%8D%E6%94%AF%E6%8C%81%E9%9A%8F%E6%84%8F%E6%9B%B4%E6%94%B9Y%E7%BA%B5%E5%9D%90%E6%A0%87%0A28//%20%20%20%20public%20void%20setY(int%20y)%20%7B%0A29//%20%20%20%20%20%20%20%20this.y%20=%20y;%0A30//%20%20%20%20%7D%0A31%0A32%20%20%20%20//%E9%87%8D%E5%86%99%E5%85%8B%E9%9A%86%E6%96%B9%E6%B3%95%0A33%20%20%20%20@Override%0A34%20%20%20%20public%20EnemyPlane%20clone()%20throws%20CloneNotSupportedException%20%7B%0A35%20%20%20%20%20%20%20%20return%20(EnemyPlane)super.clone();%0A36%20%20%20%20%7D%0A37%7D%0A
注意看从第21行开始的修改,setX()方法为了保证克隆飞机的个性化,因为它们出现的位置是不同的。第34行的克隆方法重写我们调用了父类Object的克隆方法,这里JVM会进行内存操作直接拷贝原始数据流,简单粗暴,不会有其他更多的复杂操作(类加载,实例化,初始化等等),速度远远快于实例化操作。OK,我们看怎么克隆这些敌机,做一个造飞机的工厂吧。
%201public%20class%20EnemyPlaneFactory%20%7B%0A%202%20%20%20%20//%E6%AD%A4%E5%A4%84%E7%94%A8%E7%97%B4%E6%B1%89%E6%A8%A1%E5%BC%8F%E9%80%A0%E4%B8%80%E4%B8%AA%E6%95%8C%E6%9C%BA%E5%8E%9F%E5%9E%8B%0A%203%20%20%20%20private%20static%20EnemyPlane%20protoType%20=%20new%20EnemyPlane(200);%0A%204%0A%205%20%20%20%20//%E8%8E%B7%E5%8F%96%E6%95%8C%E6%9C%BA%E5%85%8B%E9%9A%86%E5%AE%9E%E4%BE%8B%0A%206%20%20%20%20public%20static%20EnemyPlane%20getInstance(int%20x)%7B%0A%207%20%20%20%20%20%20%20%20EnemyPlane%20clone%20=%20protoType.clone();//%E5%A4%8D%E5%88%B6%E5%8E%9F%E5%9E%8B%E6%9C%BA%0A%208%20%20%20%20%20%20%20%20clone.setX(x);//%E9%87%8D%E6%96%B0%E8%AE%BE%E7%BD%AE%E5%85%8B%E9%9A%86%E6%9C%BA%E7%9A%84x%E5%9D%90%E6%A0%87%0A%209%20%20%20%20%20%20%20%20return%20clone;%0A10%20%20%20%20%7D%0A11%7D%0A
此处我们省去抓异常,随后的事情就非常简单了,我们只需要很简单地调用EnemyPlaneFactory.getInstance(int x)并声明x坐标位置,一架敌机很快地就做好了,并且我们保证是在敌机出现的时候再去克隆,确保不要一开局就全部克隆出来,如此一来,既保证了实时性节省了内存空间,又保证了敌机实例化的速度,游戏绝不会卡帧!至于此处代码中的懒汉原型还可以怎样优化那就要根据具体场景了,交给大家自由发挥吧,这里只说明主要问题。
最后,还要强调一点就是浅拷贝和深拷贝的问题。假如我们的敌机类里有一颗子弹bullet可以射击我们的主角,如下。
1public%20class%20EnemyPlane%20implements%20Cloneable%7B%0A2%20%20%20%20private%20Bullet%20bullet%20=%20new%20Bullet();%0A3%20%20%20%20private%20int%20x;//%E6%95%8C%E6%9C%BA%E6%A8%AA%E5%9D%90%E6%A0%87%0A4%20%20%20%20private%20int%20y%20=%200;//%E6%95%8C%E6%9C%BA%E7%BA%B5%E5%9D%90%E6%A0%87%0A5%0A6%20%20%20%20//%E4%B9%8B%E5%90%8E%E4%BB%A3%E7%A0%81%E7%9C%81%E7%95%A5%E2%80%A6%E2%80%A6%0A7%7D%0A
我们都知道Java中的变量分为原始类型和引用类型,所谓浅拷贝只是拷贝原始类型的指,比如坐标x, y的指会被拷贝到克隆对象中,对于对象bullet也会被拷贝,但是请注意拷贝的只是地址而已,那么多个地址其实真正指向的对象还是同一个bullet
由于我们调用父类Objectclone方法进行的是浅拷贝,所以此处的bullet并没有被克隆成功,比如我们每架敌机必须携带的子弹是不同的实例,那么我们就必须进行深拷贝,于是我们的代码就得做这样的改动。
%201public%20class%20EnemyPlane%20implements%20Cloneable%7B%0A%202%20%20%20%20private%20Bullet%20bullet%20=%20new%20Bullet();%0A%203%0A%204%20%20%20%20public%20void%20setBullet(Bullet%20bullet)%20%7B%0A%205%20%20%20%20%20%20%20%20this.bullet%20=%20bullet;%0A%206%20%20%20%20%7D%0A%207%0A%208%20%20%20%20@Override%0A%209%20%20%20%20protected%20EnemyPlane%20clone()%20throws%20CloneNotSupportedException%20%7B%0A10%20%20%20%20%20%20%20%20EnemyPlane%20clonePlane%20=%20(EnemyPlane)%20super.clone();//%E5%85%88%E5%85%8B%E9%9A%86%E5%87%BA%E6%95%8C%E6%9C%BA%EF%BC%8C%E5%85%B6%E4%B8%AD%E5%AD%90%E5%BC%B9%E8%BF%98%E6%9C%AA%E8%BF%9B%E8%A1%8C%E5%85%8B%E9%9A%86%E3%80%82%0A11%20%20%20%20%20%20%20%20clonePlane.setBullet(this.bullet.clone());//%E5%AF%B9%E5%AD%90%E5%BC%B9%E8%BF%9B%E8%A1%8C%E6%B7%B1%E6%8B%B7%E8%B4%9D%0A12%20%20%20%20%20%20%20%20return%20clonePlane;%0A13%20%20%20%20%7D%0A14%0A15%20%20%20%20//%E4%B9%8B%E5%90%8E%E4%BB%A3%E7%A0%81%E7%9C%81%E7%95%A5%E2%80%A6%E2%80%A6%0A16%7D%0A
相信大家看注释就能懂了,这里就不做过多解释,当然对于Bullet类也同样实现了克隆接口,代码不用再写了吧?相信大家都学会了举一反三。至此,我们的每个敌机携带的弹药也同样被克隆完毕了,再也不必担心游戏的流畅性了。
推荐大而全的【后端技术精选】
以上数据来源于网络,如有侵权,请联系删除。
评论 (0)