天天看点

23种设计模式之_享元模式

文章目录

  • ​​一、什么是享元模式​​
  • ​​二、享元模式几个角色​​
  • ​​抽象享元类(Flyweight)​​
  • ​​具体享元类(FlyWeightAIml,FlyWeightBIml)​​
  • ​​享元工厂类(FlyweightFactoiy)​​
  • ​​三、享元模式使用场景​​
  • ​​四、代码分析​​
  • ​​总结​​

设计模式的熟练掌握,能够更容易理解系统的底层架构实现。

一、什么是享元模式

享元模式(Flyweight Pattern):以共享的方式高效的支持大量的细粒度对象。通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。

  享元的英文是Flyweight,是一个来自体育方面的专业用语,在拳击、摔跤和举重比赛中特指最轻量的级别。把这个单词移植到软件工程中,也是用来表示特别小的对象,即细粒度的对象。至于为什么把Flyweight翻译为“享元”,可以理解为共享元对象,也就是共享细粒度对象。

  在面向对象中,大量细粒度对象的创建、销毁及存储所造成的资源和性能上的损耗,可能会在系统运行时形成瓶颈。那么该如何避免产生大量的细粒度对象,同时又不影响系统使用面向对象的方式进行操作呢?享元模式提供了一个比较好的解决方案。

23种设计模式之_享元模式

二、享元模式几个角色

uml类图:

23种设计模式之_享元模式

抽象享元类(Flyweight)

它是所有具体享元类的超类。为这些类规定出需要实现的公共接口,那些需要外蕴状态(Exte的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。

具体享元类(FlyWeightAIml,FlyWeightBIml)

具体享元类实现了抽象享元类所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元类又称为单纯具体享元类,因为复合享元类是由单纯具体享元角色通过复合而成的。

享元工厂类(FlyweightFactoiy)

享元工厂类负责创建和管理享元对象。当一个客户端对象请求一个享元对象的时候,享元工厂需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。

####客户类(Client)

客户类需要自行存储所有享元对象的外蕴状态。

三、享元模式使用场景

当系统中某个对象类型的实例较多的时候;

  当系统设计时候,对象实例真正有区别的分类很少,例如对于拼音,如果对每个字母都初始化一个对象实例的话,这样实例就太多了。使用享元模式只需要提前初始化基本拼音,就可以任意进行组装成不同的拼音

四、代码分析

网上很多例子,看着不太明白。使用字母进行举例子通俗易懂。

CharactorFactory 工厂

package Chartflyweight;

import java.util.Hashtable;

public class CharactorFactory {
  private Hashtable<String, FlyWeight> charactors = new Hashtable<String, FlyWeight>();

  // 构造函数
  public CharactorFactory() {
    charactors.put("A", new FlyWeightAIml());
    charactors.put("B", new FlyWeightBIml());
  }

  // 获得指定字符实例
  public FlyWeight getCharactor(String key) {
    FlyWeight charactor = (FlyWeight) charactors.get(key);
    if (charactor == null) {
      if (key.equals("A")) {
        charactor = new FlyWeightAIml();
      } else if (key.equals("B")) {
        charactor = new FlyWeightBIml();
      }
      charactors.put(key, charactor);
    }
    return charactor;
  }
}      

FlyWeight 享元抽象类

package Chartflyweight;

public abstract class FlyWeight {
  protected String charStr = "";

  protected int fontSize;

  protected abstract void operator(int fontSize);

  // 显示方法
  protected abstract void displayCharator();

}      

FlyWeightAIml 具体实现类

package Chartflyweight;

public class FlyWeightAIml extends FlyWeight {


  @Override
  protected void operator(int fontSize) {
       this.fontSize=fontSize;
  }
  
  

  public FlyWeightAIml() {
    this.charStr = "A";
    this.fontSize=12;
  }



  @Override
  protected void displayCharator() {
    System.out.println("字符:" + this.charStr + ",大小:" + fontSize);
  }

}      

FlyWeightBIml 具体实现类

package Chartflyweight;

public class FlyWeightBIml extends FlyWeight {

  @Override
  protected void operator(int fontSize) {
    this.fontSize = fontSize;
  }

  public FlyWeightBIml() {
    this.charStr = "B";
    this.fontSize = 12;
  }

  @Override
  protected void displayCharator() {
    System.out.println("字符:" + this.charStr + ",大小:" + fontSize);
  }

}      

Client 测试类 1 ( 简单的剔除外蕴状态,在client中进行存储)

package Chartflyweight;

//如何有特别多的外部状态,则需要很多的函数,函数进行抽取


public class Clinet {
  public static void main(String[] args) {
    FlyWeightAIml a = new FlyWeightAIml();
    FlyWeightBIml b = new FlyWeightBIml();
    // 显示字符A
    display(a, 12);
    // 显示字符B
    display(b, 14);
  }

  // 设置字符的大小
  public static void display(FlyWeight objChar, int nSize) {
    try {
      System.out.println("字符:" + objChar.charStr + ",大小:" + nSize);
    } catch (Exception err) {
    }
  }
}      

测试类2 (考虑到复用性,将外蕴状态作为参数在使用时候进行传递)

package Chartflyweight;

//如何有特别多的外部状态,则需要很多的函数,函数进行抽取

public class Clinet2 {
  public static void main(String[] args) {
    FlyWeightAIml a = new FlyWeightAIml();
    FlyWeightBIml b = new FlyWeightBIml();
    // 设置字符A的大小
    a.operator(12);
    // 显示字符B
    a.displayCharator();
    // 设置字符B的大小
    b.operator(14);
    // 显示字符B
    b.displayCharator();
  }
}      

输出结果:

字符:A,大小:12

字符:B,大小:14

上面代码内蕴共享对象是 A,B ,而外蕴不共享状态是 fontSize.

引用个例子:

享元模式在一般的项目开发中并不常用,而是常常应用于系统底层的开发,以便解决系统的性能问题。

Java和.Net中的String类型就是使用了享元模式。如果在Java或者.NET中已经创建了一个字符串对象s1,那么下次再创建相同的字符串s2的时候,系统只是把s2的引用指向s1所引用的具体对象,这就实现了相同字符串在内存中的共享。如果每次执行s1=“abc”操作的时候,都创建一个新的字符串对象的话,那么内存的开销会很大。

如果大家有兴趣的话,可以用下面的程序进行测试,就会知道s1和s2的引用是否一致:

Java代码:

String s1 = "测试字符串1";
String s2 = "测试字符串1";
//“==”用来判断两个对象是否是同一个,equals判断字符串的值是否相等
if( s1 == s2 ){
System.out.println("两者一致");
}else{
System.out.println("两者不一致");
}      

程序运行后,输出的结果为“两者一致”,这说明String类的设计采用了享元模式。如果s1的内容发生了变化,比如执行了s1 += "变化"的语句,那么s1与s2的引用将不再一致。

总结

享元模式属于结构型模式。

享元模式是池技术的重要实现方式,它可以减少重复对象的创建,使用缓存来共享对象,从而降低内存的使用。

细粒度的对象其状态可以分为两种:内部状态和外部状态。

内部状态:对象可共享出来的信息,存储在享元对象内部并且不会随环境的改变而改变。
外部状态:对象依赖的一个标记是随环境改变而改变的,并且不可共享。      

一般会有一个Flyweight Factory,管理享元对象的创建和缓存。

继续阅读