黑马程序员技术交流社区

标题: 关于享元模式的问题 [打印本页]

作者: 吴亨    时间: 2012-2-8 11:02
标题: 关于享元模式的问题
本帖最后由 吴亨 于 2012-2-10 19:46 编辑

   对于享元模式那节课,感到似懂非懂。只能理解到对于有共同需求的数据,只搞一个对象,让他们共同使用。不知道准不准确?
  可能是没接触过实例,有会的兄弟能给我举一个具体实例吗?
作者: 李泽霖    时间: 2012-2-8 11:54
网上的一些资源,感觉蛮好的,分享一下:
Flyweight : 次最轻量级的拳击选手。即粒度最小。
因此,享元模式的目的是采用共享技术解决大量细粒度对象的爆炸问题。
图:

四. 享元模式应用之QQ聊天
我们不妨假设QQ是在服务器端将每次的对话都抽象出来形成了一个类。于是代 码如下:
class People
{
    private string name;
    private int age;
    public string Name
    {
        get
        {
            return name;
        }
    }
    public int Age
    {
        get
        {
            return age;
        }
        set
        {
            age = value;
        }
    }
    public People(string

name, int age)
    {
        this.name = name;
        this.age = age;
    }
}

class Chat
{
    private People

boy;
    private People

girl;
    private string chatContent;

    public Chat(People

p1, People p2)
    {
        this.boy = p1;
        this.girl = p2;
    }
    public string ChatContent
    {
        get
        {
            return chatContent;
        }
        set
        {
            chatContent = value;
        }
    }
    public People Boy
    {
        get
        {
            return boy;
        }
    }
    public People Girl
    {
        get
        {
            return girl;
        }
    }
}
若每次二者聊天时均将Chat实例化为一个对象,如下:
class Program
{
    static void Main(string[] args)
    {
        People boy=new

People("PrettyBoy",20);
        People girl=new

People("BeautifulGirl",18);
        Chat chat = new

Chat(boy, girl);
        chat.ChatContent = "I love you";
        ChatServer.Send(chat);
    }
}
若如此,服务器就需要每次都去初始化一个对象,而当chatServer将此次聊天的记录发 送给客户机之后,这个对象便成了垃圾对象。这样,每小时几百万的聊天次数,便有了几百万的对象垃 圾。垃圾回收器GC便需要不停地去工作,回收对象。
这就对效率产生了极大的影响。于是,我们想办法,使用享元模式来解决这个 问题。
两者聊天,他们的聊天方是不变的,因此,我们可以在服务器端去维护一个这样的Chat 对象集合,如果该聊天对象已经存在,那么我们便重复去利用这个聊天对象。这样既减少了内存垃圾, 又节省了创建对象的时间。
代码如下:
class

