《设计模式沉思录》—第2章2.3节“但是应该如何引入代用品呢?”

2.3 “但是应该如何引入代用品呢?”
很高兴你能提出这个问题,因为我们现在正打算添加一个新的功能——符号化链接(symbolic link,它在Mac Finder中被称为别名,在Windows 95中被称为快捷方式)。符号化链接基本上是对文件系统中另一个节点的引用。它是该节点的“代用品”(surrogate),它不是节点本身。如果删除符号化链接,它会消失但不会影响到它所引用的节点。

符号化链接有自己的访问权限,这个访问权限与它引用的节点的访问权限可能是不同的。但是在大多数情况下,符号化链接都表现得和节点本身一样。如果一个符号化链接引用的是文件,那么客户代码可以将这个符号化链接当作该文件来处理。举个例子,客户代码可以编辑文件,或许还可以通过符号化链接来把对文件的改动保存起来。如果一个符号化链接引用的是目录,那么客户代码可以通过符号化链接来执行在目录中添加或者删除节点的操作,就好像这个符号化链接是目录本身一样。

符号化链接非常方便,它使我们无需移动或复制远在另一个地方的文件,就可以访问它们。对那些必须保存在一个地方但需要在另一个地方使用的节点来说,这很棒。如果我们的设计不支持符号化链接,那将是我们的失职。

因此,那些花钱买了《设计模式》的人应该问的第一个问题就是:有没有哪个模式可以帮助我们设计和实现符号化链接?事实上,还有一个更大的问题:我们如何找到正确的设计模式来解决手头的问题?

《设计模式》一书的1.7节给出了以下6个步骤。

(1)考虑设计模式如何解决设计问题。(换句话说,学习1.6节。但你我都知道在紧张的开发过程中这样做的可能性有多大。)

(2)快速浏览那些可能合适的模式的意图部分。(有点蛮干的意思。)

(3)研习模式之间是如何相互联系起来的。(这对我们来说仍然太过复杂,但我们已经很接近了。)

(4)看看哪些模式的目的(创建型、结构型和行为型)能与我们正在解决的问题对应起来,并查看这些模式。(嗯,给文件系统添加符号化链接看起来和结构有关。)

(5)审视引起重新设计的相关因素(在《设计模式》的第24页④中列出),并运用那些能够帮助避免这些因素的模式。(因为我们现在根本还没有设计,所以重新设计看起来有些为时过早。)

(6)考虑一下设计中哪些部分应该是可变的。针对每一个设计模式,《设计模式》的第30页中的表1-2列出了该模式允许设计的哪些方面发生变化。

让我们沿着第6条的方向继续前进。如果看一看表1-2中的结构型模式,我们会发现如下内容。

ADAPTER让我们改变一个对象的接口。
BRIDGE让我们改变一个对象的实现。
COMPOSITE让我们改变对象的结构和组成。
DECORATOR让我们改变职责而无需派生子类。
FACADE让我们改变一个子系统的接口。
FLYWEIGHT让我们改变对象的存储开销。
PROXY让我们改变如何访问一个对象以及改变对象的位置。
也许我有一些偏见,但听起来PROXY像是我们要找的模式。翻到该模式,我们找到了它的意图:

为另一个对象提供一个代用品或占位符,以便控制对它的访问。

动机部分将该模式运用于延迟载入图像的问题(这与我们在Web浏览器中想要实现的效果不无相似之处)。

但让我们最终敲定PROXY模式的,是它的适用性部分。其中阐述了当我们需要对一个对象进行引用,但这个引用需要具备更多的功能或要比一个简单的指针更加复杂时,PROXY模式就适用。其中还列出了一些它适用的常见情形,其中包括一个用来控制对另一个对象的访问的“保护代理”(protection proxy)——这恰恰是我们需要的。

好了,现在我们如何将PROXY模式运用到文件系统的设计中去呢?看一下该模式的结构图(如图2-3所示),我们会看到三个关键的类:一个Subject抽象类,一个RealSubject具体子类,还有另一个Proxy具体子类。因此我们可以推断出Subject 、RealSubject和Proxy有兼容的接口。Proxy子类还包含一个对RealSubject的引用。

该模式的参与者部分解释了Proxy类提供的接口与Subject的完全相同,这使得Proxy对象能够替代任何Subject对象。此外,RealSubject是Proxy所代表的对象。
把这些关系映射回我们的文件系统类,显然我们想要遵循的共同的接口是Node的接口。(毕竟那是COMPOSITE模式教给我们的。)这意味着Node类在Proxy模式中扮演的角色是Subject。

下面我们需要为Node定义一个子类来与Proxy模式中的Proxy类相对应。我称之为“Link”。

class Link : public Node {
public:
  Link(Node*);

  // redeclare common Node interface here
private:
  Node* _subject;
};

