设计模式-享元详解(从入门到源码使用)

16 阅读5分钟

享元模式

介绍

享元模式是一种设计模式,它的目的是通过共享来有效地支持大量细粒度的对象。它通过共享已有的可公用的内部状态来大幅度减少需要创建的对象数量。这样可以节省系统资源,并同时提高系统的性能。

享元模式通过将对象的内部状态和外部状态分离开来,只保留对象的外部状态。对象的内部状态可以共享,而对象的外部状态则是独立的。这样可以有效地减少系统中对象的数量,并同时提高系统的性能。

二spring源码中的应用(可以先看后面两个简单的例子)

最常见的就是在sprin源码中: spring 框架中有多个地方使用了享元模式。例如,在 Spring 的事件处理机制中,使用了享元模式来管理事件监听器对象的创建和销毁,从而节省系统资源,提高系统性能。

另外,在 Spring 的 BeanFactory 中,也使用了享元模式来管理 Bean 对象的创建和销毁,从而实现对象的复用,节省系统资源,提高系统性能。

beanfactory中

// 定义 Bean 接口
interface Bean {
  // 省略其他方法
}

// 定义具体 Bean 类
class ConcreteBean implements Bean {
  // 省略其他方法
}

// 定义 BeanFactory
class BeanFactory {
//这里就是享元模式的体现
  private static final Map<String, Bean> beans = new HashMap<>();

  public static Bean getBean(String beanName) {
    Bean bean = beans.get(beanName);
    if (bean == null) {
      // 从配置文件加载 Bean 的配置信息
      BeanDefinition beanDefinition = loadBeanDefinitionFromConfigFile(beanName);
      // 根据配置信息创建 Bean 对象
      bean = createBean(beanDefinition);
      beans.put(beanName, bean);
    }
    return bean;
  }
}

// 使用 BeanFactory
public class Main {
  public static void main(String[] args) {
    // 获取两个不同名称的 Bean
    Bean bean1 = BeanFactory.getBean("bean1");
    Bean bean2 = BeanFactory.getBean("bean2");

    // 使用 Bean
    // ...
  }
}

在这个例子中,我们定义了一个 Bean 接口表示 Bean 对象,并定义了一个 ConcreteBean 类来实现该接口。然后,我们定义了一个 BeanFactory 类来管理 Bean 对象的创建和缓存。该类包含一个 Map 对象,用来存储已经加载过的 Bean 对象,并实现了 getBean 方法来获取指定名称的 Bean 对象。如果该 Bean 对象还没有被加载,则需要从配置文件中加载 Bean 的配置信息,并根据配置信息创建 Bean 对象,然后,将新创建的 Bean 对象添加到 Map 中,并返回该对象。如果该 Bean 对象已经被加载过,则直接从 Map 中获取并返回。 最后,在 main 方法中,我们通过调用 getBean 方法来获取两个不同名称的 Bean 对象,然后使用这些 Bean 对象。由于我们使用了享元模式,因此每个名称对应的 Bean 只会被创建一次,并被缓存在 Map 中。

事件监听器

// 定义事件监听器接口
interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
  void onApplicationEvent(E event);
}

// 定义事件
class MyEvent extends ApplicationEvent {
  // 省略其他方法
}

// 定义事件监听器
class MyListener implements ApplicationListener<MyEvent> {
  public void onApplicationEvent(MyEvent event) {
    // 处理 MyEvent 事件
    // ...
  }
}

// 定义事件处理器
class EventHandler {
  private static final Map<Class<? extends ApplicationEvent>, Set<ApplicationListener<?>>> listeners = new HashMap<>();

  public static <E extends ApplicationEvent> void registerListener(Class<E> eventType, ApplicationListener<E> listener) {
    Set<ApplicationListener<?>> eventListeners = listeners.get(eventType);
    if (eventListeners == null) {
      eventListeners = new HashSet<>();
      listeners.put(eventType, eventListeners);
    }
    eventListeners.add(listener);
  }

