zoukankan      html  css  js  c++  java
  • 【设计模式】享元模式

    享元模式

      在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。

    展示网站项目需求示例  

      小型的外包项目团队,给客户 A 做一个产品展示网站,客户 A 的朋友 B 感觉效果不错,也希望做一个这样的产品展示网站,但是要求都有些不同:

      1) 有客户要求以新闻的形式发布

      2) 有客户要求以博客的形式发布

      3) 有客户要求以微信公众号的形式发布

    传统方案解决网站展现项目

      

    传统方案解决网站展现项目-问题分析:

    1. 需要的网站结构相似度很高,而且都不是高访问量网站,如果分成多个虚拟空间来 处理,相当于一个相同网站的实例对象很多,造成服务器的资源浪费

    2. 解决思路:整合到一个网站中,共享其相关的代码和数据,对于硬盘、内存、CPU、 数据库空间等服务器资源都可以达成共享,减少服务器资源

    3. 对于代码来说,由于是一份实例,维护和扩展都更加容易

    4. 上面的解决思路就可以使用 享元模式 来解决

    享元模式基本介绍  

    1. 享元模式(Flyweight Pattern)也叫蝇量模式:运用共享技术有效地支持大量细粒度的对象

    2. 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个

    3. 享元模式能够解决重复对象的内存浪费的问题, 当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率

    4. 享元模式经典的应用场景就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,享元模式是池技术的重要实现方式

      

    享元模式的主要优点是

      相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

    其主要缺点是:

    1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性

    2. 读取享元模式的外部状态会使得运行时间稍微变长

    享元模式的结构与实现

    模式的结构

    1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入

    2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口

    3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中

    4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象

      图 1 是享元模式的结构图。图中的 UnsharedConcreteFlyweight 是与享元角色,里面包含了非共享的外部状态信息 info;而 Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;FlyweightFactory 是享元工厂角色,它通过关键字 key 来管理具体享元;客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。

      

    享元模式中存在以下两种状态:

    1. 内部状态,即不会随着环境的改变而改变的可共享部分

    2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化

      比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态。

      举个例子:围棋理论上有361个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。

    模式的实现

      代码如下:

     1 package flyweight;
     2 import java.util.HashMap;
     3  
     4 public class FlyweightPattern {
     5     
     6     public static void main(String[] args) {
     7         FlyweightFactory factory=new FlyweightFactory();
     8         Flyweight f01 = factory.getFlyweight("a");11         Flyweight f11 = factory.getFlyweight("b");13         f01.operation(new UnsharedConcreteFlyweight("第1次调用a。"));       
    14         f01.operation(new UnsharedConcreteFlyweight("第2次调用a。"));       
    15         f01.operation(new UnsharedConcreteFlyweight("第3次调用a。"));       
    16         f11.operation(new UnsharedConcreteFlyweight("第1次调用b。"));       
    17         f11.operation(new UnsharedConcreteFlyweight("第2次调用b。"));
    18     }
    19 }
    20  
    21  
    22 // 非享元角色
    23 class UnsharedConcreteFlyweight {
    24  
    25     private String info;
    26  
    27     UnsharedConcreteFlyweight(String info) {
    28         this.info = info;
    29     }
    30  
    31     public String getInfo() {
    32         return info;
    33     }
    34  
    35     public void setInfo(String info) {
    36         this.info = info;
    37     }
    38 }
    39  
    40  
    41 // 抽象享元角色
    42 interface Flyweight {
    43     public void operation(UnsharedConcreteFlyweight state);
    44 }
    45  
    46  
    47 // 具体享元角色
    48 class ConcreteFlyweight implements Flyweight {
    49  
    50     private String key;
    51  
    52     ConcreteFlyweight(String key) {
    53         this.key = key;
    54         System.out.println("具体享元" + key + "被创建!");
    55     }
    56  
    57     public void operation(UnsharedConcreteFlyweight outState) {
    58         System.out.print("具体享元" + key + "被调用,");
    59         System.out.println("非享元信息是:" + outState.getInfo());
    60     }
    61 }
    62  
    63  
    64 // 享元工厂角色
    65 class FlyweightFactory {
    66     private HashMap<String, Flyweight> flyweights = new HashMap<String, Flyweight>();
    67  
    68     public Flyweight getFlyweight(String key) {
    69  
    70         Flyweight flyweight = (Flyweight)flyweights.get(key);
    71  
    72         if(flyweight != null) {
    73             System.out.println("具体享元"+key+"已经存在,被成功获取!");
    74         } else {
    75             flyweight = new ConcreteFlyweight(key);
    76             flyweights.put(key, flyweight);
    77         }
    78         return flyweight;
    79     }
    80 }

    享元模式解决网站展现项目

      

      代码:

     1 public class Client {
     2 
     3     public static void main(String[] args) {
     4         // 创建一个工厂类
     5         WebSiteFactory factory = new WebSiteFactory();
     6         // 客户要一个以新闻形式发布的网站
     7         WebSite webSite1 = factory.getWebSiteCategory("新闻");
     8         webSite1.use(new User("tom"));
     9         // 客户要一个以博客形式发布的网站
    10         WebSite webSite2 = factory.getWebSiteCategory("博客");
    11         webSite2.use(new User("jack"));
    12         // 客户要一个以博客形式发布的网站
    13         WebSite webSite3 = factory.getWebSiteCategory("博客");
    14         webSite3.use(new User("smith"));
    15         // 客户要一个以博客形式发布的网站
    16         WebSite webSite4 = factory.getWebSiteCategory("博客");
    17         webSite4.use(new User("king"));
    18         System.out.println("网站的分类共 = " + factory.getWebSiteCount());
    19     }
    20 }
     1 // 具体网站
     2 public class ConcreteWebSite extends WebSite {
     3 
     4     // 共享的部分,内部状态
     5     // 网站发布的形式(类型)
     6     private String type = "";
     7 
     8     // 构造器
     9     public ConcreteWebSite(String type) {
    10         this.type = type;
    11     }
    12 
    13     @Override
    14     public void use(User user) {
    15         System.out.println("网站的发布形式为:" + type + " 在使用中 .. 使用者是" + user.getName());
    16     }
    17 }
     1 public class User {
     2 
     3     private String name;
     4 
     5     public User(String name) {
     6         super();
     7         this.name = name;
     8     }
     9 
    10     public String getName() {
    11         return name;
    12     }
    13 
    14     public void setName(String name) {
    15         this.name = name;
    16     }
    17 
    18 }
    1 public abstract class WebSite {
    2 
    3     // 抽象方法
    4     public abstract void use(User user);
    5 }
     1 // 网站工厂类,根据需要返回压一个网站
     2 public class WebSiteFactory {
     3 
     4     // 集合,充当池的作用
     5     private HashMap<String, ConcreteWebSite> pool = new HashMap<>();
     6 
     7     // 根据网站的类型,返回一个网站,如果没有就创建一个网站,并放入到池中,并返回
     8     public WebSite getWebSiteCategory(String type) {
     9 
    10         if(!pool.containsKey(type)) {
    11             // 就创建一个网站,并放入到池中
    12             pool.put(type, new ConcreteWebSite(type));
    13         }
    14 
    15         return (WebSite)pool.get(type);
    16     }
    17 
    18     // 获取网站分类的总数 (池中有多少个网站类型)
    19     public int getWebSiteCount() {
    20         return pool.size();
    21     }
    22 }

    享元模式在 JDK-Interger 的应用源码分析

      

      

      代码示例:

     1 public class FlyWeight {
     2 
     3     public static void main(String[] args) {
     4         // 如果 Integer.valueOf(x) x 在 -128 --- 127 直接,就是使用享元模式返回,如果不在
     5         // 范围类,则仍然 new
     6         // 小结:
     7         // 1. 在 valueOf 方法中,先判断值是否在 IntegerCache 中,如果不在,就创建新的 Integer(new),否则,就直接从缓存池返回
     8         // 2. valueOf 方法,就使用到享元模式
     9         // 3. 如果使用 valueOf 方法得到一个 Integer 实例,范围在 -128 - 127 ,执行速度比 new 快
    10         // 得到 x 实例,类型
    11         Integer x = Integer.valueOf(127);
    12         // 得到 y 实例,类型
    13         Integer y = new Integer(127);
    14         // ..
    15         Integer z = Integer.valueOf(127);
    16 
    17         Integer w = new Integer(127);
    18 
    19         // 大小,true
    20         System.out.println(x.equals(y));
    21         // false
    22         System.out.println(x == y);
    23         // true
    24         System.out.println(x == z);
    25         // false
    26         System.out.println(w == x);
    27         // false
    28         System.out.println(w == y);
    29 
    30         Integer x1 = Integer.valueOf(200);
    31         Integer x2 = Integer.valueOf(200);
    32         // false
    33         System.out.println("x1==x2" + (x1 == x2));
    34 
    35     }
    36 }

    享元模式的应用场景

      前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

    1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源

    2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态

    3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式

    享元模式的注意事项和细节

    1. 在享元模式这样理解,“享”就表示共享,“元”表示对象

    2. 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式

    3. 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable存储

    4. 享元模式大大减少了对象的创建,降低了程序内存的占用,提高效率

    5. 享元模式提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方

    6. 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制

    7.  享元模式经典的应用场景是需要缓冲池的场景,比如String常量池、数据库连接池

    原文链接:https://blog.csdn.net/qq784515681/article/details/106516122

  • 相关阅读:
    Java Web 网络留言板2 JDBC数据源 (连接池技术)
    Java Web 网络留言板3 CommonsDbUtils
    Java Web ConnectionPool (连接池技术)
    Java Web 网络留言板
    Java Web JDBC数据源
    Java Web CommonsUtils (数据库连接方法)
    Servlet 起源
    Hibernate EntityManager
    Hibernate Annotation (Hibernate 注解)
    wpf控件设计时支持(1)
  • 原文地址:https://www.cnblogs.com/h--d/p/14556588.html
Copyright © 2011-2022 走看看