大卫的Design Patterns学习笔记12:Proxy

一、概述
大家都用过代理服务器,代理服务器是从出发点到目的地之间的中间层。而Proxy模式中的Proxy功能上与此类似,是对象的访问者与对象之间的中间层。
Proxy(代理)模式可用于解决在直接访问对象不方便或不符合要求时,为这个对象提供一种代理,以控制对该对象的访问。

二、结构
Proxy模式的类图结构如下图所示:

1:Proxy模式类图示意
在上面的类图中,Proxy类是Subject类的子类,但个人认为,单纯从Proxy的意图上讲,这一约束不是必须的。

与前面讨论的Decorator模式不同,在应用Proxy模式时,我们可能知道目标,也可能不知道目标(此时,被代理的对象由Proxy类创建),而Decorator模式下我们往往按照访问目标的方式去访问Decorator,即我们总是知道目标,或者说我们更注意的是目标,而不是Decorator。并且,我们Proxy类可能提供与被访问者不同的接口,而Decorator模式下应保证接口的一致性,以便用户可以用与访问Decoratee一样的方式来访问Decorator。
单纯从结构上讲,Adapter模式与Proxy模式比较相似,但二者的区别在于意图的不同:Adapter的意图在于接口的转换,而Proxy的意图在于代理(或控制),因此,有人形象地将Proxy模式称为“票贩子模式”,而将Adapter模式称为“外汇买卖模式”。

三、应用
Proxy这个模式的目的比较笼统,引入Proxy的目的是在Subject和Client之间构建一个中间层,它适用的情况很多(以至于JDK1 .3开始,特别添加了对Proxy的支持,详见java .lang .reflect .Proxy说明文档),可以简单分成以下几类:
1
、远程访问代理(可能为了简化客户代码,也可能为了集中管理等);
2
、重要对象(可能是共享对象或大的,耗资源的对象)访问代理;
3
、访问控制代理。
在实际应用中,可能出现同时属于以上多种类别的情况。下面是一些可以应用Proxy模式的常见情况:
1
、远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。这个不同的地址空间可以是在本机器中,也可是在另一台机器中,远程代理又叫做大使(Ambassador)。常见的应用如CORBA、DCOM等。
2
、虚拟(Virtual)代理:根据需要创建一个资源消耗较大的对象,使得此对象只在需要时才会被真正创建。如某个Word文档中包含很多较大的图片,需要花费很长时间才能显示出来,那么使用编辑器或浏览器打开这个文档,不能等待大图片处理完成,这时需要做个图片Proxy来代替真正的图片,先在图片的位置显示一个框,然后随着图片逐步被打开,再对显示区域以及内容进行调整,直至显示所需内容。再比如说数据库的显示,客户现在只需要显示 1 - 10条,你放在内存中 10000条对客户没有任何意义,这时可以用一个代理,只是取出前 10条就可以了,当客户选择显示下 10条时再取出接下来的 10条数据显示给客户。
3
、Copy -on -Write代理:虚拟代理的一种。把复制(克隆)拖延到只有在客户端需要时,才真正采取行动。这在访问大的对象和防止频繁拷贝造成过多消耗时经常被用到,现代操作系统往往使用这种技术来防止频繁写磁盘,对于我们的普通应用而言,这种技术也被经常使用,当对象未发生修改时,先使用已有的对象,任何一方发生修改时才执行拷贝动作,创建新的对象,以避免在创建对象上过多的消耗(因为我们有可能在整个对象存在期间不会被修改),从而提高处理效率。
4
、保护(Protect  or Access)代理:控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。对于硬件等系统资源而言,OS即是一层保护代理。
5
、Cache代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。  
6
、防火墙(Firewall)代理:保护目标,不让恶意用户接近。  
7
、同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。  
8
、智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,比如将对此对象调用的次数记录下来等。

总之,当需要对Client访问目标对象的行为进行控制时,请尽可能不要通过继承来对目标进行深入规范,而是采用Proxy模式在Client和Subject之间建立一个中间层,这将为我们的系统提供更大的可扩展性。