  public static <E extends ApplicationEvent> void unregisterListener(Class<E> eventType, ApplicationListener<E> listener) {
    Set<ApplicationListener<?>> eventListeners = listeners.get(eventType);
    if (eventListeners != null) {
      eventListeners.remove(listener);
    }
  }

  public static void fireEvent(ApplicationEvent event) {
    Set<ApplicationListener<?>> eventListeners = listeners.get(event.getClass());
    if (eventListeners != null) {
      for (ApplicationListener<?> listener : eventListeners) {
      // 调用监听器的 onApplicationEvent 方法来处理事件
      listener.onApplicationEvent(event);
    }
  }
}

我们定义了一个 ApplicationListener 接口表示事件监听器,并定义了一个 MyEvent 类和一个 MyListener 类来处理该事件。然后,我们定义了一个 EventHandler 类来处理事件。该类包含一个 Map 对象,用来存储事件监听器对象,并实现了 registerListener 和 unregisterListener 方法来注册和注销事件监听器。

在 EventHandler 类中,我们通过调用 registerListener 方法来将事件监听器添加到 Map 中,以便在接收到事件时调用监听器的回调方法。在 unregisterListener 方法中,我们通过调用 remove 方法来将事件监听器从 Map 中删除,以便停止监听事件。

最后,在 fireEvent 方法中,我们通过调用 get 方法来从 Map 中获取与事件对应的事件监听器集合,然后通过遍历集合调用监听器的回调方法来处理事件。

通过使用享元模式,我们可以将事件监听器对象的创建和销毁集中在一个地方进行,从而节省系统资源,提高系统性能。

图形化系统

一个常见的例子是使用享元模式来优化图形系统。在这种情况下,每个图形对象可以被表示为一个享元对象,其中包含可以共享的内部状态(如颜色、形状等)和独立的外部状态(如位置、大小等)。这样,在系统中如果有大量图形对象需要被绘制,就可以通过共享内部状态来减少系统中对象的数量,从而提高系统的性能。

// 定义图形接口
interface Shape {
  void draw();
}

// 定义具体图形类(内部状态类)
class Rectangle implements Shape {
  private String color;

  public Rectangle(String color) {
    this.color = color;
  }

  public void draw() {
    System.out.println("Drawing a rectangle with color " + color);
  }
}

// 定义享元工厂类
class ShapeFactory {
  private static final Map<String, Shape> shapes = new HashMap<>();

  public static Shape getShape(String color) {
    Shape shape = shapes.get(color);
    if (shape == null) {
      shape = new Rectangle(color);
      shapes.put(color, shape);
    }
    return shape;
  }
}

// 使用享元模式
public class Main {
  public static void main(String[] args) {
    // 创建 10 个不同颜色的矩形对象
    for (int i = 0; i < 10; i++) {
      Shape shape = ShapeFactory.getShape("Color " + i);
      shape.draw();
    }
  }
}

在这个例子中,我们定义了一个 Shape 接口表示图形,并定义了一个 Rectangle 类来实现该接口。该类包含一个内部状态(颜色),并实现了 draw 方法来绘制矩形。 然后,我们定义了一个 ShapeFactory 类来创建并管理图形对象。该类使用一个静态的 Map 对象来存储已经创建过的图形对象,并实现了一个 getShape 方法来根据颜色获取图形对象。getShape 方法首先检查指定颜色的图形对象是否已经存在,如果不存在则创建一个新的图形对象并将其添加到 Map 中,然后返回该图形对象。

在 main 方法中,我们通过循环来创建 10 个不同颜色的矩形对象,并调用它们的 draw 方法来绘制矩形。这样一来我们只要去map容器中寻找并且通过getshape来添加新的对象,提高了系统性能。

享元模式+对象池

另外,享元模式还可以通过对象池的方式来管理对象的创建和销毁,从而进一步减少对象的创建,提高系统性能。

// 定义图形接口
interface Shape {
  void draw();
}

// 定义具体图形类(内部状态类)
class Rectangle implements Shape {
  private String color;

