明天一起来看一个新的设计模式,那就是享元模式,对于此模式,常见的就是 “我的项目外包”、
以及 “五子棋” 这样两个例子,咱们上面就抉择应用 “我的项目外包” 这个例子引入去讲

一 故事引入

(一) 故事背景

程序员小B,帮忙客户 A 做了一个展现一些产品内容的网站,通过 A 的 举荐,客户 B 、客户C 也想要做这样一个网站,然而就是模式有一些变动

  • 有的客户心愿是新闻公布模式的
  • 有的客户心愿是博客模式的
  • 有的客户心愿是公众号模式的等等

而且他们都心愿可能升高一些费用,然而每一个空间部署着一个网站,所以租借空间的费用是固定的,同时程序员小B 并不想从本人的劳动报酬中缩减费用

(二) 思考解决方案

(1) 最简略的传统计划

先说最简略能想到的计划,间接把网站代码复制几份,而后每一个都租借一个空间,而后对代码进行定制批改。注:这里还没思考优化或者省钱

咱们用一个 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();    }}

运行后果:

以后网站分类: 博客
以后网站分类: 博客
以后网站分类: 博客
以后网站分类: 新闻公布
以后网站分类: 公众号
以后网站分类: 公众号

(2) 存在的问题及改良思路

  • ① 假如虚拟空间在同一台服务器上,做上述内容,须要实例化 6 个 WebSite,而其本质又没有很大的差异,所以对于服务器的资源节约很大
  • ② 网站构造类似度很高,根本全是反复的代码

对于这种重复性很高的内容,首先咱们要做到将其形象进去,反复创立实例在设计模式中必定是不太理智的,咱们想要做到多个客户,共享同一个实例。这样不论是代码还是服务器资源利用,都会改善很多

一个不算特地失当的例子:例如外卖平台中的一个一个商家店铺,是不是能够了解为平台中的一个小店铺,小网站,其中通过例如店铺 ID 等内容来辨别不同店铺,然而其每一家店铺整体的模板和样子是差不多的。

咱们上面要做的就是,将大量类似内容形象成一个网站模板类,而后把一些特定的内容,通过参数移到实例的里面,调用的时候再指定,这样能够大幅度缩小单个实例的数目。

(3) 享元模式初步改良

创立一个形象的 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

(4) 享元模式再改良-辨别内外部状态

下面的代码,应用工厂代替了间接实例化的形式,工厂中,次要通过一个池的概念,实现了共享对象的目标,然而其实咱们会发现,例如创立三个博客类型的网站,然而如同这三个网站就是截然不同的,然而不同的客户,其中博客网站中的数据必定是不同的,这就是咱们还没有辨别外部内部的状态

外部状态:对象共享进去的信息,存储在享元对象外部并且不会随环境扭转的共享局部

内部状态:对象用来标记的一个内容,随环境会扭转,不可共享

打个比方,五子棋只有黑白两色,总不能下多少子,就创立多少个实例吧,所以咱们把色彩看做外部状态,有黑白两种色彩。而各个棋子的地位并不相同,当咱们落子后这个地位信息才会被传入,所以地位信息就是内部状态

那么对于“外包网站”的例子中,很显然,不同的客户网站数据就是一个内部状态,上面来批改一下

首先新增一个 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

(二) 结构图

注:办法参数和返回值没细细弄,次要为了阐明构造

  • 形象享元角色(Flyweight):是所有的具体享元类的超类或接口,非享元的内部状态以参数的模式通过办法传入。
  • 具体享元(Concrete Flyweight)角色:实现形象享元角色中所规定的接口。
  • 非享元(Unsharable Flyweight) 角色:是不共享的内部状态,它以参数的模式注入具体享元的相干办法中,这也意味着,享元模式并不强制共享
  • 享元工厂(Flyweight Factory)角色:负责创立和治理享元角色。

    • 当客户对象申请一个享元对象时,享元工厂检査零碎中是否存在符合要求的享元对象

      • 如果存在则提供给客户
      • 如果不存在的话,则创立一个新的享元对象

(二) 简述优缺点

长处:雷同对象只须要保留一份,升高了零碎中内存的数量,缩小了零碎内存的压力

毛病:程序复杂性增大,同时读取享元模式的内部状态会使得运行工夫略微变长

(三) 利用场景

享元模式其中也须要一个工厂进行管制,所以就如同是在工厂办法模式的根底上,减少了一个缓存机制,也就是通过一个 “池” 的概念,防止了大量雷同的对象创立,大大降低了内存空间的耗费。

那么利用场景如下:

  • 一个程序应用了大量类似或者雷同的对象,且造成了很大的开销的时候
  • 大部分对象,能够依据外部状态分组,且可将不同局部内部化,这样每一个组只需保留一个外部状态。

    • 例如下面的博客,新闻,公众号站模式就是三种组,每个组只须要传入用户数据这个内部状态即可
  • 因为应用享元模式,须要一个保留享元的数据结构(例如下面的 Hashmap)所以请确认实例足够多的时候才值得去应用享元模式。