分享
 
 
 

翻译TIPatterns--对象去耦(Object decoupling)

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

对象去耦(Object decoupling)

代理(Proxy)模式和状态(State)模式分别提供了供你使用的代理类(surrogate class);正真干活的那个类被代理类隐藏了。当你调用代理类的一个方法的时候,代理类只是简单的调用实现类(implementing class)所对应的方法。这两种模式非常相似,实际上,代理(Proxy)模式只是状态(State)模式的一个特例。

有人试图将这两种模式合在一起统称为Surrogate模式,但是“代理(proxy)”这个术语已经用了很长时间了,而且它有自己特殊的含义,它的这些含义基本上体现了这两种模式的差别所在。

这两种模式的基本概念非常简单:代理类 (surrogate) 和 实现类都由同一个基类派生出来:

当创建一个代理对象 (surrogate object) 时,同时会创建一个实现(对象),代理对象会把所有的方法调用传递给实现对象。

从结构上看,代理(Proxy)模式和状态(State)模式之间的差别非常简单:一个代理(Proxy)只对应一个实现(implementation),而一个状态(State)却可以对应多个实现。《设计模式》一书认为,这两种两种模式的应用场合是截然不同的:代理(Proxy)模式用于控制对实现(类)的访问,而状态(State)模式可以动态地改变实现(类)。但是,如果把“控制对实现类的访问”这个概念扩展开来的话,这两种模式就可以优雅的结合在一起了。

代理:替另外一个对象打点一切(Proxy: fronting for another object)

我们按照上面的图示实现代理(Proxy)模式,下面是实现代码:

//: proxy:ProxyDemo.java

// Simple demonstration of the Proxy pattern.

package proxy;

import junit.framework.*;

interface ProxyBase {

void f();

void g();

void h();

}

class Proxy implements ProxyBase {

private ProxyBase implementation;

public Proxy() {

implementation = new Implementation();

}

// Pass method calls to the implementation:

public void f() { implementation.f(); }

public void g() { implementation.g(); }

public void h() { implementation.h(); }

}

class Implementation implements ProxyBase {

public void f() {

System.out.println("Implementation.f()");

}

public void g() {

System.out.println("Implementation.g()");

}

public void h() {

System.out.println("Implementation.h()");

}

}

public class ProxyDemo extends TestCase {

Proxy p = new Proxy();

public void test() {

// This just makes sure it will complete

// without throwing an exception.

p.f();

p.g();

p.h();

}

public static void main(String args[]) {

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

}

} ///:~

当然,并不是说实现类和代理类必须实现完全相同的接口;既然代理类只是在一定程度上代表那个需要它提交(referring)方法的类,这就已经满足了proxy模式的基本要求(注意这里的陈述和GoF一书所给出的定义是有差别的)。尽管如此,定义一个公共的接口还是很方便的,这样就可以强制实现类(Implementation)实现(fulfill)代理类(Proxy)需要调用的所有方法。

用Proxy模式实现PoolManager

//: proxy:PoolManager.java

package proxy;

import java.util.*;

public class PoolManager {

private static class PoolItem {

boolean inUse = false;

Object item;

PoolItem(Object item) { this.item = item; }

}

public class ReleasableReference { // Used to build the proxy

private PoolItem reference;

private boolean released = false;

public ReleasableReference(PoolItem reference) {

this.reference = reference;

}

public Object getReference() {

if(released)

throw new RuntimeException(

"Tried to use reference after it was released");

return reference.item;

}

public void release() {

released = true;

reference.inUse = false;

}

}

private ArrayList items = new ArrayList();

public void add(Object item) {

items.add(new PoolItem(item));

}

// Different (better?) approach to running out of items:

public static class EmptyPoolItem {}

public ReleasableReference get() {

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

PoolItem pitem = (PoolItem)items.get(i);

if(pitem.inUse == false) {

pitem.inUse = true;

return new ReleasableReference(pitem);

}

}

// Fail as soon as you try to cast it:

// return new EmptyPoolItem();

return null; // temporary

}

} ///:~

//: proxy:ConnectionPoolProxyDemo.java

package proxy;

import junit.framework.*;

