今天一块儿来看一个新的设计模式,那就是享元模式,关于此模式,常见的就是 “项目外包”、
以及 “五子棋” 这样两个例子,咱们下面就选择使用 “项目外包” 这个例子引入去讲java
程序员小B,帮助客户 A 作了一个展现一些产品内容的网站,经过 A 的 推荐,客户 B 、客户C 也想要作这样一个网站,可是就是形式有一些变化程序员
并且他们都但愿可以下降一些费用,可是每个空间部署着一个网站,因此租借空间的费用是固定的,同时程序员小B 并不想从本身的劳动报酬中缩减费用web
先说最简单能想到的方案,直接把网站代码复制几份,而后每个都租借一个空间,而后对代码进行定制修改。注:这里还没考虑优化或者省钱设计模式
咱们用一个 WebSite 类来模拟一个网站的模板,全部类型能够经过对 name 赋值而后调用 use 方法进行修改缓存
public class WebSite { private String name = ""; public WebSite(String name) { this.name = name; } public void use(){ System.out.println("当前网站分类: " + name); } }
若是按照刚才的思路,是这样操做的服务器
public class Test { public static void main(String[] args) { WebSite webSite1 = new WebSite("博客"); webSite1.use(); WebSite webSite2 = new WebSite("博客"); webSite2.use(); WebSite webSite3 = new WebSite("博客"); webSite3.use(); WebSite webSite4 = new WebSite("新闻发布"); webSite4.use(); WebSite webSite5 = new WebSite("公众号"); webSite5.use(); WebSite webSite6 = new WebSite("公众号"); webSite6.use(); } }
运行结果:数据结构
当前网站分类: 博客
当前网站分类: 博客
当前网站分类: 博客
当前网站分类: 新闻发布
当前网站分类: 公众号
当前网站分类: 公众号ide
① 假设虚拟空间在同一台服务器上,作上述内容,须要实例化 6 个 WebSite,而其本质又没有很大的差异,因此对于服务器的资源浪费很大测试
② 网站结构类似度很高,基本全是重复的代码优化
对于这种重复性很高的内容,首先咱们要作到将其抽象出来,重复建立实例在设计模式中确定是不太明智的,咱们想要作到多个客户,共享同一个实例。这样不论是代码仍是服务器资源利用,都会改善不少
一个不算特别恰当的例子:例如外卖平台中的一个一个商家店铺,是否是能够理解为平台中的一个小店铺,小网站,其中经过例如店铺 ID 等内容来区分不一样店铺,可是其每一家店铺总体的模板和样子是差很少的。
咱们下面要作的就是,将大量类似内容抽象成一个网站模板类,而后把一些特定的内容,经过参数移到实例的外面,调用的时候再指定,这样能够大幅度减小单个实例的数目。
建立一个抽象的 WebSite 类
public abstract class WebSite { public abstract void use(); }
接下来是具体实现,建立其子类,和前面同样,全部类型能够经过对 type赋值而后调用 use 方法进行修改
public class ConcreteWebSite extends WebSite { // 网站发布形式 private String type = ""; public ConcreteWebSite(String type) { this.type = type; } @Override public void use() { System.out.println("当前网站分类: " + type); } }
建立一个工厂类,用于建立,返回一个指定的网站实例
这一个类,首先用一个 HashMap 模拟一种链接池的概念,由于咱们既然想要达到不重复建立实例的效果,就须要经过一些逻辑判断,判断 Map 中是否存在这个实例,若是有就直接返回,若是没有就建立一个新的,一样类型 type 是在调用时,显式的指定的。
后面补充了一个获取网站分类总数的方法,用来测试的时候,看一下是否是没有重复建立实例
import java.util.HashMap; /** * 网站工厂类,根据须要返回 */ public class WebSiteFactory { // 模拟一个链接池 private HashMap<String, ConcreteWebSite> pool = new HashMap<>(); /** * 获取网站:根据传入的类型,返回网站,无则建立,有则直接返回 * * @param type * @return */ public WebSite getWebSiteCategory(String type) { if (!pool.containsKey(type)) { // 建立一个网站,放到池种 pool.put(type, new ConcreteWebSite(type)); } return (WebSite) pool.get(type); } /** * 获取网站分类总数 */ public int getWebSiteCount() { return pool.size(); } }
测试一下
public class Test { public static void main(String[] args) { // 建立一个工厂 WebSiteFactory factory = new WebSiteFactory(); // 给客户建立一个博客类型的网站 WebSite webSite1 = factory.getWebSiteCategory("博客"); webSite1.use(); // 给客户建立一个博客类型的网站 WebSite webSite2 = factory.getWebSiteCategory("博客"); webSite2.use(); // 给客户建立一个博客类型的网站 WebSite webSite3 = factory.getWebSiteCategory("博客"); webSite3.use(); // 给客户建立一个新闻发布类型的网站 WebSite webSite4 = factory.getWebSiteCategory("新闻发布"); webSite4.use(); // 给客户建立一个公众号类型的网站 WebSite webSite5 = factory.getWebSiteCategory("公众号"); webSite5.use(); // 给客户建立一个公众号类型的网站 WebSite webSite6 = factory.getWebSiteCategory("公众号"); webSite6.use(); // 查看一下链接池中的实例数 System.out.println("实例数:" + factory.getWebSiteCount()); } }
运行结果:
当前网站分类: 博客
当前网站分类: 博客
当前网站分类: 博客
当前网站分类: 新闻发布
当前网站分类: 公众号
当前网站分类: 公众号
实例数:3
上面的代码,使用工厂代替了直接实例化的方式,工厂中,主要经过一个池的概念,实现了共享对象的目的,可是其实咱们会发现,例如建立三个博客类型的网站,可是好像这三个网站就是如出一辙的,可是不一样的客户,其中博客网站中的数据确定是不一样的,这就是咱们尚未区份内部外部的状态
内部状态:对象共享出来的信息,存储在享元对象内部而且不会随环境改变的共享部分
外部状态:对象用来标记的一个内容,随环境会改变,不可共享
打个比方,五子棋只有黑白两色,总不能下多少子,就建立多少个实例吧,因此咱们把颜色看作内部状态,有黑白两种颜色。而各个棋子的位置并不相同,当咱们落子后这个位置信息才会被传入,因此位置信息就是外部状态
那么对于“外包网站”的例子中,很显然,不一样的客户网站数据就是一个外部状态,下面来修改一下
首先新增一个 User 类,后面会将其引入做为外部状态
public class User { private String name; public User(String name) { this.name = name; } public String getName() { return name; } }
修改抽象类和子类,经过参数的方式引入 User 这个外部状态
抽象类
public abstract class WebSite { public abstract void use(User user); }
子类
public class ConcreteWebSite extends WebSite { // 网站发布形式 private String type = ""; public ConcreteWebSite(String type) { this.type = type; } @Override public void use(User user) { System.out.println("【网站分类】: " + type + " 【客户】: " + user.getName()); } }
工厂类不变,最后修改测试类
public class Test { public static void main(String[] args) { // 建立一个工厂 WebSiteFactory factory = new WebSiteFactory(); // 给客户建立一个博客类型的网站 WebSite webSite1 = factory.getWebSiteCategory("博客"); webSite1.use(new User("客户A")); // 给客户建立一个博客类型的网站 WebSite webSite2 = factory.getWebSiteCategory("博客"); webSite2.use(new User("客户B")); // 给客户建立一个博客类型的网站 WebSite webSite3 = factory.getWebSiteCategory("博客"); webSite3.use(new User("客户C")); // 给客户建立一个新闻发布类型的网站 WebSite webSite4 = factory.getWebSiteCategory("新闻发布"); webSite4.use(new User("客户A")); // 给客户建立一个公众号类型的网站 WebSite webSite5 = factory.getWebSiteCategory("公众号"); webSite5.use(new User("客户A")); // 给客户建立一个公众号类型的网站 WebSite webSite6 = factory.getWebSiteCategory("公众号"); webSite6.use(new User("客户B")); // 查看一下链接池中的实例数 System.out.println("实例数:" + factory.getWebSiteCount()); } }
运行结果:
【网站分类】: 博客 【客户】: 客户A
【网站分类】: 博客 【客户】: 客户B
【网站分类】: 博客 【客户】: 客户C
【网站分类】: 新闻发布 【客户】: 客户A
【网站分类】: 公众号 【客户】: 客户A
【网站分类】: 公众号 【客户】: 客户B
实例数:3
能够看出来,虽然有 6 个客户,可是实际上只有三个实例,一样再增长几十个,也最多只会有三个实例
定义:享元(Flyweight)模式运用共享技术来有效地支持大量细粒度对象的复用。
它经过共享已经存在的对象来大幅度减小须要建立的对象数量、避免大量类似类的开销,从而提升系统资源的利用率。
享元模式又叫作蝇量模式,因此英文为 Flyweight
注:方法参数和返回值没细细弄,主要为了说明结构
优势:相同对象只须要保存一份,下降了系统中内存的数量,减小了系统内存的压力
缺点:程序复杂性增大,同时读取享元模式的外部状态会使得运行时间稍微变长
享元模式其中也须要一个工厂进行控制,因此就好像是在工厂方法模式的基础上,增长了一个缓存机制,也就是经过一个 “池” 的概念,避免了大量相同的对象建立,大大下降了内存空间的消耗。
那么应用场景以下: