分享
 
 
 

翻译TIPatterns--太多(Too many)

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

太多(Too many)

享元(Flyweight):太多对象(too many objects)

Flyweidht模式可能让人感到奇怪的一点是, 它(在其它模式的辅助下)是改善性能(performance hack)的一种方法。 通常来说比较好的做法就是简单的把系统里所有东西都弄成对象,但是有时候这样做会使得产生的对象数量非常巨大,这可能会导致系统过于缓慢或者内存耗尽。

Flyweight模式通过减少对象数量来解决这个问题。为了达到这个目的,需要外部化(externalize)原本属于对象一些数据,这样使得看起来对象的数量要比实际的多。但是,这同时也增加了应用这些对象时接口的复杂度,因为你必须传入额外的信息用以告诉调用方法如何找到那些外部化的信息。

作为一个非常简单的例子,考虑一个DataPoint对象,它包含一个int和float类型的数据成员,还有一个id数据成员用来表示对象编号。假设你需要创建一百万个这样的对象,然后对它们进行操作,像下面这样:

//: flyweight:ManyObjects.java

class DataPoint {

private static int count = 0;

private int id = count++;

private int i;

private float f;

public int getI() { return i; }

public void setI(int i) { this.i = i; }

public float getF() { return f; }

public void setF(float f) { this.f = f; }

public String toString() {

return "id: " + id + ", i = " + i + ", f = " + f;

}

}

public class ManyObjects {

static final int size = 1000000;

public static void main(String[] args) {

DataPoint[] array = new DataPoint[size];

for(int i = 0; i < array.length; i++)

array[i] = new DataPoint();

for(int i = 0; i < array.length; i++) {

DataPoint dp = array[i];

dp.setI(dp.getI() + 1);

dp.setF(47.0f);

}

System.out.println(array[size -1]);

}

} ///:~

上面这个程序运行下来可能需要几秒钟,这取决于你的机器。更复杂的对象和更多的操作可能会使额外开销难以维持。为了解决这个问题,我们通过外部化原本属于DataPoint数据成员的方法,使对象个数从一百万个减少到一个。

//: flyweight:FlyWeightObjects.java

class ExternalizedData {

static final int size = 5000000;

static int[] id = new int[size];

static int[] i = new int[size];

static float[] f = new float[size];

static {

for(int i = 0; i < size; i++)

id[i] = i;

}

}

class FlyPoint {

private FlyPoint() {}

public static int getI(int obnum) {

return ExternalizedData.i[obnum];

}

public static void setI(int obnum, int i) {

ExternalizedData.i[obnum] = i;

}

public static float getF(int obnum) {

return ExternalizedData.f[obnum];

}

public static void setF(int obnum, float f) {

ExternalizedData.f[obnum] = f;

}

public static String str(int obnum) {

return "id: " +

ExternalizedData.id[obnum] +

", i = " +

ExternalizedData.i[obnum] +

", f = " +

ExternalizedData.f[obnum];

}

}

public class FlyWeightObjects {

public static void main(String[] args) {

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

FlyPoint.setI(i, FlyPoint.getI(i) + 1);

FlyPoint.setF(i, 47.0f);

}

System.out.println(

FlyPoint.str(ExternalizedData.size -1));

}

} ///:~

这么一来,因为所有数据都存放在ExternalizedData里,所以每个对FlyPoint方法的调用都必须包含对ExternalizedData的索引。出于一致性的考虑,而且也是为了提醒读者注意(FlyPoint)各个方法的隐含(implicit)this指针的相似性,“this 索引”是作为第一个参数传入的。

自然,这里又要对过早优化(premature optimization)提出警告。“先让程序能够运行起来,如果确有这个必要的话,再想办法提高速度。”再说一次,用profiler工具来检测性能瓶颈,而不要靠猜测。

装饰模式(Decorator):太多类(too many classes)

Decorator模式是指:用分层的对象(layered objects)动态地和透明地给单个对象添加功能(responsibilities)。

当使用派生类方法产生过多(不灵活的)类的时候考虑应用Decorator模式。

所有外覆在原始对象上的装饰类都必须有同样的基本接口。

动态proxy/surrogate?

Decorator模式对于非常规的继承结构特别管用。

权衡:使用decoator模式通常会使代码变得比较复杂。

基本decorator结构

一个关于咖啡的例子A coffee example

考虑去当地的一家咖啡馆BeanMeUp喝杯咖啡。那里通常会提供许多种不同的饮料-蒸馏咖啡, lattes, 茶, 冰咖啡,热巧克力饮品,而且还有许多额外的添加物,比如生奶油或者额外的蒸馏咖啡粉末(当然这些是要另外收钱的)。你也可以不支付额外的费用而改变你的饮料,比如你可以要求把普通咖啡换成脱咖啡因咖啡。