interface Connection {

Object get();

void set(Object x);

void release();

}

class ConnectionImplementation implements Connection {

public Object get() { return null; }

public void set(Object s) {}

public void release() {} // Never called directly

}

class ConnectionPool { // A singleton

private static PoolManager pool = new PoolManager();

private ConnectionPool() {} // Prevent synthesized constructor

public static void addConnections(int number) {

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

pool.add(new ConnectionImplementation());

}

public static Connection getConnection() {

PoolManager.ReleasableReference rr =

(PoolManager.ReleasableReference)pool.get();

if(rr == null) return null;

return new ConnectionProxy(rr);

}

// The proxy as a nested class:

private static

class ConnectionProxy implements Connection {

private PoolManager.ReleasableReference implementation;

public

ConnectionProxy(PoolManager.ReleasableReference rr) {

implementation = rr;

}

public Object get() {

return

((Connection)implementation.getReference()).get();

}

public void set(Object x) {

((Connection)implementation.getReference()).set(x);

}

public void release() { implementation.release(); }

}

}

public class ConnectionPoolProxyDemo extends TestCase {

static {

ConnectionPool.addConnections(5);

}

public void test() {

Connection c = ConnectionPool.getConnection();

c.set(new Object());

c.get();

c.release();

}

public void testDisable() {

Connection c = ConnectionPool.getConnection();

String s = null;

c.set(new Object());

c.get();

c.release();

try {

c.get();

} catch(Exception e) {

s = e.getMessage();

System.out.println(s);

}

assertEquals(s,

"Tried to use reference after it was released");

}

public static void main(String args[]) {

junit.textui.TestRunner.run(

ConnectionPoolProxyDemo.class);

}

} ///:~

动态代理(Dynamic Proxies)

JDK1.3引入了动态代理 (Dynamic Proxy). 尽管一开始有些复杂,但它确实是一个吸引人的工具。下面这个有趣的小例子证明了这一点, 当invocation handler被调用的时候,代理机制(proxying)开始工作。这是非常Cool的一个例子,它就在我的脑海里,但是我必须得想出一些合理的东西给invocation handler,这样才能举出一个有用的例子…(作者还没有写完)

// proxy:DynamicProxyDemo.java

// Broken in JDK 1.4.1_01

package proxy;

import java.lang.reflect.*;

interface Foo {

void f(String s);

void g(int i);

String h(int i, String s);

}

public class DynamicProxyDemo {

public static void main(String[] clargs) {

Foo prox = (Foo)Proxy.newProxyInstance(

Foo.class.getClassLoader(),

new Class[]{ Foo.class },

new InvocationHandler() {

public Object invoke(

Object proxy, Method method,

Object[] args) {

System.out.println(

"InvocationHandler called:" +

"\n\tMethod = " + method);

if (args != null) {

System.out.println("\targs = ");

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

System.out.println("\t\t" + args[i]);

}

return null;

}

});

prox.f("hello");

prox.g(47);

prox.h(47, "hello");

}

} ///:~

练习:用java的动态代理创建一个对象作为某个简单配置文件的前端。例如,在good_stuff.txt文件里有如下条目:

a=1

b=2

c="Hello World"

客户端程序员可以使用(你写的)NeatPropertyBundle类:

NeatPropertyBundle p =

new NeatPropertyBundle("good_stuff");

System.out.println(p.a);

System.out.println(p.b);

System.out.println(p.c);

配置文件可以包含任何内用,任意的变量名。动态代理要么返回对应属性的值要么告诉你它不存在(可能通过返回null)。如果你摇设置一个原本不存在的属性值,动态代理会创建一个新的条目。ToString()

方法应该显示当前的所有条目。

练习:和上一道练习类似,用Java的动态代理连接一个DOS的Autoexec.bat文件。

练习:接受一个可以返回数据的SQL查询语句,然后读取数据库的元数据(metadata)。为每一条记录(record)提供一个对象,这个对象拥有一下属性:列名(column names)和对应的数据类型(data types).

练习:用XML-RPC写一个简单的服务器和客户端.每一个客户端返回的对象都必须使用动态代理的概念(dynamic proxy concept)来实现(exercise)远端的方法。(瞎翻的,不知道啥意思)

