【设计模式】第十一篇:来一块儿瞅瞅享元模式

今天一块儿来看一个新的设计模式,那就是享元模式,关于此模式,常见的就是 “项目外包”、
以及 “五子棋” 这样两个例子,咱们下面就选择使用 “项目外包” 这个例子引入去讲java

一 故事引入

(一) 故事背景

程序员小B,帮助客户 A 作了一个展现一些产品内容的网站,经过 A 的 推荐,客户 B 、客户C 也想要作这样一个网站,可是就是形式有一些变化程序员

  • 有的客户但愿是新闻发布形式的
  • 有的客户但愿是博客形式的
  • 有的客户但愿是公众号形式的等等

并且他们都但愿可以下降一些费用,可是每个空间部署着一个网站,因此租借空间的费用是固定的,同时程序员小B 并不想从本身的劳动报酬中缩减费用web

(二) 思考解决方案

(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();
    }
}

运行结果:数据结构

当前网站分类: 博客
当前网站分类: 博客
当前网站分类: 博客
当前网站分类: 新闻发布
当前网站分类: 公众号
当前网站分类: 公众号ide

(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)因此请确认实例足够多的时候才值得去使用享元模式。
相关文章
相关标签/搜索