有一点是非常清楚的,如果我们想给上面所有这些饮料和它们的组合建模,那将会产生一张非常大的类图。为了能够清楚的说明问题,我们只考虑所有咖啡品种的一个子集:Espresso, Espresso Con Panna, Café Late, Cappuccino and Café Mocha. 我们提供2种额外的添加物-生奶油(“whipped”)和蒸馏咖啡粉,和三种可选择的饮品-脱咖啡因咖啡,steamed milk ("wet") 和 foamed milk ("dry").

每种组合对应一个类Class for each combination

一个解决办法是给每种组合都创建一个类。用它来描述每种饮料(的组成)和价格等等。最终产生的饮料单是巨大的,整个类图的一部分可能看起来是这样的:

下面是Cappuccino这种组合的一种简单实现:

class Cappuccino {

private float cost = 1;

private String description = "Cappucino";

public float getCost() {

return cost;

}

public String getDescription() {

return description;

}

}

使用这种方法最关键的一点是找到你需要的那种特定组合。一旦找到你想要的饮品,下面CoffeeShop类的代码演示了如何使用你想要的饮品类。

//: decorator:nodecorators:CoffeeShop.java

// Coffee example with no decorators

package decorator.nodecorators;

import junit.framework.*;

class Espresso {}

class DoubleEspresso {}

class EspressoConPanna {}

class Cappuccino {

private float cost = 1;

private String description = "Cappucino";

public float getCost() {

return cost;

}

public String getDescription() {

return description;

}

}

class CappuccinoDecaf {}

class CappuccinoDecafWhipped {}

class CappuccinoDry {}

class CappuccinoDryWhipped {}

class CappuccinoExtraEspresso {}

class CappuccinoExtraEspressoWhipped {}

class CappuccinoWhipped {}

class CafeMocha {}

class CafeMochaDecaf {}

class CafeMochaDecafWhipped {

private float cost = 1.25f;

private String description =

"Cafe Mocha decaf whipped cream";

public float getCost() {

return cost;

}

public String getDescription() {

return description;

}

}

class CafeMochaExtraEspresso {}

class CafeMochaExtraEspressoWhipped {}

class CafeMochaWet {}

class CafeMochaWetWhipped {}

class CafeMochaWhipped {}

class CafeLatte {}

class CafeLatteDecaf {}

class CafeLatteDecafWhipped {}

class CafeLatteExtraEspresso {}

class CafeLatteExtraEspressoWhipped {}

class CafeLatteWet {}

class CafeLatteWetWhipped {}

class CafeLatteWhipped {}

public class CoffeeShop extends TestCase {

public void testCappuccino() {

// This just makes sure it will complete

// without throwing an exception.

// Create a plain cappuccino

Cappuccino cappuccino = new Cappuccino();

System.out.println(cappuccino.getDescription()

+ ": $" + cappuccino.getCost());

}

public void testCafeMocha() {

// This just makes sure it will complete

// without throwing an exception.

// Create a decaf cafe mocha with whipped

// cream

CafeMochaDecafWhipped cafeMocha =

new CafeMochaDecafWhipped();

System.out.println(cafeMocha.getDescription()

+ ": $" + cafeMocha.getCost());

}

public static void main(String[] args) {

junit.textui.TestRunner.run(CoffeeShop.class);

}

} ///:~

对应的输出结果如下:

Cappucino: $1.0

Cafe Mocha decaf whipped cream: $1.25

你可以看到,创建你想要的组合是非常简单的,因为你只要创建某个类的一个实例就可以了。但是,这种方法存在许多问题。首先,这些组合都是静态固定死的,客户想要的每个组合都必须预先创建好。其次,整个组合生成的菜单实在是太大了,要找到某个特定的组合非常困难而且耗时。

Decorator方法

另外可以采用把这些饮料拆成类似于esspresso和foamedmilk的组件(components),然后让顾客通过组合这些组件来描述某种特定的咖啡饮料。

我们使用Decorator模式,通过编程来实现上述想法。Decorator通过包裹(wrapping)一个组件来给它增加功能,但是Decorator兼容它所包裹的那个类的接口,这样,对component的包裹就是透明的。Decorator自身又可以被(别的Decorator)包裹而不丧失其透明性。

调用Decorator的某个方法,会依次调用到Decorator所包裹的component的方法,而component的方法可以在Decorator方法被调用之前或者之后被调用。

如果为DrinkComponent接口添加getTotalCost()和getDescription()方法,Esspresso看起来就会像下面的样子:

class Espresso extends Decorator {

private float cost = 0.75f;

private String description = " espresso";

public Espresso(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return component.getTotalCost() + cost;

}

public String getDescription() {

return component.getDescription() +

description;

}

}

你可以通过组合来创建一种饮品,就像下面的代码那样:

//: decorator:alldecorators:CoffeeShop2.java