读者Andrea写道:

除了最后一个练习,我觉得你给出的上面几个练习都不咋的。我更愿意把Invocation handler看成是能和被代理对象正交的 (orthogonal) 东东。

换句话说,invocation handler的实现应该是和动态创建的代理对象所提供的那些接口完全无关的。也就是说,一旦invocation handler写好之后,你就可以把它用于任何暴露接口的类,甚至是那些晚于invocation handler出现的类和接口。

这就是我为什么要说invocation handler所提供的服务是和被代理对象正交的(orthognal)。Rickard 在他的SmartWorld例子里给出了几个handler,其中我最喜欢的是那个调用-重试(call-retry)handler。它首先调用那个(被代理的)实际对象,如果调用产生异常或者等待超时,就重试三次。如果这三次都失败了,那就返回一个异常。这个Handler可以被用于任何一个类。

那个handler的实现相对于你这里讲的来说过于复杂了,我用这个例子仅仅是想说明我所指的正交(orthogonal)服务到底是什么意思。

您所给出的那几个练习,在我看来,唯一适合用动态代理实现的就是最后那个用XML-RPC与对象通信的那个练习。因为你所使用的用以分发消息的机制(指XML-RPC)是和你想要建立通信的那个对象完全正交的。

状态模式:改变对象的行为(State: changing object behavior)

一个用来改变类的(状态的)对象。

迹象:几乎所有方法里都出现(相同的)条件(表达式)代码。

为了使同一个方法调用可以产生不同的行为,状态(State)模式在代理(surrogate)的生命周期内切换它所对应的实现(implementation)。当你发现,在决定如何实现任何一个方法之前都必须作很多测试的情况下,这是一种优化实现代码的方法。例如,童话故事青蛙王子就包含一个对象(一个生物),这个对象的行为取决于它自己所处的状态。你可以用一个布尔(boolean)值来表示它的状态,测试程序如下:

//: state:KissingPrincess.java

package state;

import junit.framework.*;

class Creature {

private boolean isFrog = true;

public void greet() {

if(isFrog)

System.out.println("Ribbet!");

else

System.out.println("Darling!");

}

public void kiss() { isFrog = false; }

}

public class KissingPrincess extends TestCase {

Creature creature = new Creature();

public void test() {

creature.greet();

creature.kiss();

creature.greet();

}

public static void main(String args[]) {

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

}

} ///:~

但是,greet() 方法(以及其它所有在完成操作之前必须测试isFrog值的那些方法)最终要产生一大堆难以处理的代码。如果把这些操作都委托给一个可以改变的状态对象(State object),那代码会简单很多。

//: state:KissingPrincess2.java

package state;

import junit.framework.*;

class Creature {

private interface State {

String response();

}

private class Frog implements State {

public String response() { return "Ribbet!"; }

}

private class Prince implements State {

public String response() { return "Darling!"; }

}

private State state = new Frog();

public void greet() {

System.out.println(state.response());

}

public void kiss() { state = new Prince(); }

}

public class KissingPrincess2 extends TestCase {

Creature creature = new Creature();

public void test() {

creature.greet();

creature.kiss();

creature.greet();

}

public static void main(String args[]) {

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

}

} ///:~

此外,状态(State)的改变会自动传递到所有用到它的地方,而不需要手工编辑类的方法以使改变生效。

下面的代码演示了状态(State)模式的基本结构。

//: state:StateDemo.java

// Simple demonstration of the State pattern.

package state;

import junit.framework.*;

interface State {

void operation1();

void operation2();

void operation3();

}

class ServiceProvider {

private State state;

public ServiceProvider(State state) {

this.state = state;

}

public void changeState(State newState) {

state = newState;

}

// Pass method calls to the implementation:

public void service1() {

// ...

state.operation1();

// ...

state.operation3();

}

public void service2() {

// ...

state.operation1();

// ...

state.operation2();

}

public void service3() {

// ...

state.operation3();

// ...

state.operation2();

}

}

