分享
 
 
 

大卫的Design Patterns学习笔记10:Flyweight

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

一、概述

当类的部分属性在整个系统中的多个对象间重复出现时,一个通常的作法是将重复出现的属性从类定义中分离出来,并在多个对象间通过共享来节约系统开销,这种情况在界面相关的应用中尤其常见。如用于浏览目录内容的树,每个节点前面有一个Icon用于表示该节点的类型,如果将该Icon保存在每个节点的数据结构中,无疑是一种巨大的浪费,这时候通过共享(每个节点只需要保存一个所使用Icon的标识即可,在C++中,可以通过引用、指针或ID标识等来实现)可以提高性能,并且当被共享的次数越多时,这种提高就越明显。

Flyweight(享元)模式采用共享来避免大量拥有相同内容对象的开销,这种开销中最常见、最直观的就是内存的损耗,Flyweight模式以共享的方式高效地支持大量的细粒度对象。

二、结构

存在两种典型的运用Flyweight模式的情形:单纯Flyweight模式和复合Flyweight模式。

单纯Flyweight模式的类图结构如下:

图1:单纯Flyweight模式类图示意

在上面的类图中包括以下组成部分:

1、抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口,通过这个接口Flyweight可以接受并作用于外部状态(Extrinsic State)。

2、具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态(Intrinsic State)的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。

3、享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

4、客户端(Client)角色:本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。

单纯Flyweight模式在收到对象创建请求时检查是否该类型对象已存在,若存在,则直接返回该对象,否则,创建新的对象。单纯Flyweight模式的的结构十分简单,其思想与Singleton模式及Simple Factory Pattern也有几分相似之处,但单纯Flyweight模式注重对多个对象(数量不确定)的共享,希望通过这种共享来达到效率或者空间上的节省,而Singleton模式注重对对象创建数目的控制,Simple Factory Pattern则注重对对象创建细节的屏蔽和分离。

复合Flyweight模式的类图结构如下:

图2:复合Flyweight模式类图示意

在上面的类图中包括以下组成部分:

1)抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。

2)具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。

3)复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合。

4)享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!

5)客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。

复合Flyweight模式通过组合的方式来结合关联的具有相同Extrinsic属性而Intrinsic属性不同的多个单纯Flyweight对象,是Flyweight模式的一种延伸。

三、应用

在以下情况下可以考虑使用Flyweight模式:

1)系统中有大量的对象,他们使系统的效率降低。

2)这些对象的状态可以分离出所需要的内外两部分。

GoF的DP一书举出了一个字处理的例子,由于在通常情况下,一篇文档中字符及其字体颜色属性的组合并不会太多(如果是纯文本文件,我认为没有使用Flyweight模式的必要,因为存一个字符跟存一个字符的索引的消耗是相当的),根据GoF的统计,一篇包含180,000个字符(英文)的文档需要分配的Flyweight的数目大约只有480个。因此,通过保存各字符的索引(通过字符的颜色、大小等信息进行分类,可以对保存的策略进行进一步优化,如DP一书提到采用B-Tree进行存储),而不是实际保存每一个字符以及其大小、颜色信息,可以大大节约实际使用的内存大小。但是,话说回来,虽然我没有实际测试过,但是,个人认为这种存储策略可能在很多情况下并非最优,对于类似的情况,其它一些处理策略,如采用类似位图行程压缩的方式存放属性变化信息,而将文档内容以纯文本形式存放,在很多情况下可能空间使用效率也非常高,只是可能需要涉及比较复杂的逻辑处理。

我不知道是由于Flyweight模式的名字的原因或者其它什么原因,在通常所能看到的关于Flyweight模式的材料中总是假设被共享的对象很小,我并不同意这种观点。实际上,个人认为,Flyweight模式对于大的对象(可能内存消耗大,也可能创建成本高)更有价值,如连接池/线程池就是共享大的对象的最好的例证,只是由于大的对象往往具有更多的属性,这在一定程度上阻碍了共享的发生。

四、优缺点

享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:使得系统逻辑变得更加复杂,而且在一定程度上外蕴状态影响了系统的速度。

同时,外蕴状态和内蕴状态的划分,以及两者关系的对应关系也是必须考虑的因素。只有将内外划分妥当才能使内蕴状态发挥它应有的作用;如果划分失误,可能在空间和时间两个方面都得不偿失。

五、举例

上面已经说过,准确划分Intrinsic State和Extrinsic State是应用Flyweight模式的关键,划分时应保证内蕴状态尽可能多,而外蕴状态尽可能少,以充分利用共享减小重复消耗。

作为一个设计良好的程序库,在JDK中存在着一些运用Flyweight模式的例子,如BorderFactory就是一个享元工厂类,下面的例子输出为Yes:

import javax.swing.*;

import javax.swing.border.*;

public class BorderTest {

public static void main(String[] args) {

BorderTest test = new BorderTest();

}

public BorderTest() {

Border border1 = BorderFactory.createRaisedBevelBorder();

Border border2 = BorderFactory.createRaisedBevelBorder();

if(border1 == border2)

System.out.println("Yes. Two borders are shared");

else

System.out.println("No. Two borders are NOT shared");

}

}

此外,Java中的String和JTree、JTable等也通过共享使用部分公共元素,使得性能得以提升。

下面举一个绘图的例子,通常来讲,图形会包含线型、线宽、颜色等信息,在一个包含大量线条(直线或曲线)的绘图系统中,如果在每一个线条对象中均保存这些信息,无疑是一种巨大的浪费。为此,我们将线条对象的属性进行如下划分:

Intrinsic State:

Color

LineWidth

...

Extrinsic State (for line):

Start Point

End Point

根据以上划分,相应示例的实现如下(Java Code):

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.event.*;

import java.util.ArrayList;

public class FlyweightTest extends JFrame {

private static final Color colors[] = { Color.red, Color.blue,

Color.yellow, Color.orange,

Color.black, Color.white };

private static final int WINDOW_WIDTH = 400, WINDOW_HEIGHT = 400, NUMBER_OF_LINES = 100;

private ArrayList vLine = new ArrayList();

JButton button = new JButton("draw lines");

final JPanel panel = new JPanel();

public static void main(String[] args) {

FlyweightTest test = new FlyweightTest();

test.show();

}

public FlyweightTest() {

super("Flyweight Test");

Container contentPane = getContentPane();

contentPane.setLayout(new BorderLayout());

contentPane.add(panel, BorderLayout.CENTER);

contentPane.add(button, BorderLayout.SOUTH);

setBounds(20, 20, WINDOW_WIDTH, WINDOW_HEIGHT);

setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

button.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent event) {

vLine.clear();

for(int i = 0; i < NUMBER_OF_LINES; i++) {

int index = LineFlyweightFactory.getIndex(getRandomColor(), getRandomWidth());

vLine.add(new Line(new Point(getRandomX(), getRandomY()), new Point(getRandomX(), getRandomY()), index));

}

repaint();

}

});

}

private int getRandomX() {

return (int)(Math.random() * WINDOW_WIDTH);

}

private int getRandomY() {

return (int)(Math.random() * WINDOW_HEIGHT);

}

private Color getRandomColor() {

return colors[(int)(Math.random() * colors.length)];

}

private int getRandomWidth() {

return (int)(Math.random() * 5);

}

public void paint(Graphics g) {

super.paint(g);

Graphics gp = panel.getGraphics();

Line line;

for(int i = 0; i < vLine.size(); i++) {

line = (Line)vLine.get(i);

line.draw(gp);

}

}

}

// class which contains extrinsic state and reference to flyweight

class Line {

private Point start, end;

private int index; // reference to flyweight

public Line(Point start, Point end, int index) {

this.start = start;

this.end = end;

this.index = index;

}

public void draw(Graphics g) {

LineFlyweight line = LineFlyweightFactory.getLine(index);

line.draw(g, start.x, start.y, end.x, end.y); // pass extrinsic state to flyweight

}

}

// Flyweight

class LineFlyweight {

// intrinsic state

private Color color;

private BasicStroke stroke;

public LineFlyweight(Color color, float lineWidth) {

this.color = color;

stroke = new BasicStroke(lineWidth);

}

public boolean equals(Color color, int lineWidth) {

if (this.color.equals(color) && (stroke.getLineWidth() == lineWidth))

return true;

return false;

}

public void draw(Graphics g, int x, int y, int x2, int y2) {

Graphics2D g2 = (Graphics2D)g;

g2.setColor(color);

g2.setStroke(stroke);

g2.drawLine(x, y, x2, y2);

}

}

// Flywight Factory

class LineFlyweightFactory {

private static final ArrayList vFlyweight = new ArrayList();

public static int getIndex(Color color, int lineWidth) {

LineFlyweight line;

for (int i = 0; i < vFlyweight.size(); i++) {

line = (LineFlyweight)vFlyweight.get(i);

if (line.equals(color, lineWidth))

return i;

}

line = new LineFlyweight(color, lineWidth);

vFlyweight.add(line);

System.out.println("Creating " + color + " line with width = " + lineWidth);

return vFlyweight.size() - 1;

}

public static LineFlyweight getLine(int index) {

if (index > vFlyweight.size())

return null;

return (LineFlyweight)vFlyweight.get(index);

}

}

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有