// Coffee example using decorators

package decorator.alldecorators;

import junit.framework.*;

interface DrinkComponent {

String getDescription();

float getTotalCost();

}

class Mug implements DrinkComponent {

public String getDescription() {

return "mug";

}

public float getTotalCost() {

return 0;

}

}

abstract class Decorator implements DrinkComponent

{

protected DrinkComponent component;

Decorator(DrinkComponent component) {

this.component = component;

}

public float getTotalCost() {

return component.getTotalCost();

}

public abstract String getDescription();

}

class Espresso extends Decorator {

private float cost = 0.75f;

private String description = " espresso";

public Espresso(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return component.getTotalCost() + cost;

}

public String getDescription() {

return component.getDescription() +

description;

}

}

class Decaf extends Decorator {

private String description = " decaf";

public Decaf(DrinkComponent component) {

super(component);

}

public String getDescription() {

return component.getDescription() +

description;

}

}

class FoamedMilk extends Decorator {

private float cost = 0.25f;

private String description = " foamed milk";

public FoamedMilk(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return component.getTotalCost() + cost;

}

public String getDescription() {

return component.getDescription() +

description;

}

}

class SteamedMilk extends Decorator {

private float cost = 0.25f;

private String description = " steamed milk";

public SteamedMilk(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return component.getTotalCost() + cost;

}

public String getDescription() {

return component.getDescription() +

description;

}

}

class Whipped extends Decorator {

private float cost = 0.25f;

private String description = " whipped cream";

public Whipped(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return component.getTotalCost() + cost;

}

public String getDescription() {

return component.getDescription() +

description;

}

}

class Chocolate extends Decorator {

private float cost = 0.25f;

private String description = " chocolate";

public Chocolate(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return component.getTotalCost() + cost;

}

public String getDescription() {

return component.getDescription() +

description;

}

}

public class CoffeeShop2 extends TestCase {

public void testCappuccino() {

// This just makes sure it will complete

// without throwing an exception.

// Create a plain cappucino

DrinkComponent cappuccino = new Espresso(

new FoamedMilk(new Mug()));

System.out.println(cappuccino.

getDescription().trim() + ": $" +

cappuccino.getTotalCost());

}

public void testCafeMocha() {

// This just makes sure it will complete

// without throwing an exception.

// Create a decaf cafe mocha with whipped

// cream

DrinkComponent cafeMocha = new Espresso(

new SteamedMilk(new Chocolate(new Whipped(

new Decaf(new Mug())))));

System.out.println(cafeMocha.getDescription().

trim() + ": $" + cafeMocha.getTotalCost());

}

public static void main(String[] args) {

junit.textui.TestRunner.run(CoffeeShop2.class);

}

} ///:~

这种方法当然提供了最灵活和体积最小的菜单。你只需从很少几个组件(components)里挑出你需要的,但是拼接这些“关于咖啡的描述”就变的十分费劲。

如果你想描述纯的cappussino咖啡,你可以这么写:

new Espresso(new FoamedMilk(new Mug()));

但是创建“a decaf Café Mocha with whipped cream”就需要相当冗长的描述。

折中Compromise

用上面这种方法来描述某种咖啡饮品实在是太冗长了。一些组合是会经常被用到的,如果用一种更快的方法来描述它们会方便很多。

这里的第三种方法就是前面两种方法的组合,也就是灵活性和易用性相结合。

这种折中是通过创建合适大小的供常规选择的菜单来实现的,这个菜单基本上是不怎么变化的,但是如果你想给这些基本饮品加点伴侣(比如whipped cream, decaf等),那你可以用decorators

来改变基本饮品。这其实也是大多数咖啡馆提供的那种菜单。

下面就是常规饮品(basic selection)和加了伴侣之后的饮品 (decorated selection) 的一个例子:

//: decorator:compromise:CoffeeShop3.java

// Coffee example with a compromise of basic

// combinations and decorators

package decorator.compromise;

import junit.framework.*;

interface DrinkComponent {

float getTotalCost();

String getDescription();

}

class Espresso implements DrinkComponent {

private String description = "Espresso";

private float cost = 0.75f;

public float getTotalCost() {

return cost;

}

public String getDescription() {

return description;

}

}

class EspressoConPanna implements DrinkComponent {

private String description = "EspressoConPare";

private float cost = 1;

public float getTotalCost() {

return cost;

}

public String getDescription() {

return description;

}

}

class Cappuccino implements DrinkComponent {

private float cost = 1;

private String description = "Cappuccino";

public float getTotalCost() {

return cost;

}

public String getDescription() {

return description;

}

}

class CafeLatte implements DrinkComponent {

private float cost = 1;

private String description = "Cafe Late";

public float getTotalCost() {

return cost;

}

public String getDescription() {

return description;

}

}