  public Rectangle(String color) {
    this.color = color;
  }

  public void draw() {
    System.out.println("Drawing a rectangle with color " + color);
  }
}

// 定义享元工厂类
class ShapeFactory {
  private static final Map<String, Shape> shapes = new HashMap<>();
  private static final ObjectPool<Shape> pool = new ObjectPool<>(() -> {
    // 创建一个新的图形对象
    return new Rectangle("");
  });

  public static Shape getShape(String color) {
    Shape shape = pool.acquire();
    shape.setColor(color);
    return shape;
  }

  public static void releaseShape(Shape shape) {
    pool.release(shape);
  }
}

// 使用享元模式
public class Main {
  public static void main(String[] args) {
    // 创建 10 个不同颜色的矩形对象
    List<Shape> shapes = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
      Shape shape = ShapeFactory.getShape("Color " + i);
      shape.draw();
      shapes.add(shape);
    }

    // 释放所有图形对象
    for (Shape shape : shapes) {
      ShapeFactory.releaseShape(shape);
    }
  }
}

在这个例子中,我们定义了一个 ObjectPool 类来管理对象的创建和销毁。该类包含一个 Map 对象,用来存储可以复用的对象,并实现了 acquire 和 release 方法来分别获取和释放对象。 在 ShapeFactory 类中,我们创建了一个 ObjectPool 对象来管理图形对象的创建和销毁。在 getShape 方法中,我们通过调用 ObjectPool 的 acquire 方法来从对象池中获取一个可用的图形对象,然后设置它的颜色并返回。在 releaseShape 方法中,我们通过调用 ObjectPool 的 release 方法来将图形对象释放回对象池中,以便下次使用。

网页浏览器中使用享元模式来管理缓存

是在网页浏览器中使用享元模式来管理缓存。在这种情况下,每个缓存的网页可以被表示为一个享元对象,其中包含可以共享的内部状态(如网页内容、图片等)和独立的外部状态(如网页的 URL 等)。这样,当用户浏览网页时,如果该网页已经被缓存过,就可以直接从缓存中加载该网页,从而减少网络流量,提高浏览器的性能。

// 定义页面接口
interface Page {
  void render(String url);
}

// 定义具体页面类(内部状态类)
class HtmlPage implements Page {
  private String content;

  public HtmlPage(String content) {
    this.content = content;
  }

  public void render(String url) {
    System.out.println("Rendering HTML page with URL " + url + " and content " + content);
  }
}

// 定义享元工厂类
class PageFactory {
  private static final Map<String, Page> pages = new HashMap<>();

  public static Page getPage(String url) {
    Page page = pages.get(url);
    if (page == null) {
      // 从网络加载页面内容
      String content = loadPageContentFromNetwork(url);
      page = new HtmlPage(content);
      pages.put(url, page);
    }
    return page;
  }
}

// 使用享元模式
public class Main {
  public static void main(String[] args) {
    // 渲染 10 个不同 URL 的 HTML 页面
    for (int i = 0; i < 10; i++) {
      Page page = PageFactory.getPage("http://www.example.com/" + i);
      page.render("http://www.example.com/" + i);
    }
  }
}

在这个例子中,我们定义了一个 Page 接口表示网页,并定义了一个 HtmlPage 类来实现该接口。然后,我们定义了一个 PageFactory 类来管理网页对象的创建和缓存。该类包含一个 Map 对象,用来存储已经加载过的网页对象,并实现了 getPage 方法来获取指定 URL 的网页对象。如果该网页对象还没有被加载,则需要从网络加载页面内容,并创建一个新的网页对象,然后将该对象添加到 Map 中,并返回。如果该网页对象已经被加载过,则直接从 Map 中获取并返回。

最后,在 main 方法中,我们通过循环来获取并渲染 10 个不同 URL 的网页对象。由于我们使用了享元模式,因此每个 URL 对应的网页只会被加载一次,并被缓存在 Map 中,从而节省了系统资源,提高了系统性能。