成员_subject用来引用实际对象。但是我们似乎有些偏离了该模式的结构图,结构图中引用的类型是RealSubject。在此例中,这相当于引用的类型是File或Directory,但我们仍然想让这两种类型的节点都可以使用符号化链接。我们应该怎么办?

如果看一看PROXY模式中对Proxy参与者的描述,我们会发现下面的语句:

[Proxy]用来维护一个引用,并让该代理访问实际对象。如果RealSubject和Subject具有相同的接口,那么Proxy也可以引用Subject。

根据前面的讨论,File和Directory共享了Node接口,这正是上面描述的情况。因此_subject是指向Node的指针。如果没有一个共同的接口,要定义一种能够同时用于文件和目录的符号化链接是非常困难的。事实上,我们最终可能会定义出两种符号化链接,除了一种用于文件而另一种用于目录之外,两者的工作方式完全相同。

我们要解决的最后一个主要问题是Link如何实现Node接口。基本上,只要把每个操作委托给_subject中与之对应的操作就可以了。因此getChild的实现可能是下面这样。

Node* Link::getChild (int n) {
  return _subject->getChild(n);
}

在某些情况下,Link所表现出来的行为可能并不依赖于它的subject。例如,Link可能会定义自己的保护操作,在这种情况下,它会像File那样来实现此类操作。

※   ※   ※

Laurion Burchall就PROXY模式的应用提出了他敏锐的见解[Burchall95]:

如果一个文件被删除了,那么指向它的代理将变成一个无关联指针(dangling pointer)。当一个文件被删除时,我们可以使用OBSERVER模式通知所有的代理,但这种方法不允许我们把新文件移动到旧文件的位置,从而让符号化链接继续工作。

在Unix和Mac中,符号化链接持有的是被引用文件的名字,而不是具体的对象。一个代理可以持有该文件的名字并引用文件系统的根目录。但由于每次都要查找名字,因此这会大大增加通过代理来访问文件的开销。

除了与OBSERVER有关的那部分之外,上面说的这些都没错。当代理指向的文件被替换掉的时候,我们完全可以通知代理并让它和文件重新关联起来。在这方面,替换和删除是相似的。

但Laurion的观点仍然是正确的:虽然只保持一个指向subject的指针是非常高效的,但如果不增加一些新的机制,那么这种做法很难让人满意。如果想把一个subject替换掉但又不把指向它的链接作废,就需要一个额外的间接层,而我们现在还没有。我们可以用存储文件名来代替存储对象指针,但是为了把文件名高效地映射到对象,这种方法可能需要某种类型的关联存储器(associatiue store)。即便如此,与只存储一个指针相比,这种方法仍然需要额外的开销。但是,除非指向文件的符号化链接太多,或者符号化链接的层次太多,否则这不应该是什么问题。当然,当文件被删除或者被替换的时候,关联存储器也必须要更新。

我倾向于不考虑这些不常用的情况,目的是为了让常用的情况能够快速运行。如果通过符号化链接来访问一个文件要比通过符号化链接来替换或删除一个文件更加常用——我认为事实的确如此,那么我更加倾向于采用基于OBSERVER的方法,而不是采用基于名字查找的方法。

※   ※   ※

在像这样的设计逐渐形成的过程中,需要注意的是不要把基类变成一个大杂烩:随着时间的推移,接口中的操作持续累积,接口不断膨胀。文件系统的每个新特性都会增加一两个操作。今天是为了支持可扩展属性,下个星期是为了计算一种新类型的文件大小统计数据,下个月是为了给图形用户界面返回图标。不用多久,Node就变成了一个巨型类——难以理解,难以维护,难以从中派生子类。

我们接下来就来解决这个问题。我们要寻找一种根本不需要对已有的类做任何改动就能在设计中添加新操作的方法。

时间: 2024-09-20 06:21:12

《设计模式沉思录》—第2章2.3节“但是应该如何引入代用品呢?”的相关文章

《设计模式沉思录》—第1章1.1节对模式的十大误解

第1章 介绍设计模式沉思录在阅读本书之前,如果读者还没有听说过一本名叫<设计模式>(Design Patterns: Elements of Reusable Object-Oriented Software [GoF95])的书,那么现在正好可以去找一本来读.如果读者听说过该书,甚或自己还有一本但却从来没有实际研读过,那么现在也正好应该好好研读一下. 如果你仍然在继续往下阅读,那么我会假设你不是上述两种人.这意味着你对模式有大致的了解,特别是对23个设计模式有一定的了解.你至少需要具备这样的

《设计模式沉思录》—第2章2.1节基础

