23种设计模式之享元(Flyweight)模式

2,634 阅读5分钟

1、定义

运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很少,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此又称为轻量级模式。

2、内部状态VS外部状态

  • 内部状态是存储在享元对象内部并且不会随环境变化而变化的状态,因此内部状态可以共享。
  • 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外部状态之间是相互独立的。

3、单纯享元模式VS复合享元模式

3.1 单纯享元模式

3.1.1 模式结构

单纯享元模式由三部分组成:

  • Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;但用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例将其存储在享元池中。

3.1.2 实例

3.1.2.1 网站(Flyweight)

public interface WebSite {
    
    // name是外部状态
    public void use(String name);
}

3.1.2.2 具体网站(ConcreteFlyweight)

public class ConcreteWebSite implements WebSite {
    
    // 内部状态
    private String type;
    
    public ConcreteWeiSite(String type) {
        this.type = type;
    }
    
    @Override
    public void use(String name) {
        System.out.println(name + "在使用以《" + type + "》形式发布的网站");
    }
}

3.1.2.3 WebSiteFactory(FlyweightFactory)

public class WebSiteFactory {

    private static WebSiteFactory FACTORY = new WebSiteFactory();

    private WebSiteFactory(){}

    public static WebSiteFactory getInstance() {
        return FACTORY;
    }

    private Map<String, WebSite> webSites = new HashMap<>();

    public WebSite getFlyWeight(String type) {
        WebSite webSite = webSites.get(type);
        if (webSite == null) {
            webSite = new ConcreteWebSite(type);
            webSites.put(type, webSite);
        }
        return webSite;
    }

    public int getWebSiteCount() {
        return webSites.size();
    }
}

3.1.2.4 客户端调用

public class Client {

    public static void main(String[] args) {
        
        WebSiteFactory factory = WebSiteFactory.getInstance();
        
        WebSite news = factory.getFlyWeight("新闻");
        news.use("小白");
        
        WebSite blog1 = factory.getFlyWeight("博客");
        blog1.use("小明");
        
        WebSite blog2 = factory.getFlyWeight("博客");
        blog2.use("小红");
        
        System.out.println("网站的分类总数=" + factory.getWebSiteCount());
    }
}
3.2 复合享元模式

3.2.1 模式结构

复合享元模式由四部分组成:

  • Flyweight(抽象享元类):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • ConcreteFlyweight(具体享元类):它实现了抽象享元类,其实例称为享元对象;在具体享元类为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • CompositeConcreteFlyweight(复合享元类):它实现了抽象享元类,同时使用Map来保存单纯享元对象,并提供方法来增加或者移除单纯享元对象。
  • FlyweightFactory(享元工厂类):享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中,享元池一般设计为一个存储“键值对”的集合(也可以是其他类型的集合),可以结合工厂模式进行设计;但用户请求一个具体享元对象时,享元工厂提供一个存储在享元池中已创建的实例或者创建一个新的实例(如果不存在的话),返回新创建的实例将其存储在享元池中。

3.2.2 实例

3.2.2.1 网站(Flyweight)

public interface WebSite {

    // name是外部状态
    public void use(String name);
}   

3.2.2.2 具体网站(ConcreteFlyweight)

public class ConcreteWebSite implements WebSite {

    // 内部状态
    private String type;

    public ConcreteWebSite(String type) {
        this.type = type;
    }

    @Override
    public void use(String name) {
        System.out.println(name + "在使用以《" + type + "》形式发布的网站");
    }
}

3.2.2.3 CompositeConcreteWebSite(CompositeConcreteFlyweight)

public class CompositeConcreteWebSite implements WebSite {

    private Map<String, WebSite> webSites = new HashMap<>();

    @Override
    public void use(String name) {
        for (Map.Entry<String, WebSite> entry : webSites.entrySet()) {
            entry.getValue().use(name);
        }
    }

    public void add(String type, WebSite webSite) {
        webSites.put(type, webSite);
    }

    public void remove(String type) {
        webSites.remove(type);
    }
}

3.2.2.4 WebSiteFactory(FlyweightFactory)

public class WebSiteFactory {

    private static WebSiteFactory FACTORY = new WebSiteFactory();

    private WebSiteFactory(){}

    public static WebSiteFactory getInstance() {
        return FACTORY;
    }

    private Map<String, WebSite> webSites = new HashMap<>();

    // 单纯享元模式
    public WebSite getFlyWeight(String type) {
        WebSite webSite = webSites.get(type);
        if (webSite == null) {
            webSite = new ConcreteWebSite(type);
            webSites.put(type, webSite);
        }
        return webSite;
    }

    // 复合享元模式
    public WebSite getFlyWeight(List<String> types) {
        CompositeConcreteWebSite webSite = new CompositeConcreteWebSite();
        for (String type : types) {
            webSite.add(type, this.getFlyWeight(type));
        }
        return webSite;
    }

    public int getWebSiteCount() {
        return webSites.size();
    }
}

3.1.2.5 客户端调用

public class Client {

    public static void main(String[] args) {
        
        WebSiteFactory factory = WebSiteFactory.getInstance();
        
        List<String> types = new ArrayList<>();
        types.add("新闻");
        types.add("博客");
        types.add("视频");
        
        WebSite webSite1 = factory.getFlyWeight(types);
        webSite1.use("小白");
        
        WebSite webSite2 = factory.getFlyWeight(types);
        webSite2.use("小明");
        
        // 不相等,复合享元对象不可共享
        Sytem.out.println(webSite1 == webSite2)
    }
}
3.3 不同点
  • 单纯享元模式:所有的具体享元类都是可以共享的,不存在非共享具体享元类。
  • 复合享元模式:将一些单纯享元对象使用组合模式加以组合,形成复合享元对象,这样复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。复合享元模式可以对多个单纯享元对象设置相同的外部状态。

4、适用场景

  • 一个系统有大量相同或者相似的对象,造成内存的大量耗费。
  • 对象的大部分状态都可以外部化,可以将这些外部传入对象在鸿。
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的系统资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式。

5、在JDK的应用

Integer部分源码:

6、优缺点

6.1 优点
  • 可以极大减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而可以节约系统资源,提高系统性能。
  • 享元模式的对象状态相对独立,从而不会影响其内部状态,从而使得享元对象可以在不同的环境被共享。
6.2 缺点
  • 享元模式使得系统变得复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。
  • 为了是对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长。

源代码:github.com/freedom9/de…