程序员潇然 发表于 2022-8-2 14:54:27

享元模式 FlyWeight 结构型 设计模式(十五)

享元模式(FlyWeight)

“享 **”取“共享”之意,“** **元** **”取“单元”之意。**

!(data/attachment/forum/202208/02/142815xjsey0vsv1k3071j.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")



### 意图

**运用共享技术,有效的支持大量细粒度的对象。**

### 意图解析

面向对象的程序设计中,一切皆是对象,这也就意味着系统的运行将会依赖大量的对象。

试想,如果对象的数量过多,势必会增加系统负担,导致运行的代价过高。

下面看两个小例子理解下

1.)有一首歌曲叫做《大舌头》

其中有一句歌词“说说说说 说你爱我 我我我我 说不出口”

!(data/attachment/forum/202208/02/142845i553kubkvvobkv5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")



如果使用面向对象的编程方式对这段歌词进行描述,假设一个汉字表示一个对象,你会怎么做?

你会用七个还是十六个对象进行表示?

!(data/attachment/forum/202208/02/142859a27v922v2u00ez0i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


2.)有一个文本编辑器软件,对于每一个字符使用对象进行表示
当打开一篇有很多重复字符的、数万字的文章时,你会使用几个对象进行表示?
如果仍旧采用每个字符占用一个对象,系统势必崩溃,必然需要共享对象

上面的两个例子中,都涉及到重复对象的概念



而享元模式的意图就是如此,将**重复的对象进行共享以达到支持大量细粒度对象的目的**。

如果不进行共享,如例2中描述的那样,一篇数万字符的文章将会产生数万个对象,这将是一场可怕的灾难。

!(data/attachment/forum/202208/02/142919medddddljxlxrexx.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


flyweight意为轻量级

在我们当前的场景下,寓意为通过共享技术,轻量级的---也就是内存占用更小

本质就是“共享”所以中文翻译过来多称之为享元

**简言之,享元模式就是要“共享对象”**

对于Java语言,我们熟悉的String,就是享元模式的运用

String是不可变对象,一旦创建,将不会改变

在JVM内部,String对象都是共享的

如果一个系统中的两个String对象,包含的字符串相同,只会创建一个String对象提供给两个引用

从而实现String对象的共享(new 的对象是两个不同的)

!(data/attachment/forum/202208/02/142934pvivctadut76dzyg.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


**享元模式又不仅仅是简单的“共享对象”**

上面的两个小例子中,对于文字中的重复字符

可以通过共享对象的方式,对某些对象进行共享,从而减少内存开销。

**考虑下图中的情景,这里面所有的“你”字,到底是不是同样的?**

* 是,因为全部都是汉字“你”
* 不是,因为尽管都是汉字“你”,但是他们的字体,颜色,字号,却又明显不同,所以不是同样的

!(data/attachment/forum/202208/02/142948w9mmzhkzmjv9d5hm.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")




**如果将字体、颜色、字号,作为“你”这个汉字的状态**

**是不是可以认为:他们都是一样的汉字,但是他们却又具有不同的状态?**

**其实享元模式不仅仅用来解决大量重复对象的共享问题,还能够用来解决相似对象的问题。**

享元对象能够共享的关键在于:区分对象的**内部状态**和**外部状态**

**内部状态是存储在享元对象内部的,并且不会随环境的变化而有所改变。**

比如上面的汉字“你”,无论在任何情况下,汉字“你”,始终是“你”,不会变成“她”

所以说享元模式解决共享问题,本质是共享内部状态

**外部状态是随外部环境变化而变化,不能共享的状态。**

享元对象的外部状态通常由客户端保存,在必要的时候在传递到享元对象内部

比如上面汉字“你”的字体、颜色、字号就是外部状态。

#### 小结

**享元模式就是为了避免系统中出现大量相同或相似的对象,同时又不影响客户端程序通过面向对象的方式对这些对象进行操作**

**享元模式通过共享技术,实现相同或相似对象的重用**

比如文编编辑器读取文本

在逻辑上每一个出现的字符都有一个对象与之对应,然而在物理上它们却共享同一个享元对象

在享元模式中,存储这些共享实例对象的地方通常叫做享元池(FlyweightPool)

享元模式可以结合String的**intern()**方法一起进行理解

通过区分了内部状态和外部状态,就可以将相同内部状态的对象存储在池中,池中的对象可以实现共享

需要的时候将对象从池中取出,实现对象的复用

通过向取出的对象注入不同的外部状态,进而得到一些列相似的对象

而这些看似各异的对象在内存中,仅仅存储了一份,大大节省了空间,所以说很自然的命名为“flyweight”轻量级

### 享元工厂

通过对意图的认识,可以认为,**享元模式其实就是对于“程序中会出现的大量重复或者相似对象”的一种“重构”**

当然,你应该是在设计之初就想到这个问题,而不是真的出现问题后再去真的重构

比如,你想要设计“字符”这种对象时,就应该考虑到他的“大量””重复““相似”的特点

所以需要分析出字符的内部状态,与外部状态

上面也提到对于享元对象,通过享元池进行管理

对于池的管理通常使用工厂模式,借助于工厂类对享元池进行管理

**用户需要对象时,通过工厂类获取**

**工厂提供一个存储在享元池中的已创建的对象实例,或者创建一个新的实例**



### 示例代码

针对于上面的例子,汉字“你”作为内部状态,可以进行共享

“颜色”作为外部状态,由客户端保存传递

创建字符类 Character、汉字字符类ChineseCharacter、颜色类Color以及工厂类CharacterFactory

Color含有颜色属性,通过构造方法设置,getter方法获取

```java
package flyweight;
public class Color {
    public String Color;
    public Color(String color) {
      this.Color = color;
    }
    public String getColor() {
      return Color;
    }
}
```


Character 抽象的字符类,用于描述字符

```java
package flyweight;
public abstract class Character {
    public abstract String getValue();
   
    public void display(Color color) {
      System.out.println("字符: " + getValue() + " ,颜色: " + color.getColor());
    }
}
```

汉字字符类,为了简化,直接设置value为汉字“你”

```java
package flyweight;
public class ChineseCharacter extends Character {
    @Override
    public String getValue() {
      return "你";
    }
}
```


CharacterFactory字符工厂类

通过单例模式创建工厂

内部HashMap用于存储字符,并且提供获取方法

为了简化程序,初始就创建了一个汉字字符“你”存储于字符中

```java
package flyweight;
import java.util.HashMap;
public class CharacterFactory {
    /**
    * 单例模式 饿汉式创建
    */
    private static CharacterFactory instance = new CharacterFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
    private CharacterFactory() {
      Character character = new ChineseCharacter();
      hm.put("你", character);
    }
    /**
    * 单例全局访问接口获取工厂
    */
    public static CharacterFactory getInstance() {
      return instance;
    }
   
    /**
    * 根据key获取池中的对象
    */
    public Character getCharacter(String key) {
      return (Character) hm.get(key);
    }
}
```

测试代码

!(data/attachment/forum/202208/02/143115dzuk0z13d13h8kcc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


示例中,我们通过工厂,从享元池中获取了三个汉字字符“你”。

通过 == 可以看得出来,他们都是同一个对象

在分别调用他们的display方法时,在客户端(此处为我们的Test main方法)中创建,并且传递给享元对象

通过方法参数的形式进行外部状态的设置。

!(data/attachment/forum/202208/02/143130prysn7cybuccjm2j.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")



CharacterFactory 单例模式,返回自身实例

CharacterFactory内部维护Character的享元池

Character 依赖Color

ChineseCharacter是Character的实现类


### 结构

将上面的示例转换为标准的享元模式的名称

!(data/attachment/forum/202208/02/143149nbpnnvsnsv2px1wf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


**抽象享元角色 FlyWeight**

所有具体享元类的超类,为这些类规定了需要实现的公共接口

外部状态可以通过业务逻辑方法的参数形式传递进来

**具体享元角色ConcreteFlyWeight**

实现抽象享元角色所规定的的接口

需要保存内部状态,而且,内部状态必须与外部状态无关

从而才能使享元对象可以在系统内共享

**享元工厂角色 FlyWeightFactory**

负责创建和管理享元角色,也就是维护享元池

必须保证享元对象可以被系统适当的共享

接受客户端的请求

如果有适当符合要求的享元对象,则返回

如果没有一个适当的享元对象,则创建

**客户端角色Client**
客户端角色维护了对所有享元对象的引用

!(data/attachment/forum/202208/02/143204flrlp7el2l72pnwu.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")

需要保存维护享元对象的外部状态,然后通过享元对象的业务逻辑方法作为参数形式传递

!(data/attachment/forum/202208/02/143215il4vkbr6hvvvs64z.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


### 分类

#### 单纯享元模式

在上面的结构中,如果所有的ConcreteFlyWeight都可以被共享

也就是所有的FlyWeight子类都可以被共享,那就是所有的享元对象都可以被共享

这种形式被称之为**单纯享元模式**

**单纯享元代码**

```java
package flyweight.simple;
public abstract class FlyWeight {
/**
* 抽象的业务逻辑方法,接受外部状态作为参数
*/
abstract public void operation(String outerState);
}
```

```java
package flyweight.simple;
public class ConcreteFlyWeight extends FlyWeight {
    private String innerState = null;
    public ConcreteFlyWeight(String innerState) {
      this.innerState = innerState;
    }
    /**
    * 外部状态作为参数传递
    */
    @Override
    public void operation(String outerState) {
      System.out.println("innerState = " + innerState + " outerState = " + outerState);
    }
}
```

```java
package flyweight.simple;
import java.util.HashMap;
public class FlyWeightFactory {
    /**
    * 单例模式 饿汉式创建
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
   
    private FlyWeightFactory() {
    }
    /**
    * 单例全局访问接口获取工厂
    */
    public static FlyWeightFactory getInstance() {
    return instance;
    }
    /**
    * 根据innerState获取池中的对象
    * 存在返回,不存在创建并返回
    */
    public FlyWeight getFylWeight(String innerState) {
      if(hm.containsKey(innerState)){
      return (FlyWeight) hm.get(innerState);
      }else{
      FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
      hm.put(innerState,flyWeight);
      return flyWeight;
      }
    }
}
```

```java
package flyweight.simple;
public class Test {
public static void main(String[] args){
FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");

System.out.println(flyWeight1);
System.out.println(flyWeight2);
System.out.println(flyWeight3);
System.out.println();

flyWeight1.operation("outer state XXX");
flyWeight2.operation("outer state YYY");
flyWeight3.operation("outer state ZZZ");
}
}
```

!(data/attachment/forum/202208/02/143308rtwfrui8fceretzr.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


#### 复合享元模式

与单纯享元模式对应的是复合享元模式

单纯享元模式中,所有的享元对象都可以共享

复合享元模式中,则并不是所有的ConcreteFlyWeight都可以被共享

也就是说:**不是所有的享元对象都可以被共享**

实际上,并不是所有的FlyWeight子类都需要被共享

FlyWeight接口使的可以进行共享,但是没有任何必要 强制必须共享

实践中,UnsharedConcreteFlyWeight对象通常将ConcreteFlyWeight对象作为子节点

!(data/attachment/forum/202208/02/143321veye23uj5z63egty.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


与单纯享元模式相比,仅仅是拥有了不可共享的具体子类

而且,这个子类往往是应用了组合模式,将ConcreteFlyWeight对象作为子节点

**复合享元角色UnsharedConcreteFlyWeight**
复合享元角色,也就是不可共享的,也被称为 不可共享的享元对象
但是一个复合享元对象可以分解为多个本身是单纯享元对象的组合
这些单纯的享元对象就又是可以共享的

**复合享元代码**

将简单模式中的示例代码进行改造

FlyWeight不变

```java
package flyweight.composite;
public abstract class FlyWeight {
/**
* 抽象的业务逻辑方法,接受外部状态作为参数
*/
abstract public void operation(String outerState);
}
```

ConcreteFlyWeight不变

```java
package flyweight.composite;
public class ConcreteFlyWeight extends FlyWeight {
private String innerState = null;
public ConcreteFlyWeight(String innerState) {
this.innerState = innerState;
}
/**
* 外部状态作为参数传递
*/
@Override
public void operation(String outerState) {
      System.out.println("innerState = " + innerState + " outerState = " + outerState);
}
}
```



新增加不共享的子类也就是组合的享元子类

内部使用list 维护单纯享元模式对象,提供add方法进行添加

提供operation操作

```java
package flyweight.composite;
import java.util.ArrayList;
import java.util.List;
public class UnsharedConcreateFlyWeight extends FlyWeight {
private String innerState = null;
public UnsharedConcreateFlyWeight(String innerState) {
this.innerState = innerState;
}

private List<FlyWeight> list = new ArrayList<>();
public void add(FlyWeight flyWeight) {
list.add(flyWeight);
}
@Override
public void operation(String outerState) {
for (FlyWeight flyWeight:list) {
flyWeight.operation(outerState);
    }
}
}
```


FlyWeightFactory工厂类进行改造

新增加public UnsharedConcreateFlyWeight getCompositeFylWeight(String state)

用于获得组合享元对象

```java
package flyweight.composite;

import java.util.HashMap;

public class FlyWeightFactory {
    /**
    * 单例模式 饿汉式创建
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
   
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
   
    /**
    * 管理复合享元对象
    */
    private HashMap<String, Object> compositeHm = new HashMap<>();
   
    private FlyWeightFactory() {
    }
   
    /**
    * 单例全局访问接口获取工厂
    */
    public static FlyWeightFactory getInstance() {
      return instance;
    }
   
    /**
    * 根据innerState获取池中的对象
    * 存在返回,不存在创建并返回
    */
    public FlyWeight getFylWeight(String innerState) {
      if(hm.containsKey(innerState)){
            return (FlyWeight) hm.get(innerState);
      }else{
            FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
            hm.put(innerState,flyWeight);
            return flyWeight;
      }
    }
   
    /**
    * 根据innerState获取池中的对象
    * 存在返回,不存在创建并返回
    */
    public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) {
      if(compositeHm.containsKey(state)){
            return (UnsharedConcreateFlyWeight) compositeHm.get(state);
      }else{
            UnsharedConcreateFlyWeight flyWeight = new UnsharedConcreateFlyWeight(state);
            compositeHm.put(state,flyWeight);
            return flyWeight;
      }
    }

}
```

测试类也进行改造

```java
package flyweight.composite;
public class Test {
public static void main(String[] args){
    FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
    FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
    FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
    FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");
   
    System.out.println(flyWeight1);
    System.out.println(flyWeight2);
    System.out.println(flyWeight3);
   
    System.out.println("###########################################");
   
    flyWeight1.operation("outer state XXX");
    flyWeight2.operation("outer state YYY");
    flyWeight3.operation("outer state ZZZ");
    System.out.println("###########################################");
    UnsharedConcreateFlyWeight compositeFlyWeight = flyWeightFactory.getCompositeFylWeight("composite");
    compositeFlyWeight.add(flyWeight1);
    compositeFlyWeight.add(flyWeight2);
    compositeFlyWeight.operation("composite out state OOO");
    }
}
```


!(data/attachment/forum/202208/02/145344mv5352dzujsbzvq5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


测试程序在原来的基础上,新获得了一个组合享元对象

然后将两个单纯享元对象添加到组合享元对象中

然后调用operation,通过打印信息可以看得出来

不同的单纯享元对象,他们却有了一致的外部状态

!(data/attachment/forum/202208/02/145359gpv9qj2zqp8p29p1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "image.png")


所以使用复合享元模式的一个常用目的就是:

**多个内部状态不同的单纯享元对象,拥有一致的外部状态**

这种场景下,就可以考虑使用复合享元模式

### 使用场景

如果有下列情况,则可以考虑使用享元模式

* 应用程序中使用了大量的对象
* 大量的对象明显增加了程序的存储运行开销
* 对象可以提取出内部状态,并且可以分离外部状态

**使用享元模式有一点需要特别注意:应用程序运行不依赖这些对象的身份**

换句话说这些对象是不做区分的,适用于“在客户端眼里,他们都是一样的”这种场景

比如单纯的使用对象的方法,而不在意对象是否是创建而来的,否则如果客户端鉴别对象的身份(equals),当他们是同一个对象时将会出现问题



### 总结

**享元模式的核心就是共享**

**共享就需要找准内部状态,以及分离外部状态**,**外部状态由客户端维护,在必要时候,通过参数的形式注入到享元对象中**

**在有大量重复或者相似对象的场景下,都可以考虑到享元模式**

而且为了达到共享的目的,需要通过**工厂**对象进行控制

只有通过工厂来维护享元池才能达到共享的目的,如果任意创建使用则势必不能很好地共享

享元模式大大的减少了对象的创建,降低了系统所需要的内存空间

但是由于**将状态分为内部状态和外部状态,而外部状态是分离的,那么状态的读取必然会增大开销**

所以说**享元模式是时间换空间**

如果确定需要使用享元模式,如果对于多个内部状态不同的享元对象,希望他们拥有一致性的外部状态

那么就可以考虑复合享元模式,复合享元模式是与合成模式的结合。

!(data/attachment/forum/202206/16/141330jha7st9soow8772i.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "common_log.png")
`转载务必注明出处:程序员潇然,疯狂的字节X,https://crazybytex.com/thread-113-1-1.html `
页: [1]
查看完整版本: 享元模式 FlyWeight 结构型 设计模式(十五)