class Implementation1 implements State {

public void operation1() {

System.out.println("Implementation1.operation1()");

}

public void operation2() {

System.out.println("Implementation1.operation2()");

}

public void operation3() {

System.out.println("Implementation1.operation3()");

}

}

class Implementation2 implements State {

public void operation1() {

System.out.println("Implementation2.operation1()");

}

public void operation2() {

System.out.println("Implementation2.operation2()");

}

public void operation3() {

System.out.println("Implementation2.operation3()");

}

}

public class StateDemo extends TestCase {

static void run(ServiceProvider sp) {

sp.service1();

sp.service2();

sp.service3();

}

ServiceProvider sp =

new ServiceProvider(new Implementation1());

public void test() {

run(sp);

sp.changeState(new Implementation2());

run(sp);

}

public static void main(String args[]) {

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

}

} ///:~

在main()函数里,先用到的是第一个实现,然后转入第二个实现。

当你自己实现State模式的时候就会碰到很多细节的问题,你必须根据自己的需要选择合适的实现方法,比如用到的状态(State)是否要暴露给调用的客户,以及如何使状态发生变化。有些情况下(比如Swing的LayoutManager),,客户端可以直接传对象进来,但是在KissingPrincess2.java那个例子里,状态对于客户端来说是不可见的。此外,用于改变状态的机制可能很简单也可能很复杂-比如本书后面将要提到的状态机(State Machine),那里会讲到一系列的状态以及改变状态的不同机制。

上面提到Swing的LayoutManager那个例子非常有趣,它同时体现了Strategy模式和State模式的行为。

Proxy模式和State模式的区别在于它们所解决的问题不同。《设计模式》里是这么描述Proxy模式的一般应用的:

1. 远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代理。A remote proxy is created for you automatically by the RMI compiler rmic as it creates stubs and

2. 虚代理(Virtual proxy),根据需要,在创建复杂对象时使用“lazy initialization” .

3. 保护代理(protection proxy) 用于你不希望客户端程序员完全控制被代理对象(proxied object)的情况下。

4. 智能引用(smart reference). 当访问被代理对象时提供额外的动作。例如,它可以用来对特定对象的引用进行计数,从而实现copy-on-write,进而避免对象别名(object aliasing). 更简单的一个例子是用来记录一个特定方法被调用的次数。

你可以把java里的引用(reference)看作是一种保护代理,它控制对分配在堆(heap)上的实际对象的访问(而且可以保证你不会用到一个空引用(null reference))。

【重写:在《设计模式》一书里,Proxy模式和State模式被认为是互不相干的,因为那本书给出的用以实现这两种模式的结构是完全不同的(我认为这种实现有点武断)。尤其是State模式,它用了一个分离的实现层次结构,但我觉着完全没有必要,除非你认定实现代码不是由你来控制的(当然这也是一种可能的情况,但是如果代码是由你来控制的,那还是用一个单独的基类更简洁实用)。此外,Proxy模式的实现不需要用一个公共的基类,因为代理对象只是控制对被代理对象的访问。尽管有细节上的差异,Proxy模式和State模式都是用一个代理(surrogate)把方法调用传递给实现对象。】

State模式到处可见,因为它是最基本的一个想法,比如,在Builder模式里,“Director”就是用一个后端(backend)的Buider object来产生不同的行为。

迭代器:分离算法和容器(Iterators: decoupling algorithms

from containers)

Alexander Stepanov(和Dave Musser一起)写STL以前 ,已经用了好几年思考泛型编程(generic programming)的问题。最后他得出结论:所有的算法都是定义在代数结构(algebraic structures)之上的-我们把代数结构称作容器(container)。

在这个过程中,他意识到i迭代器对于算法的应用是至关重要的,因为迭代器将算法从它所使用的特定类型的容器中分离出来。这就意味着在描述算法的时候,可以不必考虑它所操作的特定序列。更为一般情况,用迭代器写的任何代码都与它所操作的数据结构相分离,这样一来这些代码就更为通用并且易于重用。

迭代器的另外一个应用领域就是functional programming,它的目标是描述程序的每一步是干什么的,而不是描述程序的每一步是怎么做的。也就是说,使用“sort”(来排序),而不是具体描述排序的算法实现。C++STL的目的就是为C++语言提供对这种泛型编程方法的支持(这种方法成功与否还需要时间来验证)。

