代理模式设计

模式编程法则

1、开闭法则

开闭法则(Open Closed Principle ,OCP):软件实体(模块)应易于扩展(开放),但免于修改。换言之,我们希望可以改变模块的行为,而不用修改其源代码。

2、Liskov 代换法则

Liskov代换法则(Liskov Substitution Principle,LSP):派生类应该可以用其基类代换。这个法则是由Barbar Liskov在研究抽象及类型理论是发现的。同时Bertrand Meyer提出的契约式设计概念中也曾提到。Liskov代换法则如下图所示 。

Liskov

由图可见,如果在某些User的方法中,使用类型Base(基类)作为参数,那么它应该允许在方法中传入Derived(派生类)的对象实例。这就是说,Liskov代换法则利用了类型继承关系中的向上的转型机制。向上转型机制是安全的,但向下转型就不一定安全了。所以Liskov代换法则不能反过来代换,即如果一个软件实体使用的是一个派生类作参数,那么它不一定能适用于基类。

3、依赖反转法则

依赖反转法则(Dependency Inversion Principle,DIP):依赖抽象不依赖具体。

如果说OCP给出了面向对象架构的目标,那么DIP则是指以构架的主要机制。依赖的策略强调依赖接口、抽象方法及抽象类,而不是依赖具体方法或类。

      在传统的面向过程的设计中,人们习惯于使用高层模块依赖于底层模块,抽象层依赖于具体层,由上到下,形成一种,顺序依赖的架构设计,这种结构从顶部主模块开始,然后向下发展出细节部分。高层模块依赖低层的模块,然后再依赖低层的模块,如此延续下去。

        这种以来结构在本质上存在着许多优点。首先作为底层模块其实现的具体细节,这就决定了它的多变。如果总是向下依赖于多变的下级模块,必然会把这种变化不定的因素传播到上层,影响系统得稳定性和健壮性。虽然通过增加中间层的诺里可以吸收底层变化因素,但中间层并不能改变这种顺序依赖,相反,中间层的加入反而又导致了上层对中间层的依赖,增加了系统的复杂性。

        实际上高层模块应该只处理应用程序的策略。这些高层策略一般很少涉及实际的细节。既然如此,为什么这些高层模块必须直接依赖那些实现细节的模块呢?如果我们改变思维,反转依赖,得到的将是一种非常不同的依赖结构如下图所示

这才是一个真正的面向对象的架构,因为高层的模块不再依赖于含有实现细节的模块,而是依赖抽象的接口。这种设计的好处是通过抽象接口取代被依赖的中间层,保证了上层结构的稳定。因为底层细节实现模块的变化并不导致接口的改变。也就是说,同一接口允许有不同的实现方法,甚至不同的实现模块。

        依赖反转法则的用意相当简单。设计中的每一个依赖关系,应该是朝向接口,或一个抽象类,而不是一个具体类。这是因为具体的东西经常改变,而抽象的东西比较稳定。抽象往往能使我们有更多的回旋余地。抽象层可以作为上下层之间的枢纽,代表设计中可以伸缩或扩展的地方,而且在变动时不致于被修改,从而达到OCP的要求。打个比方,如果我们为了记一个电话号码而借一支笔,通常会说“借一支笔”,而不会说“借一支派克笔”或“借一支狼毫小楷毛笔”。因为我们依赖于抽象的笔而不是具体的该笔或毛笔,这样使得我们有回旋的余地,比较容易借到笔,实现记录电话号码的目的。相反,如果我们不依赖抽象而依赖于具体,那么具体的笔是多变的,我们不知道对方可能有什么样的笔,那么你要借一支派克笔可能会失败,或者你不得不频频修改你的需求“如果没有派克笔,,就借一支狼毫小楷毛笔”,。。。

        DIP的动机就是防止你依赖易变的具体实现。DIP假设所有具体的东西都是易变的。虽然这样的假设有点严格,但在可能的情况下,这个法则应该尽可能遵循,至少组件一定要遵循这个法则。如COM就坚持这个法则。COM组件唯一可以看到的部分就是其抽象的接口。因此在COM中很少逾越DIP

        DIP也是模式编成的法则。例如,在设计中最长发生依赖的地方式建构对象。依据定义,你无法建构抽象类的对象实例。因此,为了构造一个对象实例,你必须依赖一个具体类。在架构设计中的鹅任何地方都可能发生构造对象实例的情况。因此,似乎无法逃脱这种状况,而整个构架会被这种依赖具体类的事实弄乱。但是抽象工厂模式提供了一个简单的解决方法。下面讨论了代理模式如何实现DIP,从而消除业务层对数据层的依赖,因为业务层依赖于数据层的具体API会限制业务层的扩展和充用。

        在多层系统的设计中,通过代理模式引入的代理层,可以使得原来相互依赖的层之间实现依赖反转,从而满足层与层之间独立演化的需求。特别在业务层依赖数据层或其他第三方中间层时,这种设计可以很好的消除耦合。