FlyweightFactory
{
    private IDictionary<string, Chat> cache = new Dictionary
<string, Chat>();

    private void Add(Chat

c)
    {
        cache.Add(c.Boy.Name + "_" + c.Girl.Name, c);
    }

    public Chat

GetChat(People boy , People girl)
    {
        if (!cache.ContainsKey(boy.Name + "_" + girl.Name))
        {
            cache.Add(boy.Name + "_" + girl.Name,
new Chat(boy, girl));
        }
        return cache[boy.Name + "_" + girl.Name];
    }
}
于是,从客户端访问该FlyweightFactory即可。
这样,便有效控制了对象的数量。
五. 享元模式的.NET Framework典型应用——String
(在这里麻烦请教一下各位,我想在Reflector中,看一下String赋值的具体代 码,怎么找到呢?比如说string s=”111”;这一步的代码)
好,步入正题,让我们来看看享元模式在.NET Framework中的应用。
String 无论在.NET 还是 Java中,都是一个特殊的引用对象。
我们可以试想,出现了这样一段代码:
String s=”Hello world”;
String s1=”Hello world”;
那么是不是每次都要重新的去申请一块内存,然后去保存这个字符串呢?那么这样的话 是不是会效率很低呢?因为我们知道,字符串在实际使用中往往都是非常短暂的。他们通常是被读出来 之后,便直接展示给了客户。然后这个字符串的生命结束,变成垃圾。是不是很像我们刚才那个QQ聊天 对象呢?
于是在.NET 和 Java中,String都被以不变模式来进行设计。
我们来简单的分析一下String的驻留机制:在CLR被加载之后,就会在SystemDomain的托 管堆中去建立一个HashTable来维护String。
于是模拟代码如下:(伪代码)
Hashtable table;
if (!table.Contains("Hello

world"))
{
    table.Add("Hello world", &(new String("Hello world")));
}
return *(table["Hello

world"]);

代码写的有些乱,我来解释一下。
也就是说,我是在模拟一个string s=”Hello world”的过程。过程是,首先,他先去找Hashtable中目前是否存有Key为”Hello world”的项。如果不存在,那么就分配一块堆内存,存储这字符串,然后将地址作为Value,存储在 Hashtable中。如果存在的话,那么便直接找到该字符串所对应的地址,然后取出地址中的值。
用一个Hashtable来控制String对象的数量。这次您明白了么?
六 . 享元模式的扩展——对象池的应用
我们之前说,无论是字符串还是Object对象,使用享元模式都是去检查该对象是否存在 ,只要存在,那么便去重复使用。
那么是否有这样一种情况呢?
在峰期时,大量的客户端去访问同一个服务器,这个时候,如果只有一个对象的话,会 引起一定的并发问题。我的语言表述有些不大清楚。简单的说,就是每当一个对象被访问的时候,他必 须将自身锁定,并且防止其他客户去引用至该对象。
如果这个时候,我们依然去只维护一个对象的话,便会让大量的客户端处于等待队列中 。因此,我们需要靠维护一个对象池,允许在对象池中,维护一个类的多个对象。从而来实现一个服务 器空间与客户端等待时间的均衡问题。
因此,曾经,我们是在Dictionary中去维护一个Value为Object的缓存。而如今,我们便 需要在Dictionary中去维护一个Value为List<Object>的缓存。而这个List应当是限定数量的,能 保存同一类型Object的数组。
代码如下:(参考蜡笔小王的<设计模式——基于C#的工程化实现及扩展 >)
class ObjectCache
{
    private static IDictionary<Type, Object> cache;

    static ObjectCache()
    {
        cache = new Dictionary<Type, Object>();
    }

    public bool TryToGetObejct<T>(out T item, out bool

increasable) where T : class,IPoolable, new()
    {
        TryToAddObject<T>();
        return (cache[typeof(T)] as SizeRestrictedList<T>).
Acquire(out

item, out increasable);
    }
    private void TryToAddObject<T>() where T:class,
IPoolable,new()
    {
        if (!cache.ContainsKey(typeof(T)))
        {
            cache.Add(typeof(T), new SizeRestrictedList<T>());
        }
    }
}
public bool Acquire(out T item, out bool

increasable)
{
    increasable = cache.Count >= configuration.Max ? false

: true;
    item = null;
    if (cache.Count <= 0)
    {
        return false;
    }
    foreach (T cacheItem in

cache)
    {
        if (cacheItem != null

&& cacheItem.Unoccupied)
        {
            item = cacheItem;
            return true;
        }
    }
    return false;
}
七.  从微观到宏观——究竟多小才算Flyweight
我们上文说过,Flyweight是来解决细粒度对象的重用问题。那么我们去想想 ,究竟多小才算细粒度呢?
在上文中,我们一直在解决的都是对象的重用问题。那么我们向宏观方向去想 一想。
爱因斯坦的相对论:世间万物都是相对的。没有什么是绝对大的,只有相对的 小。那么我们来这样想。
我们是否可以重用一个模块,或者一个子系统呢?
八. 举一而反三—— 从享元到单例
其实,在一定意义上,我个人认为单例模式和享元模式的初衷是一样的。他们都是一个 基于空间和性能的模式。他们都是要控制对象的数量,而且实现方式本质上有着一些类似,就是首先查 询这个对象是否存在,然后返回这个对象。
那么从享元模式上的引申,我们就一样可以用到单例模式上了:
1. 我们可以不局限于单例,而是可以控制为多例。比如说:类似我前面对象 池的目的
2. 单例只是对象么?我们一样可以把子系统和模块单例!
看看他们的不同:
应该说享元模式是单例模式的一个延伸。享元模式通过享元工厂来控制多个对象的单例 化。而单例化解决的只是本身的单例问题!
九. 不要为模式而模式——何时才用享元
我一直觉得,模式不要乱用,乱用模式是学习的阶段,但是一旦在工作中,我们去乱用 模式,那么可能会造成很惨的后果。
那么究竟何时应该用享元模式呢?
1. 系统中要有大量的对象,这才值得用享元模式。否则你去维护一张对象表 ,就不值得了。
2. 对象的创建是会消耗大量时间的过程,并且对象占用较大内存。如果不是 ,那就让系统去创建吧。
3. 在B/S的系统中,个人感觉享元的应用相对较少,Web的无状态,加之我们完全在客户端进行一系列的复 杂逻辑,然后将之统一传递给Web服务器端,而不需要享元。享元主要应用还是在C/S及Winform的本地程 序上较多。
其余的,比如,关于外蕴状态和内蕴状态究竟何种应该使用享元的问题,如果不满足情 况,您也根本没有办法去使用享元。因此,我就不在这说那些蹩嘴的定义了。
十 . 享元总结
享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。


作者: 何洪森    时间: 2012-2-8 13:20
张老师的那节视频,我也有看过,只记得老师是这样解释的:享元模式(flyweight)就是
有很多小的对象,它们有很多的属性都相同,然后就把它们变成一个对象,那些不同的属性
就把它们变成方法的参数,成为外表状态,那些相同的属性称为这个对象的内部状态。可能
是我没有太深究,和应用。我认为享元模式就是去检查对象是否存在,如果存在的话,就可以
去重复使用。具体的例子你还是看看楼上的,也希望楼下的能给你一个满意的答复。



作者: 余海龙    时间: 2012-2-9 17:23
Flyweight模式(享元)
   Java深入到一定程度,就不可避免的碰到设计模式这一概念,了解设计模式,将使自己对java中的接口或抽象类应用有更深的理解.设计模式在java的中型系统中应用广泛,遵循一定的编程模式,才能使自己的代码便于理解,易于交流,Flyweight(享元模式)模式是比较常用的一个模式。
   Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中.
   定义:避免大量拥有相同内容的小类的开销(如耗费内存),使大家共享一个类(元类)。
   设计初衷:面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,无疑耗费内存,那么我们还是要"求同存异",找出这些对象群的共同点,设计一个元类,封装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的.
  
   在此 以咖啡外卖店为例 写了4个java类来描述说明Flyweight设计模式的实现方式;
   客户买咖啡下订单,订单只区分咖啡口味,如果下了1W个订单,而咖啡店只卖20种口味的咖啡,那么我们就没有必要生成1W个订单对象,通过享元模式我们只需要生成20个订单对象。
   这个例子举的不太好,但足以说明问题。下面是具体的代码。
  
  1、 Order.java         订单抽象类
  2、 FlavorOrder.java   订单实现类
  3、 FlavorFactory.java 订单生成工厂
  4、 Client.java        客户类、带有main方法的测试类

===============   1、 Order.java
package flyweight;
public abstract class Order {
  //执行卖出动作
  public abstract void sell();
  //获取咖啡口味
  public abstract String getFlavor();
}
===============   1   end

===============   2、 FlavorOrder.java
package flyweight;
public class FlavorOrder extends Order{
  private String flavor;

  public FlavorOrder(String flavor){
      this.flavor = flavor;
  }

  public String getFlavor(){
      return this.flavor;
  }
  public void sell(){
      System.out.println("卖出一杯 [" + flavor + "]。" );
  }
}
===============   2   end

===============   3、 FlavorFactory.java
package flyweight;
import java.util.HashMap;
import java.util.Map;
public class FlavorFactory {
  //订单池
  private Map<String,Order> flavorPool = new HashMap<String,Order>(20);
  //静态工厂,负责生成订单对象
  private static FlavorFactory flavorFactory = new FlavorFactory();
  private FlavorFactory() {}
  public static FlavorFactory getInstance() {
    return flavorFactory;
  }
  //获得订单
  public Order getOrder(String flavor) {
    Order order = null;
    if(flavorPool.containsKey(flavor)){
      order = flavorPool.get(flavor);
    }else{
      //获得新口味订单
      order = new FlavorOrder(flavor);
      //放入对象池
      flavorPool.put(flavor, order);   
    }
    return order;
  }
  //获得已经卖出的咖啡全部口味数量
  public int getTotalFlavorsMade() {
    return flavorPool.size();
  }
}
===============   3   end

===============   4、 Client.java
package flyweight;
import java.util.ArrayList;
import java.util.List;
public class Client {
  //客户下的订单
  private static List<Order> orders = new ArrayList<Order>(100);
  //订单对象生成工厂
  private static FlavorFactory flavorFactory;
  //增加订单
  private static void takeOrders(String flavor) {
    orders.add(flavorFactory.getOrder(flavor));
  }
  public static void main(String[] args) {
    //订单生成工厂
    flavorFactory = FlavorFactory.getInstance();
    //增加订单
    takeOrders("摩卡");
    takeOrders("卡布奇诺");
    takeOrders("香草星冰乐");
    takeOrders("香草星冰乐");
    takeOrders("拿铁");
    takeOrders("卡布奇诺");
    takeOrders("拿铁");
    takeOrders("卡布奇诺");
    takeOrders("摩卡");
    takeOrders("香草星冰乐");
    takeOrders("卡布奇诺");
    takeOrders("摩卡");
    takeOrders("香草星冰乐");
    takeOrders("拿铁");
    takeOrders("拿铁");
    //卖咖啡
    for(Order order : orders){
      order.sell();
    }
    //打印生成的订单java对象数量
    System.out.println("n客户一共买了 " +  orders.size() + " 杯咖啡! ");
    //打印生成的订单java对象数量
    System.out.println("n共生成了 " +  flavorFactory.getTotalFlavorsMade() + " 个 FlavorOrder java对象! ");
  }
}
===============   4   end




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2