四、优缺点

五、举例
由于Proxy模式的目的是在Subject和Client之间构建一个中间层,以控制Client对Subject的访问,其应用十分广泛,很难举出一个面面俱到的例子,以下用两个典型应用来简单演示Proxy模式的应用。如果你感兴趣,也可以研究一下STL的auto_ptr和string的实现,其中也用到了Proxy模式,其中,std ::string使用了Copy -on -Write技术。
下面是一个采用Proxy模式进行图片加载的例子(Virtual Proxy , Java Code):
注:运行该示例前请在程序运行目录下放置一个图片文件,并将其命名为test .jpg

import java .awt .*;
import java .awt .event .*;
import javax .swing .*;
import javax .swing .border .*;

// This class tests a virtual proxy, which is a proxy that
// delays loading an expensive resource (an icon) until that 
// resource is needed.

public class
 VirtualProxyTest extends JFrame  {
    private static
 String IMAGE_NAME  =  "test.jpg" ;
    
    private static
 int IMAGE_X  =  256 , IMAGE_Y  =  256 ;
    
    private
 JPanel normPanel , proxyPanel ;
    
    public
 VirtualProxyTest () {
        super ( "Virtual Proxy Test" );
        
        normPanel  =  new NormPanel (IMAGE_NAME );
        proxyPanel  =  new ImageProxy (IMAGE_NAME );
        
        normPanel .setPreferredSize ( new Dimension (IMAGE_X , IMAGE_Y ));
        proxyPanel .setPreferredSize ( new Dimension (IMAGE_X , IMAGE_Y ));
        
        Container c  = getContentPane ();
        c .setLayout ( new GridLayout ( 1 ,  2 ));
        c .add (normPanel );
        c .add (proxyPanel );
        
        // Set the bounds of the frame, and the frame's default
        // close operation.
        setDefaultCloseOperation (JFrame .EXIT_ON_CLOSE );
    }

    
    static public
 void  main (String args []) {
        VirtualProxyTest app  =  new VirtualProxyTest ();
        app .show ();
        app .pack ();
    }
}


class
 NormPanel extends JPanel  {
    private
 String imageName  = null ;
    private
 Image img  = null ;
    
    public
 NormPanel (String imageName ) {
        this
.imageName  = imageName ;
        img  = Toolkit .getDefaultToolkit ().getImage (imageName );
    }

    public
 void paint (Graphics g ) {
        g .drawImage (img ,  0 ,  0 , getWidth (), getHeight (),  this );  //draw image
    }
}


// ImageProxy is a proxy (or surrogate) for an image.
// The proxy delays loading the image until the first time the
// image is drawn. While the icon is loading its image, the
// proxy draws a border and the message "Loading image..."

class
 ImageProxy extends JPanel implements Runnable  {
    boolean isIconCreated  =  false ;
    private
 String imageName  = null ;
    private
 Image img  = null ;
    Thread imageLoadThread  = null ;

    public
 ImageProxy (String imageName ) {
        this
.imageName  = imageName ;
        imageLoadThread  =  new Thread ( this );
        imageLoadThread .start ();
    }


    // The proxy's paint() method is overloaded to draw a border
    // and a message ("Loading image...") while the image 
    // loads. After the image has loaded, it is drawn. Notice
    // that the proxy does not load the image until it is
    // actually needed.

    public
 void paint (Graphics g ) {
        if
(isIconCreated ) {
            g .drawImage (img ,  0 ,  0 , getWidth (), getHeight (),  this );  //draw image
        }
        else
 {
            g .drawRect ( 1 ,  1 , getWidth () -  2 , getHeight () -  2 );
            g .drawString ( "Loading image..." ,   20 ,  20 );
        }
    }

   
    public
 void run () {
        try
 {
            // Slow down the image-loading process.
            Thread .currentThread ().sleep ( 2000 );
            // create the image
            img  = Toolkit .getDefaultToolkit ().getImage (imageName );                     
            isIconCreated  =  true ;
        }
 catch (InterruptedException ex ) {
            ex .printStackTrace ();
        }

        repaint ();
    }
}


