Java中的模式
副标题#e#
世上一直有一个神话:设计可以而且应该独立于实现的细节,设计凡是被看作是一个抽象的观念而实现是一个代码的详细实例。假如我们坚信"设计是一个富有缔造性和目标性的勾当:为某一个方针而经心拟定的布局的观念,",一个布局假如不可以或许说明它的情况,可能不能与情况协作,那么这个布局就不适合这一方针。情况中包罗方针平台–语言、东西、库、中间件(middleware),等。尚有它的成果性和非成果性的单位。
我们会认为在不知道地形机关的时候设计衡宇,可能在不清楚利用的道质料的时候制作摩天大厦是不公道的工作。我们将线程、漫衍这类观念看作为小的编码的细节的观点无疑是在设计中导致挥霍精神(时间和款子)的导火索,最终我们发明的是理论与实践的差距在实践中要比在理论中还大。固然在一些环境下一个高条理设计的某部门可以在很多技能下保持稳定,可是更多的环境是我们需要亲自来补足这个圆圈,答允(甚至勉励)细节和实际的信息来影响并奉告系统的布局。
模式(Patterns)的浸染就是获取这些布局上的信息。它们可以描写–预见性的或回首性的–设计和设计的道理,报告从问题到办理,说明情况,获取事情的动力以及应此发生的功效。这里,我将会合报告两个模式–Command-Query Separation和Command Method–为一个类接口中的要领分派任务,考查他们如何相互浸染并影响并发的、漫衍的和有序的情况以及当地执行。
接口设计。顾名思义,接口提供了差异系统之间可能系统差异组件之间的界定。在软件中,接口提供了一个屏障,从而从实现中疏散了方针,从详细中疏散了观念,从作者中疏散了用户。在Java中,有很多接口的观念:public部门为潜在的用户提供了类和要领的接口,protected部门为它的子类(subclass)以及周围的包提供了一个接口;一个包有一个公用的部门;反射(Reflection)是别的一种提供、利用工具要领接口的机制。
约束及供应。站在用户对作者的角度,一个接口成立并定名了一个目标模子的利用要领。类接口中的要领提供了一种非凡的利用要领。是这些约束–编译时的范例系统,运行是的异常机制及返回值–使得类作者的目标得以浮现和增强。在这方面最简朴的例子是对封装的意义的领略:私有化可以担保类用户只可以通过类的公用要领接口来操纵信息和行为。
然而,对付封装来说,远不止数据私有那么简朴。在设计中,封装往往会涉及到自我包括(self-containment)。一个需要你知道如何挪用一个要领(e.g."在一个线程的情况中,在一个要领挪用后挪用另一个要领,你必需明晰地同步工具")的类的封装就不如将所有这些全部包括并埋没的类(e.g."这个类是thread-safe的")好。前一个设计存在着设计的裂痕,它的很多限定条件是恍惚的而不是颠末增强的。这就把责任推给了用户而不是让类提供者做这些事情来完成类的设计,而且,这是不行制止的裂痕百出。
在这种环境下,供应(affordances)描写了利用的可行性和不行行性。
术语供应(affordances)指事物的被感知的真实的属性,首先,这些属性可以抉择事物的利用的大概要领。一个椅子可以用来支撑其他对象,所以,可以坐人。一个椅子照样可以搬运(carried)。玻璃可以透光,也可以被打坏……
供应提供了对事物操纵的线索,板状物可以压、柄状物可以旋转,沟状物可以插入对象。球状物可以扔可能反弹。当利用了供应的优势后,用户可以只通过看便确定该做什么:没有图、没有标签也没有说明。巨大的事物大概会需要一些表明,可是简朴的事物不该该这样。当简朴的对象也需要用图片、标签来说明的时候,设计就是失败的。
类设计者的一个职责即是在接口中减小约束与供应之间的隔膜(gap),匹配方针以及必然水平上的自由度,尽大概减小错误利用的大概。
对情况敏感的设计。在空间可能时间上疏散要领的执行–譬喻,线程,长途要领挪用,动静行列–可以或许对设计的正确性和效率发生意义深远的影响。这种疏散带来的功效是不行忽视的:并发引入了不确定性和情况选择的开销;漫衍引入了错误的和不绝增加的回程的挪用开销。这些是设计的问题,而不是修改bug那样简朴。
无论是在何种环境下,功效都是将会阻碍所有权气势气魄的措施设计(Property-Style Programming)–当一个接口主要由set和get要领构成的时候,每个要领都相应的直接指向私有区域。这样的类的封装很差(意思是毫无讳饰)。接口中的域会见器(Field accessors)凡是是不会提供信息的:他们在工具的利用中不能通讯、简朴化和抽象化,这凡是会导致冗长并易呈现错误的代码。所有权气势气魄的措施设计在短时间内不是一个大的勾当。漫衍和并行通过引入了正确性和严重的机能开销放大了这些名目上实践的问题。
#p#分页标题#e#
透明度和bug劫难。抽象答允我们在须要的时候可以忽略细节,所以我们的设计思想可以均衡情况的因素而不是受制于它们。抉择什么样的细节可以忽略便成为一个挑战。问题的严重性在重要的细节别忽略的环境下上升了。
设计往往会只管使情况因素尽大概的透明。透明可以或许成为一个诱人的主意:也许它可以让线程和长途工具通讯完全透明,这样用户在举办工具通讯的时候什么也不会发觉到。Proxy模式支持必然水平上的透明度。这增强了RMI和COBRA的基本。当地的署理的工具和利用长途的工具在利用中具有沟通的接口,而且编组上的细节答允挪用着利用熟悉的要领来挪用模子。然而,这种漫衍透明并不完全:失误和潜在的影响,不能被完全埋没而且需要思量。究竟透明不是毛巾。
#p#副标题#e#
Command-Query Separation
担保一个要领是不呼吁(Command)就是查询(Query)
问题。要领,当它们返回一个值往返应一个问题的时候,具有查询的性质,当它们采纳强制动作来的改变工具的状态的时候,具有呼吁的属性。所以一个要领可以是纯的Command模式可能是纯的Query模式,可能是这两者的殽杂体。
譬喻,在java.util.Iterator中,hasNext可以被看作一种查询,remove是一种呼吁,next和awkward归并了呼吁和查询:
public interface Iterator
{
boolean hasNext();
Object next();
void remove();
}
假如不将一个Iterator工具的当前值向前到下一个的话,就不可以或许查询一个Iterator工具。这导致了一个初始化(initialization)、增加(continuation)、会见(access)和前进(advance)疏散而清晰界说的轮回布局的错位:
for(initialization; continuation condition; advance)
{
... access for use ...
}
将Command和Query成果归并入一个要领的的功效是低落了清晰性。这大概阻碍基于断言的措施设计而且需要一个变量来生存查询功效:
for(Iterator iterator = collection.iterator();
iterator.hasNext();)
{
Object current = iterator.next();
... use current...
... again use current...
}
办理方案。担保要领的行为严格的是呼吁可能是查询,这样可以返回值的要领是纯的函数而没有复效应(side effects),有负效应的要领不行能有返回值。"另一个表述这点的要领是问一个问题而不影响到谜底。"
Combined Method
组合要领常常一起被利用在线程和漫衍情况中来担保正确性并改进效率。
问题。一些主要提供麋集的要领的接口,起初,看来是最小化和附着性强的–都是吸引人的特点。然而,在利用的进程中,一些接口显现得过于原始。它们过于简朴化,从而迫使类用户用更多的事情来实现普通的的任并哄骗要领之间的依赖性(临时耦合)。这长短常贫苦而且容易堕落的,导致了代码反复–代码中该当制止的–而且为bug提供了很好的滋生条件。
一些需要同时执行乐成的要领,在执行的时候在多线程、异常、和漫衍的处所碰着了贫苦。假如两个行动需要同时执行,它们必需遵守协作或反转(commit-or-rollback)语义学–它们必需都完全乐成的执行可能一个行动的失败会反转另一个行动的执行–它们由两个独立的要领举办描写。
线程的引入使不确定水平大大增加。一系列要领挪用一个易变的(mutable)工具并不会确保功效是猜想中的,假如这个工具在线程之间共享,纵然我们假设单独的要领是线程安详的。看下面的对Event Source的接口,它答允安放句柄和对事件的查询:
interface EventSource
{
Handler getHandler(Event event);
void installHandler(Event event, Handler newHandler);
...
}
线程之间的交错挪用大概会引起意想不到的功效。假设source域引用一个线程共享的工具,很大概在1、2之间工具被另一个线程安装了一个句柄:
class EventSourceExample
{
...
public void example(Event event, Handler newHandler)
{
oldHandler = eventSource.getHandler(event); // 1
eventSource.installHandler(event, newHandler); // 2
}
private EventSource eventSource;
private Handler oldHandler;
}
同样的,这次也是类利用者而不是类设计者来存眷这些,拟定约束:
#p#分页标题#e#
class EventSourceExample
{
...
public void example(Event event, Handler newHandler)
{
synchronized(eventSource)
{
oldHandler = eventSource.getHandler(event);
eventSource.installHandler(event, newHandler);
}
}
private EventSource eventSource;
private Handler oldHandler;
}
假如方针工具是长途的,回程增加的开销和对要领挪用失败并发的交叉在一起成为情况的一部门。在上一个例子中,我们可以假设执行每一个要领体的时间和通讯的延迟对比是很短的。在这个例子中,开销被反复了两次,并大概在其他的实例中反复多次。
另外尚有一个问题是对外部(extern)的synchronized同步块的利用需求。Synchronized块很明明在漫衍的情况中利用可是也可以在当地的线程情况中应用的很好:在挪用者和方针之间的署理工具的利用。简而言之,对synchronized块的利用因为署理工具而不是方针工具的同步而失败。守旧的说法是,这对系统的真确性可以有一个根基的影响。因为署理利用是在接口后透明的,挪用者不能对行为做太多的担保。
办理方案。Combined Method必需在漫衍,线程情况中同时执行。连系该当反应出普通的利用要领。这样,一个Combined Method才大概比原有的要领要清晰,因为它反应了直接的应用。规复计策和一些鸠拙的要领被封装到Combined Method中,并简化了类用户角度的接口。这改进的封装低落了接口中不需要的累赘。Combined Method的全部结果是支持一种更像事务处理惩罚气势气魄的设计。
在一个连系的Command-Query中提供一个单独的Query要领凡是是公道的。然而,这需要凭据需要而拟定,而不是强制的执行。提供疏散的Command要领是不太常见的,因为Combined Method可以完成这一事情:挪用者简朴的忽略功效。假如返回一个功效招致一个开销的话,才大概会体统一个单独的Command要领。
回到前一个例子中,假如installHandler method返回上一个句柄设计变得越发简朴和独立:
class EventSourceExample
{
...
public void example(Event event, Handler newHandler)
{
oldHandler = eventSource.installHandler(event, newHandler);
}
private EventSource eventSource;
private Handler oldHandler;
}
挪用者提供了一个越发安详接口,而且不再需要办理线程的问题。这低落了风险和代码的巨细,将类设计的职责全部给了类设计者而不是推给用户。署理工具的呈现没有影响到正确性。
一个Combined Method可以是很多Query的荟萃,很多Command的荟萃,可能两者兼有。这样,它大概增补可能抵触Command-Query疏散的要领。当斗嘴产生的时候,优先选择Combined Method会发生一个差异的正确性和合用性。
在另一个例子中,思量得到资源的环境。假设,在下面的接口中,得到的要领在资源可用前一直起到阻碍浸染:
interface Resource
{
boolean isAcquired();
void acquire();
void release();
...
}
雷同于下面的代码会在一个线程系统中推荐利用:
class ResourceExample
{
...
public void example()
{
boolean acquired = true;
synchronized(resource)
{
if(!resource.isAcquired())
resource.acquire();
else
acquired = false;
}
if(!acquired)
...
}
private Resource resource;
}
然而,纵然放弃可读性和易用性,这样的设计不是一个Command-Query疏散设计的应用。假如引入了署理,它就会失败:
class ActualResource implements Resource {...}
class ResourceProxy implements Resource {...}
一个Combined Method办理了这个问题,它使并发和间接性越发透明。
interface Resource
{
...
boolean tryAcquire();
...
}
下面的代码清晰、简朴而且正确:
class ResourceExample
{
...
public void example()
{
if(!resource.tryAcquire())
...
}
private Resource resource;
}
Combined Method带来的一个功效是使一些测试和基于断言的措施设计变得十分鸠拙。然而,和本来的设计对较量,原有的要领在办理线程和漫衍问题上不是一个符合的途径。在这一环境下,单位测试提供较好的分级和疏散。Combined Method可以或许使一个要领接口恍惚并使类用户的代码越发冗长,鸠拙。在一些条件下Execute Around Method提供了一个可以担保自动和机动的另一个Combined Method。
结论
情况抉择实践的要领。