如果你用过Java的容器类(写代码不用到它们是很难的),那你肯定用过迭代器-Java1.0/1.1把它叫作枚举器(Enumeration),Java2.0叫作迭代器-你肯定已经熟悉它们的一般用法。如果你还不熟悉的话,可以参考Thinking in Java 第二版第九章 (freely downloadable from www.BruceEckel.com).

因为Java2的容器非常依赖于迭代器,所以它们就成了泛型编程/功能性编程的最佳候选技术。这一章节通过把STL移植到Java来讲解这些技术,(移植的迭代器)会和Java2的容器类一起使用。

类型安全的迭代器(Type-safe iterators)

在Thinking in Java 第二版里,我实现了一个类型安全的容器类,它只接受某一特定类型的对象。读者Linda Pazzaglia想要我实现另外一个类型安全的组件,一个可以和java.util里定义的容器类兼容的迭代器,但要限制它所遍历的对象必须都是同一类型的。

如果Java有模板(template)机制,上面这种(类型安全的)迭代器很容易就可以返回某一特定类型的对象。但是没有模板机制,就必须得返回generic Objects,或者为每一种需要遍历的对象都手工添加代码。这里我会使用前一种方法。

另外一个需要在设计时决定的问题(design decision)是什么时候判定对象的类型。一种方法是以迭代器遍历的第一个对象的类型(作为迭代器的类型),但是这种方法当容器类根据它自己的内部算法(比如hash表)重新为对象排序时就会有问题,这样同一迭代器的两次遍历就可能得到不同的结果。安全的做法是在构造迭代器的时候让用户指定迭代器的类型。

最后的问题是如何构建迭代器。我们不可能重写现有的Java类库,它已经包含了枚举器和迭代器。但是,我们可以用Decorator模式简单的创建一个枚举器或者迭代器的外覆类,产生一个具有我们想要的迭代行为(本例中,指在类型不正确的时候抛出RuntimeException异常)的新对象,而这个新对象跟原来的枚举器或者迭代器有相同的接口,这样一来,它就可以用在相同的场合(或许你会争论说这实际上是Proxy模式,但是从它的目的(intent)来说它更像Decorator模式)。

实现代码如下:

//: com:bruceeckel:util:TypedIterator.java

package com.bruceeckel.util;

import java.util.*;

public class TypedIterator implements Iterator {

private Iterator imp;

private Class type;

public TypedIterator(Iterator it, Class type) {

imp = it;

this.type = type;

}

public boolean hasNext() {

return imp.hasNext();

}

public void remove() { imp.remove(); }

public Object next() {

Object obj = imp.next();

if(!type.isInstance(obj))

throw new ClassCastException(

"TypedIterator for type " + type +

" encountered type: " + obj.getClass());

return obj;

}

} ///:~

练习:

1.写一个“virtual proxy”。

2.写一个“Smartreference”代理,用这个代理记录某个特定对象的方法调用次数。

3.仿照某个DBMS系统,写一个程序限制最大连接数。用类似于singleton的方法控制连接对象的数量。当用户释放某个连接时,必须通知系统将释放的连接收回以便重用。为了保证这一点,写一个proxy对象代替对连接的引用计数,进一步设计这个proxy使它能够将连接释放回系统。

4.用State模式,写一个UnpredictablePerson类,它根据自己的情绪(Mood)改变对hello()方法的响应。再写一个额外的Mood类:Prozac。

5.写一个简单的copy-on write实现。

6.java.util.Map 没有提供直接从一个两维数组读入“key-value”对的方法。写一个adapter类实现这个功能。

7.Create an Adapter Factory that dynamically finds and produces the adapter that you need to connect a given object to a desired interface.

8.用java标准库的动态代理重做练习7。

9.改写本节的Object Pool,使得对象再一段时间以后自动回收到对象池。

10.改写练习9,用“租借(leasing)”的方法使得客户端可以刷新“租借对象”,从而阻止对象定时自动释放。

11.考虑线程因素,重写Object Pool。

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