以下示例取自Thinking in C ++ 2nd,演示了Copy -on -Write技术的实现方法,耐心的Bruce Eckel用软件工程领域经典的Dog &DogHouse的例子清晰地告诉了我们应该如何实现Copy -on -Write(除了两处注释外,所有代码出自Bruce Eckel)。此外,关于string类中如何运用Copy -on -Write的问题,见参考 1

//: C12:ReferenceCounting.cpp
// From Thinking in C++, 2nd Edition
// Available at http://www.BruceEckel.com
// (c) Bruce Eckel 2000
// Copyright notice in Copyright.txt
// Reference count, copy-on-write
#include <string>
#include <iostream>
using namespace std ;

// assistant function
inline  void require ( bool requirement ,  
                    const
 std ::string & msg  =  "Requirement failed" ){
    using namespace
 std ;
    if
 (!requirement ) {
        fputs (msg .c_str (), stderr );
        fputs ( "/n" , stderr );
        exit ( 1 );
    }
}


// Subject class
class Dog  {
    string nm ;
    int
 refcount ;
    Dog ( const string & name
        :
 nm (name ), refcount ( 1 ) {
        cout  <<  "Creating Dog: "  << * this  << endl ;
    }

    // Prevent assignment:
    Dog &  operator =( const Dog & rv );
public
:
    // Dogs can only be created on the heap:
    static Dog * make ( const string & name ) {
        return
 new Dog (name );
    }

    Dog ( const Dog & d
        :
 nm (d .nm  +  " copy" ), refcount ( 1 ) {
        cout  <<  "Dog copy-constructor: "  
            << *
this  << endl ;
    }
    ~
Dog () { 
        cout  <<  "Deleting Dog: "  << * this  << endl ;
    }

    void
 attach () { 
        ++
refcount ;
        cout  <<  "Attached Dog: "  << * this  << endl ;
    }

    void
 detach () {
        require (refcount  !=  0 );
        cout  <<  "Detaching Dog: "  << * this  << endl ;
        // Destroy object if no one is using it:
        if (--refcount  ==  0 )  delete this ;
    }

    // Conditionally copy this Dog.
    // Call before modifying the Dog, assign
    // resulting pointer to your Dog*.
    Dog * unalias () {
        cout  <<  "Unaliasing Dog: "  << * this  << endl ;
        // Don't duplicate if not aliased:
        if (refcount  ==  1 )  return  this ;
        --
refcount ;
        // Use copy-constructor to duplicate:
        return  new Dog (* this );
    }

    void
 rename ( const string & newName ) {
        nm  = newName ;
        cout  <<  "Dog renamed to: "  << * this  << endl ;
    }

    friend
 ostream &
        operator
<<(ostream & os ,  const Dog & d ) {
        return
 os  <<  "["  << d .nm  <<  "], rc = "  
            <<
 d .refcount ;
    }
};


// Proxy class
class DogHouse  {
    Dog * p ;
    string houseName ;
public
:
    DogHouse (Dog * dog ,  const string & house )
        :
 p (dog ), houseName (house ) {
        cout  <<  "Created DogHouse: " << * this  << endl ;
    }

    DogHouse ( const DogHouse & dh )
        :
 p (dh .p ),
        houseName ( "copy-constructed "  + 
        dh .houseName ) {
        p ->attach ();
        cout  <<  "DogHouse copy-constructor: "
            << *
this  << endl ;
    }

    DogHouse &  operator =( const DogHouse & dh ) {
        // Check for self-assignment:
        if (&dh  !=  this ) {
            houseName  = dh .houseName  +  " assigned" ;
            // Clean up what you're using first:
            p ->detach ();
            p  = dh .p ;  // Like copy-constructor
            p ->attach ();
        }

        cout  <<  "DogHouse operator= : "
            << *
this  << endl ;
        return
 * this ;
    }

    // Decrement refcount, conditionally destroy
    ~DogHouse () {
        cout  <<  "DogHouse destructor: "  
            << *
this  << endl ;
        p ->detach (); 
    }

    void
 renameHouse ( const string & newName ) {
        houseName  = newName ;
    }

    void
 unalias () { p  = p ->unalias (); }
    // Copy-on-write. Anytime you modify the 
    // contents of the pointer you must 
    // first unalias it:
    void renameDog ( const string & newName ) {
        unalias ();
        p ->rename (newName );
    }

    // ... or when you allow someone else access:
    Dog * getDog () {
        unalias ();
        return
 p
    }

    friend
 ostream &
        operator
<<(ostream & os ,  const DogHouse & dh ) {
        return
 os  <<  "["  << dh .houseName  
            <<
 "] contains "  << *dh .p ;
    }
};
 

int
 main () {
    DogHouse 
        fidos (Dog ::make ( "Fido" ),  "FidoHouse" ),
        spots (Dog ::make ( "Spot" ),  "SpotHouse" );
    cout  <<  "Entering copy-construction"  << endl ;
    DogHouse bobs (fidos );
    cout  <<  "After copy-constructing bobs"  << endl ;
    cout  <<  "fidos:"  << fidos  << endl ;
    cout  <<  "spots:"  << spots  << endl ;
    cout  <<  "bobs:"  << bobs  << endl ;
    cout  <<  "Entering spots = fidos"  << endl ;
    spots  = fidos ;
    cout  <<  "After spots = fidos"  << endl ;
    cout  <<  "spots:"  << spots  << endl ;
    cout  <<  "Entering self-assignment"  << endl ;
    bobs  = bobs ;
    cout  <<  "After self-assignment"  << endl ;
    cout  <<  "bobs:"  << bobs  << endl ;
    // Comment out the following lines:
    cout  <<  "Entering rename(/"Bob/")"  << endl ;
    bobs .getDog ()->rename ( "Bob" );
    cout  <<  "After rename(/"Bob/")"  << endl ;
    
    return
 0 ;
}
 ///:~

参考:
1
、More Effective C ++ Item M29:引用计数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
现代C++中的设计模式是用于对象重用的可重复性方法。设计模式是一种在不同情况下解决相似问题的经验总结,可以通过将问题解决方案的关键部分抽象出来,从而提供灵活性和可重用性。设计模式不是编程语言特定的功能,而是一种通用的方法论。 在现代C++中,有许多常用的设计模式可以用于对象的可重用性。以下是几个常见的设计模式示例: 1.单例模式:用于确保一个类只能创建一个实例,并提供对该实例的全局访问点。对于有些对象只需要一个实例的情况,单例模式可以确保该实例的唯一性,从而方便访问和管理。 2.工厂模式:用于创建对象的过程中封装创建逻辑,让客户端代码无需关心对象的具体创建细节。通过工厂模式,可以通过一个工厂类来创建对象,从而提供更高的灵活性和可扩展性。 3.观察者模式:用于对象之间的发布-订阅机制,让一个对象(主题)的状态发生变化时,能够通知并自动更新其他依赖于该对象的对象(观察者)。通过观察者模式,可以实现对象之间的松耦合和消息传递,提高对象的可重用性和可维护性。 4.适配器模式:用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式可以解决接口不兼容的问题,从而使得原本不兼容的类能够一起工作,提高可重用性和互操作性。 5.策略模式:用于定义一系列算法/行为,并将其封装成独立的类,使得它们可以互相替换。策略模式可以在运行时根据需要动态切换算法/行为,从而提供更高的灵活性和可重用性。 这些设计模式都是在现代C++中常见且有用的重用性方法,可以根据具体的应用场景选择合适的设计模式来提高代码的可维护性、可扩展性和可重用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值