4、借口隔离法则

接口隔离法则(Interface Segregation PrincipleISP):不应该强迫客户端依赖于它们用不上的方法。

接口隔离的法则就是说,因不同的客户端而划分出多个不同的专用接口,比使用单一的总的接口好。若果从客户类的角度看,一个类对另一个类的依赖应该是建立在最小的接口上的。换言之,我们编成时应当把那些客户端不必要的接口从使用接口的客户端隔离开来。

ISP是模式编成的法则之一。没这个法则,组件及类将缺乏效用和可移植性。

这个法则的实质相当简单。如果你的泪有许多客户端调用,你不必向客户端开放所需的所有方法,而只需要为每一个客户端建立特定的接口,然后在类中多重继承这些接口。

5、单一职责法则

单一职责法则(Single-Responsibility PrincipleSRP):一个类应该仅有一个原因导致其变化。

单一职责法则是指一个类应该只有一个演化方向,那门这个类的变化才是可控的。这里的所谓职责指“一个导致变化的原因”。因为调整一个类所承担的职责才是我们改动这个类的真正动机,所以导致变化的原因源于类的职责。如果一个类包含过多的职责,这就意味着会有多个导致其变化的原因。而且,这些变化往往不是孤立的,他们通过多个职责间的耦合关系相互作用,是类产生多个相互依赖的不确定的演化方向,带来维护和重用上的困难。

一个类应该仅有一个原因导致其变化,意味着它们的函数关系是一元的:

C = f(x)

这里的x就是导致变化的原因 — 职责。

但是在大量的不良设计中,却使一个类中存在多个致变的因素,这正是由于一个类中承担了太多的职责,于是它们的函数关系呈现多元的。当一个类中包含多个职责,且他们的耦合关系错综复杂时,如果分离不当,会造成循环依赖的问题。对这种职责不易分离的情况,可以采用前面的接口隔离法则。

. 代理模式

1. 概述

       所谓代理模式(Proxy)就是为某一个对象的访问提供一个代理对象,而不是直接去控制对象。这个对象在客户端和源对象之间起着中介作用。

       例如,有客户来买某种Computer,但是这种Computer仅有少数几家供货商提供。客户很难找到这些供货商。即使找到了,也可能遇到这些供货商缺货的麻烦。如果客户通过代理商来购买这种Computer,事情都变得很简单了,因为代理商知道那些供货商有货源。于是代理商在客户对象和Computer对象之间起到了中介的作用。

 

 

1是产品代理模式的示意图。客户对象(TClient)通过代理购买Computer的,他所关心的不再是具体产品及其生产和供货的途径,而是抽象的产品,既作为接口的产品说明说。供货商是产品说明书的直接实现者,他严格按照产品说明书的要生产和提供符合要求的产品,代理商虽然也按照产品说明书的要求代理产品,但他的责任是根据产品说明书的要求选择合格的供货商来实现提供的产品,也就是说他自己并不具体实现该产品,他只是一个中介。由于有了代理商,因而客户不必知道那些供货商生产和提供产品,供货商也不必知道那些客户需要产品。也就是说自从有了代理商,客户和供货商之间的依赖关系不符存在,代理商也起到了解耦的作用。

在代理模式中,其核心包含接口、代理和实现三部分。接口抽象了客户感兴趣的操作,以方便客户访问对象。代理和实现都是对接口的实现,但其实现方式不同。一个接口可能会有多种不同的实现;而代理可以通过选择和使用有效的具体实现来间接实现接口,为客户提供服务。所以说,代理并不仅仅是多了一道手续而是一项增值服务,代理通过自身的业务逻辑可以帮助具体实现解决问题、克服不做,提升服务质量。

       在软件开发中,经常使用代理来解决对象与对象、系统与系统之间的强耦合,起到中介与缓冲的作用。使用代理模式可以控制存取一个对象,特别是那些耗费资源的对象,代理模式可以将对象延迟到真正需要的时在创建。

       代理是一个经常用到的概念,而且种类繁多。可以使用代理模式的常见模式有;

     远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代表对象。这个不同的地址空间可以是在本机,也可以是在其他机器中。

     虚代理(Virtual Proxy)根据需要创建开销很大的对象,使得该对象仅仅在实际使用时才会被真正创建。

     保护代理(Protection Proxy)控制原始的对象的访问。保护代理可以提供或限制用户访问用户的不同权限。

     智能引用(Smart Proxy)当一个对象被引用时,只能引用取代了简单的指针。它提供了一些访问对象时的附加操作,比如检查管理引用计数,当引用为零时,负责自动释放对象;检查引用对象是否锁定,确保不盲目改变对象的状态;检查并确保持久化对象是在首次加载时装入内存的。