第2章 运用模式进行设计设计模式沉思录如果想体验一下运用模式的感觉,那么最好的方法就是运用它们.对我来说,最大的挑战在于找到一个所有人都能够理解的示例.人们对自己的问题最感兴趣,如果某些人对某个示例越感兴趣,这个示例往往就越具体.问题在于,这样的示例所涉及的问题往往太过晦涩,对于没有相关领域背景的人来说难以理解. 层级文件系统(hierarchical file system)是每个计算机用户都熟悉的东西,就让我们来看看该如何设计它.我们不会关心诸如I/O缓冲和磁盘扇区管理之类的底层实现问题,我

《设计模式沉思录》目录—导读

版权声明设计模式沉思录Authorized translation from the English language edition, entitled Pattern Hatching: Design Patterns Applied, 9780201432930 by John Vlissides, published by Pearson Education, Inc., publishing as Addison-Wesley Professional. Copyright 1998

《设计模式沉思录》—第2章2.7节多用户文件系统的保护

2.7 多用户文件系统的保护我们已经讨论了如何给我们正在设计的文件系统添加简单的单用户保护.前面提到我们会将这个概念扩展到多用户环境,在这个环境中许多用户共享同一个文件系统.无论是配以中枢文件系统的传统分时系统,还是当代的网络文件系统,对多用户的支持都是必不可少的.即使那些为单用户环境所设计的个人计算机操作系统(如OS/2和Windows NT),现在也已经支持多用户.无论是什么情况,多用户支持都给文件系统保护这一问题增加了难度. 我们将再一次采用最简易的设计思路,效仿Unix系统的多用户保护机

《设计模式沉思录》—第2章2.8节小结

2.8 小结我们已经将模式应用于文件系统设计的各个方面.COMPOSITE模式的贡献在于定义了递归的树状结构,打造出了文件系统的主干.PROXY对主干进行了增强,使它支持符号化链接.VISITOR为我们提供了一种手段,使我们能够以一种得体的.非侵入性的方式来添加与类型相关的新功能. TEMPLATE METHOD在基本层面(即单个操作层面)为文件系统的保护提供了支持.对于单用户保护来说,我们只需要该模式就足够了.但为了支持多用户,我们还需要更多的抽象来支持登录.用户以及组.SINGLETON在两

《设计模式沉思录》—第2章2.4节访问权限

2.4 访问权限到目前为止我们已经运用了两个设计模式:我们用COMPOSITE来定义文件系统的结构,用PROXY来帮我们支持符号化链接.把我们讨论到现在的改动和其他一些改进合并起来,得到了如图2-4所示的体现了COMPOSITE模式和PROXY模式的类层次结构. getName和getProtection用来返回节点的对应属性.Node基类为这些操作定义了默认的实现.streamIn用来把节点的内容写入文件系统,streamOut用来从文件系统读出节点的内容.(我们假设文件是按照简单的字节流来建

《设计模式沉思录》—第2章2.2节孤儿、孤儿的收养以及代用品

2.2 孤儿.孤儿的收养以及代用品现在让我们深入研究一下在我们的文件系统中运用COMPOSITE模式可能会得到什么样的结果.我们首先考察在设计Node类的接口时必须采取的一个重要折中,接着会尝试给刚诞生的设计增加一些新功能. 我们使用了COMPOSITE模式来构成文件系统的主干.这个模式向我们展示了如何用面向对象的方法来表示层级文件系统的基本特征.这种模式通过继承和组合来将它的关键参与者(Component.Composite及Leaf类)联系在一起,从而支持任意大小和复杂度的文件系统结构.它同

《设计模式沉思录》—第2章2.5节关于VISITOR的一些警告

2.5 关于VISITOR的一些警告在使用VISITOR模式之前,有两件事情需要考虑. 首先问一下自己,被访问的类层次结构是否稳定?拿我们的例子来说,我们是否会经常定义新的Node子类,还是说这种情况很少见?增加一种新的Node类型可能会迫使我们仅仅为了增加一个相应的visit操作而修改Visitor类层次结构中所有的类. 如果所有的visitor对新的子类不感兴趣,而且我们已经定义了一个与visitNode等价的操作来在默认情况下进行合理的处理,那么就不存在问题.但是,如果只有一种类型的vis

《设计模式沉思录》—第2章2.6节单用户文件系统的保护

2.6 单用户文件系统的保护经常使用计算机的人大都有过丢失重要数据的惨痛经历,起因可能只是一个不巧的语法错误,也可能是鼠标点偏了,或者只是深夜脑子突然不好使.在正确的时间删除一个错误的文件是一种常见的灾难.另一种情况是无意的编辑--在不经意间修改了一个不应该修改的文件.虽然一个高级文件系统会具备撤销功能,可以从这些不幸的事件中恢复,但我们通常更希望防患于未然.可悲的是,大多数文件系统给我们另一种不同的选择:预防或后悔⑥. 目前我们将集中精力讨论对文件系统对象(即节点)的删除和修改操作进行保护.之