大话设计模式二十四之享元模式

最近又朋友给小菜介绍了一些小型外包项目,是给一些私营业主做网站。刚开始是为一个客户做一个产品展示的网站,花了一个多星期的时间做好了,也帮客户租用了虚拟空间。然后,他另外的朋友也希望能做这样的网站,然后小菜就又租用了一个空间,把之前的代码复制一份上传了。这好像有点问题。再后来,他们的朋友都希望小菜提供这样的网站,但要求不太一样,有人希望是新闻发布形式的,有人希望是博客形式的,也又还是原来的产品图片加说明形式的,而且都希望在费用上能大大降低。可是每个网站租用一个空间,费用上降低不太可能。该怎么办呢?

他们都是类似的商家客户,要求也就是信息发布、产品展示、博客留言、论坛等功能,要求差别不大。

但是,如果又100家企业来做网站,难道要去申请100个空间,用100个数据库,然后用类似的代码复制100遍去实现吗?如果是这样的话,如果又Bug或是新的需求改动,维护量就太可怕了。


如果是每个网站一个实例,代码应该是这样的



也就是说,如果要做三个产品展示,三个博客的网站,就需要六个网站类的实例,而其实它们本质上都是一样的代码,如果网站增多,实例也就随着增多,这对服务器的资源浪费得很严重。有什么办法解决这个问题?

不能大家的网站公用一套代码,但是毕竟是不同的网站,数据都不相同。

共享代码为什么不可以呢?现在大型的博客网站、电子商务网站,里面每一个博客或商家也可以理解为一个小的网站,但它们是如何做呢?

利用用户ID号的不同,来区分不同的用户,具体数据和模板可以不同,但代码核心和数据库却是共享的。

首先这些企业客户,他们需要的网站结构相似度很高而且都不是哪种高访问量的网站,如果分成多个虚拟空间来处理,相当于一个相同网站的实例对象很多,这时造成服务器的大量资源浪费,当然更实际的其实就是钞票的浪费,如果整合到一个网站中共享其相关的代码和数据,那么对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源,而对于代码,由于是一份实例,维护和扩展都更加容易。



二、享元模式

享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。




FlyweightFactory根据客户需求返回早已生成好的对象,但实际上不一定需要事先生成对象的实例,完全可以初始化时什么也不做,到需要时,再去判断对象是否为null来决定是否实例化。

尽管大部分时间需要共享对象来降低内存的损耗,但个别时候也有可能不需要共享的,那么此时的UnsharedConcreteFlyweight子类就有存在的必要了,它可以解决那些不需要共享对象的问题。


三、网站共享代码

网站应该有一个抽象类和一个具体网站类才可以,然后通过网站工厂来生成对象。





这样基本算是实现了享元模式的共享对象目的,也就是说,不管建几个网站,只要是‘产品展示’,都是一样的,只要是‘博客’,也是完全相同的,但这样是有问题的,给企业建的网站不是一家企业,它们的数据不会相同,所以至少它们都应该有不同的账号。实际上这样写没有体现对象间的不同,只体现了它们共享的部分。


四、内部状态与外部状态

在享元对象内部并且不会随着环境改变而改变的共享部分,可以称为享元对象的内部状态,而随环境改变而改变的、不可以共享的状态就是外部状态了。

享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了结构参数外基本上都是相同的,有时就能够大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来就可以通过共享大幅度地减少单个实例的数目。

也就是说,享元模式Flyweight执行时所需要的状态是有内部的也可能有外部的,内部状态存储于ConcreteFlyweigt对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象的操作时,将该状态传递给它。

客户的账号就是外部状态,应该由专门的对象来处理。




这样就可以协调内部与外部状态了由于用了享元模式,哪怕接收了1000个网站的需求,只要要求相同或类似,你的实际开发代码也就是分类的那几种,对于服务器来说,占用的硬盘空间、内存、CPU资源都是非常少的,这确实是很好的一个方式。


五、享元模式应用

如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。



享元模式达到的效果

因为用了享元模式,所以由了共享对象,实例总数就大大减少了,如果共享的对象越多,存储节约也就越多,节约量随着共享状态的增多而增大。

实际上在.NET中,字符串string就是运用了Flyweight模式。Object.ReferenceEquals(object objA,object objB)方法是用来确定objA与objB是否是相同的实例,返回值为bool值。


返回值是True,这两个字符串是相同的实例。

如果每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存的开销会很大。所以如果第一次创建了字符串对象titleA,下次再创建相同的字符串titleB时只是把它的引用指向‘大话设计模式’,这样就实现了‘大话设计模式’在内存中的共享。

虽说享元模式更多的时候是一种底层的设计模式,但现实中也是有应用的。比如休闲游戏开发中,像围棋、五子棋、跳棋等,它们都有大量的棋子对象,它们的内部状态和外部状态各是什么?

围棋和五子棋只有黑白两色、跳棋颜色略多一些,但也是不太变化的,所以颜色应该是棋子的内部状态,而各棋子的差别主要就是位置的不同,所以方位坐标应该是棋子的外部状态。

像围棋,一盘棋理论上有361个空位可以放棋子,那如果用常规的面向对象方式编程,每盘棋都可能有两三百个棋子对象产生,一台服务器就很难支持更多的玩儿家玩围棋游戏了,毕竟内存空间还是有限的。如果用了享元模式来处理棋子,那么棋子对象可以减少到只有两个实例。这的确是非常好地解决了对象的开销问题。

在某些情况下,对象的数量可能会太多,从而导致了运行时的资源与性能损耗。那么该如何避免大量细粒度的对象同时又不影响客户程序,是一饿值得去思考的问题,享元模式,可以运行共享技术有效地支持大量细粒度的对象。不过,使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。




猜你喜欢

转载自blog.csdn.net/nicolelili1/article/details/80194667