2 结构和用法

2.1 模式结构

3

代理模式的结构如图3所示,它包括了一下参与者:

     代理(TProxy)— 负责维护一个引用,使得代理可以访问真实的主题对象;提供一个与主题一直的借口让代理可以替代真实的对象;控制对真实对象的访问,并可能负责创建和删除它;把客户端的调用传递给真实的主题之前或之后,执行某些辅助性的操作。

     抽象主题(TSubject)— 定义TRealSubject TProxy 的共同接口;这样就在任何使用 TRealSubject 的地方都可以用TPtoxy

     真实主题(TRealSubjec)— 定义代理对象可以代理的真正对象。

4

4 是在执行期间的代理模式时序图。由图可见代理模式工作的过程为,客户端Client向代理对象Proxy发出请求,代理对象依据代理的种类适时传递请求给真实的对象RealSubjec。这样,客户端对象、代理对象、真实主题对象之间建立了如图 5 所示的关系。

5

由此可见,与客户端直接向真实对象发出请求的情况相比,代理可以通过间接传递请求而提供比较灵活的中介控制。比如,代理可以在传递请求的前后执行一些附加操作,完成引用计数、保护检查等操作;代理可以决定何时、何地及如何创建或销毁所代理的真实对象。总之,代理模式将一个过渡曾插入到客户端和真实主题之间,提供了一种游刃有余的编程艺术。

对于客户而言,原先要调用的真实对象可以被代理对象置换。因为他们实现的都是TSubjec 的共同接口,所以在客户端看来没什么差别。这就是说,使用代理模式解决了问题,但丝毫不影响到客户端。

2.2 给予代理模式的优化设计

       在设计时,我们都知道要把用户界面(User InterfaceUI)、业务逻辑()和数据()分层,程序员的水平高低决定了对这种分层的理解和实现。分层不仅仅是把实现逻辑代码分开,更重要的是合理逻辑设计它们的依赖关系,减低和简化它们的彼此间的耦合关系。

       例如在图6所示的分层架构设计中,由于业务层和数据层关系纠缠不清,造成循环依赖;表现层同时依赖于业务层和数据层,造成依赖关系复杂。这样虽然貌似分层,但实际上并为降低耦合度,达不到分层的真正效果。

                                                                                                

                     6                                                                     7

       7 是单一依赖关系得分层,即表现层依赖于业务层,业务层依赖于数据层。这种分层的架构设计是我们经常使用的,它改进了图6所示的混乱的依赖关系,使得层与层之间的关系清晰、简单和易于维护。

       7所示的层次关系中,表现层提供的图形界面,让用户完成业务操作,所以表现层依赖于业务层是合理的。但是业务层对数据层的过分依赖并不是一件好事。因为我们可能会有不同的数据存储及管理需求以及基于不同平台的不同实现,比如,数据层可能使用 RDBMSXML 等不同的持久机制,可能使用ODBCADOBDEJDBC等不同的数据访问接口(API)。为了适应这种变化,提高系统的健壮性,我们可以在业务层和数据层间加一层代理层,以吸收业务层和数据层的变化因素,消除它们之间的耦合,如图 8 所示

8

       注意图8所示的代理层分别依赖业务层和数据层,箭头的方向不同于图7那样的顺序。这就是说代理层依赖于业务层,而不是业务层依赖于代理层。如果是按图7那样的依赖顺序加入代理层,即业务层依赖代理层,代理层依赖数据层,那么数据层的变化仍然有可能通过依赖它的代理层传播到业务层,通过依赖反转法则(Dependency Inversion Principle)彻底隔绝了数据层对业务层的影响。

       9是基于代理模式对分层的设计。根据依赖反转法则“依赖抽象而不依赖具体”的思想,我们可以把代理模式的抽象主题(Subject)作为抽象接口让表现层和代理层依赖,而在真实的主题(RealSubject)中实现业务逻辑,并在代理(Proxy)中完成业务层和数据层的胶合。这样一来,代理层就为业务层和数据层之间的Mapping提供了集中统一的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值