class CafeMocha implements DrinkComponent {

private float cost = 1.25f;

private String description = "Cafe Mocha";

public float getTotalCost() {

return cost;

}

public String getDescription() {

return description;

}

}

abstract class Decorator implements DrinkComponent {

protected DrinkComponent component;

public Decorator(DrinkComponent component) {

this.component = component;

}

public float getTotalCost() {

return component.getTotalCost();

}

public String getDescription() {

return component.getDescription();

}

}

class ExtraEspresso extends Decorator {

private float cost = 0.75f;

public ExtraEspresso(DrinkComponent component) {

super(component);

}

public String getDescription() {

return component.getDescription() +

" extra espresso";

}

public float getTotalCost() {

return cost + component.getTotalCost();

}

}

class Whipped extends Decorator {

private float cost = 0.50f;

public Whipped(DrinkComponent component) {

super(component);

}

public float getTotalCost() {

return cost + component.getTotalCost();

}

public String getDescription() {

return component.getDescription() +

" whipped cream";

}

}

class Decaf extends Decorator{

public Decaf(DrinkComponent component) {

super(component);

}

public String getDescription() {

return component.getDescription() + " decaf";

}

}

class Dry extends Decorator {

public Dry(DrinkComponent component) {

super(component);

}

public String getDescription() {

return component.getDescription() +

" extra foamed milk";

}

}

class Wet extends Decorator {

public Wet(DrinkComponent component) {

super(component);

}

public String getDescription() {

return component.getDescription() +

" extra steamed milk";

}

}

public class CoffeeShop3 extends TestCase {

public void testCappuccino() {

// This just makes sure it will complete

// without throwing an exception.

// Create a plain cappucino

DrinkComponent cappuccino = new Cappuccino();

System.out.println(cappuccino.getDescription()

+ ": $" + cappuccino.getTotalCost());

}

public void testCafeMocha() {

// This just makes sure it will complete

// without throwing an exception.

// Create a decaf cafe mocha with whipped

// cream

DrinkComponent cafeMocha = new Whipped(

new Decaf(new CafeMocha()));

System.out.println(cafeMocha.getDescription()

+ ": $" + cafeMocha.getTotalCost());

}

public static void main(String[] args) {

junit.textui.TestRunner.run(CoffeeShop3.class);

}

} ///:~

你可以看到,创建一个基本饮品(basic selection)是非常快且简单的,因为描述基本饮品成为一项常规工作。描述添加伴侣的饮品(decorated drink)比每种组合对应一个类那种方法要麻烦一些,但是,很明显比只使用decorator模式要简单很多。最后的结果是产生不算太多的类,同样,也不算太多的咖啡伴侣(decorators)。大多数情况下,我们碰到的都是不加伴侣的情况,这样我们就可以得益于上述两种方法带来的好处。

其它考虑Other considerations

假如说我们打算后来改变菜单,比如添加一种新类型的饮品,那会发生什么情况呢?如果我们使用的是每种组合对应一个类的那种方法,添加一种额外饮品(比如糖浆syrup)将会导致类的个数呈指数增长。但是,对于只使用decorator的方法和上述那种折中的方法,都只需要创建一个额外的类。

当牛奶涨价的时候,如果我们改变steamed milk和foamed milk的价格,会带来什么影响呢?如果是每种组合对应一个类,你必须得更改所有类的(getCost)方法,这样就得维护(maintain)非常多的类。通过使用Decorators,只要在一个地方维护(价格改变相关)的逻辑代码就可以了。

练习

1. 使用上面的decorator方法,添加一个Syrup类。然后创建一个添加了Café Latte的Syrup(你得用到steamed milk和espresso)。

2. 用“折中”的那种方法重做练习1。

3. 写一个简单的系统,用decorator模式模拟以下情况:有些鸟会飞而有一些不会,有一些鸟会有用而有一些不会,还有一些既会飞又会游泳。

4. 用decorator模式写一个Pizza餐馆,它提供可供选择的基本菜单,也允许你自己定制pizza。用“折中”的方法写一个菜单包括Margherita, Havaiian, Regina和Vegetarian pizza(反正都是pizza的品种), 和浇头(toppings,即decorator)大蒜Garlic, 橄榄Olives, 菠菜Spinach, 鳄梨?Avocade. 奶酪Feta,胡椒?Pepperdews。 写一个Hawaiian pizza, 再写一个添加了菠菜,奶酪,和胡椒的Margherita pizza.

5. 关于Decorator模式,《设计模式》一书是这么说的:“通过附加(attching)或者分离(detaching)装饰者(decorators),可以在运行时刻(run-time)添加或者去处某项功能(responsibilities)”。写一个咖啡的Decoration系统,这个系统可以从某种复杂的咖啡饮料的一系列添加物(decorators)里“简单”的去除某项功能。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有