查看: 8440|回复: 2
打印 上一主题 下一主题

利用ColdFusion组件实现状态模式

[复制链接]
跳转到指定楼层
1#
发表于 2007-9-19 22:48:44 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
台州网址导航
ColdFusion MX中ColdFusion组件(CFCs)的引入,开启了在CF中面向对象编程的新篇章。在应用开发人员和程序员开始思考面向对象编程(OOP)时,总会提到设计模式的概念。

在这样的背景下,本文中我们将讨论一下状态模式:它的目的是什么,可以解决什么问题,以及如何利用CFCs实现。同时,本文这里包含了一些示例代码。你还可以从此文的下载版中得到这些代码。

问题描述

首先,我们来看一个问题,这个问题可能会使我们考虑用状态模式作为解决方案。在我的例子中,我将创建一个简单的内容管理系统,系统中的内容项可能包含这样几个状态:起草、预览、等待发行许可、已发行。内容项还可以包含多个要执行的方法:保存、许可该项、拒绝该项等。我们需要根据内容项不同的状态和将要执行的方法,分别以不同的方式做出反应。

我们可以有几种方法解决这个问题。第一种可能的方法是为每种状态与行为组合创建不同的方法。CFS内容项如列表A所示,列表B显示了使用CFC的测试运行结果。

列表 A

<cfcomponent name="MethodExplosionContentItem" hint="I am a per-reqeust CFC that represents a Content Item, but does not use the State pattern.">
<cffunction name="init" access="public" returntype="MethodExplosionContentItem" hint="Constructor.">
<cfreturn this />
</cffunction>
<cffunction name="saveDraft" access="public" returntype="void" output="true" hint="">
<cfoutput>
Draft: saving content item...<br/>
</cfoutput>
</cffunction>
<cffunction name="savePublished" access="public" returntype="void" output="true" hint="">
<cfoutput>
Published: setting the content to draft mode...<br/>
</cfoutput>
</cffunction>
<cffunction name="approveDraft" access="public" returntype="void" output="true" hint="">
<cfoutput>
Draft: saving content item and alerting reviewer about content for review...<br/>
</cfoutput>
</cffunction>

<cffunction name="approveReview" access="public" returntype="void" output="true" hint="">
<cfoutput>
Review: alerting content author that content is approved...<br/>
Review: marking content as ready for publish approval...<br/>
</cfoutput>
</cffunction>
<cffunction name="approvePublishApproval" access="public" returntype="void" output="true" hint="">
<cfoutput>
Publish approval: Marking content as deployed...<br/>
Publish approval: Pushing live and updating content cache...<br/>
</cfoutput>
</cffunction>
<cffunction name="rejectReview" access="public" returntype="void" output="true" hint="">
<cfoutput>
Review: alerting content author that content is rejected...<br/>
Review: setting back to draft mode...<br/>
</cfoutput>
</cffunction>
<cffunction name="rejectPublishApproval" access="public" returntype="void" output="true" hint="">
<cfoutput>
Publish approval: alerting reviewer that publishing is rejected...<br/>
Publish approval: setting back to review mode...<br/>
</cfoutput>
</cffunction>
</cfcomponent>

列表B

<h2>First, method explosion with separate methods for all actions and statues:</h2>
<cfset contentMethodExplosion = createObject('component','MethodExplosionContentItem').init() />
<cfset contentMethodExplosion.saveDraft() />
<cfset contentMethodExplosion.approveDraft() />
<cfset contentMethodExplosion.approveReview() />
<cfset contentMethodExplosion.approvePublishApproval() />
<cfset contentMethodExplosion.savePublished() />
<cfset contentMethodExplosion.approveDraft() />
<cfset contentMethodExplosion.rejectReview() />
<hr/>
<cfset contentMethodExplosion2 = createObject('component','MethodExplosionContentItem').init() />
<cfset contentMethodExplosion2.rejectReview() />
<cfset contentMethodExplosion2.saveDraft() />
<cfset contentMethodExplosion2.approveDraft() />
<cfset contentMethodExplosion2.approveReview() />
<cfset contentMethodExplosion2.rejectPublishApproval() />
<hr/>

从上面的运行看代码能够满足要求,它的确按照我们的要求运行。然而,对于它是如何工作的,存在几个问题。首先是拥有大量的方法,每个状态和行为组合对应一个方法。我们要有saveDraft(), savePublished(), rejectReview(), rejectPublishApproval()等方法。如果这时你注意到这一点,不错因为我们应该注意。因为如果我们增加几个状态或行为,那么状态和事件组合将会呈指数级增加。三个状态和三个行为需要九个方法,四个状态和四个行为要有十六个方法等等。很明显,这不是一个可以升级的方案。

这还不是全部,还存在另一个问题,我们需要客户端代码(在本例中就是简单的测试模板)跟踪内容项的状态变化。如果失去状态跟踪,本该调用approveDraft()方法却意外调用了approvePublishApproval()方法,就会导致内容项进入错误状态,此时可能就会发生错误。

或者导致发布本不应该发布的内容项。关键问题是客户端代码本不应该跟踪内容项状态,它的状态应该有状态项自身维护。




第二种方案

由此看来,我们应该承认“方法爆炸”方案可能不是最好的,或许我们可以简化问题。

理想的情况下,我认为每个内容项只对一个更普通的行为请求做出反应。这将会使内容项需要的方法降低到三个:save(), approve(), and reject()。列表C所示是该方法的测试用例。

列表C

<h2>Next, using conditional logic in Content Item:</h2>
<cfset contentConditional = createObject('component','ConditionalContentItem').init('draft') />
Content item created in <strong>draft</strong>...<br/>
<cfset contentConditional.save() />
<cfset contentConditional.approve() />
<cfset contentConditional.approve() />
<cfset contentConditional.approve() />
<cfset contentConditional.save() />
<cfset contentConditional.approve() />
<cfset contentConditional.reject() />
<hr/>

<cfset contentConditional2 = createObject('component','ConditionalContentItem').init('review') />
Content item created in <strong>review</strong>...<br/>
<cfset contentConditional2.reject() />
<cfset contentConditional2.save() />
<cfset contentConditional2.approve() />
<cfset contentConditional2.approve() />
<cfset contentConditional2.reject() />
<hr/>

很好,这的确看起来很简单,而且注意现在客户端代码不知道下一步状态项应该是什么状态。它只是调用了approve()方法,有内容项本身跟踪调用该方法后该如何做出反应,问题解决了。
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏 转播转播 分享分享 分享淘帖
台州维博网络(www.tzweb.com)专门运用PHP+MYSQL/ASP.NET+MSSQL技术开发网站门户平台系统等。
2#
 楼主| 发表于 2007-9-19 22:48:59 | 只看该作者
台州网址导航
但是还不能高兴得太早,我们的确简化了每个内容项的公共接口,并将跟踪状态的职责转移给了内容项本身。但是,我们又该为此付出什么代价呢?

列表D显示了这个版本的CFC代码,我要警告你,这个代码十分糟糕。看看这么多的逻辑条件。每调用一个方法,我需要对所有状态进行条件检查以确定该如何做出反应。并且,增加更多的方法或行为时仍旧意味着爆炸,只不过是一种不同类型的爆炸,在这种情况下,结果是条件语句的爆炸。

列表 D

<cfcomponent name="ConditionalContentItem" hint="I am a per-reqeust CFC that represents a Content Item, but does not use the State pattern.">
<cffunction name="init" access="public" returntype="ConditionalContentItem" hint="Constructor.">
<cfargument name="initialStatus" type="string" required="true" />
<cfset setStatus(arguments.initialStatus) />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfif getStatus() eq 'draft'>
<cfoutput>
Status '#getStatus()#' saving content item...<br/>
</cfoutput>
<cfelseif getStatus() eq 'published'>
<cfoutput>
Status '#getStatus()#' setting the content to draft mode...<br/>
</cfoutput>
<cfset setStatus('draft') />
</cfif>
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfif getStatus() eq 'draft'>
<cfoutput>
Status '#getStatus()#' saving content item and alerting reviewer about content for review...<br/>
</cfoutput>
<cfset setStatus('review') />
<cfelseif getStatus() eq 'review'>
<cfoutput>
Status '#getStatus()#' alerting content author that content is approved...<br/>
Status '#getStatus()#' marking content as ready for publish approval...<br/>
</cfoutput>
<cfset setStatus('publishApproval') />
<cfelseif getStatus() eq 'publishApproval'>
<cfoutput>
Status '#getStatus()#' Marking content as deployed...<br/>
Status '#getStatus()#' Pushing live and updating content cache...<br/>
</cfoutput>
<cfset setStatus('published') />
</cfif>
</cffunction>
<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfif getStatus() eq 'review'>
<cfoutput>
Status '#getStatus()#' alerting content author that content is rejected...<br/>
Status '#getStatus()#' setting back to draft mode...<br/>
</cfoutput>
<cfset setStatus('draft') />
<cfelseif getStatus() eq 'publishApproval'>
<cfoutput>
Status '#getStatus()#' alerting reviewer that publishing is rejected...<br/>
Status '#getStatus()#' setting back to review mode...<br/>
</cfoutput>
</cfif>
</cffunction>
<cffunction name="getStatus" access="private" returntype="string" output="false" roles="" hint="I return the status.">
<cfreturn variables.instance.status />
</cffunction>
<cffunction name="setStatus" access="private" returntype="void" output="false" roles="" hint="I set the status.">
<cfargument name="status" type="string" required="true" hint="status" />
<cfset variables.instance.status = arguments.status />
</cffunction>
</cfcomponent>

这种方法可能比第一种方法好,但是我还是认为有太多的缺点。这就是为什么我们转向状态模式,看看是否能够有所帮助。

状态模式定义

状态模式的概念在由Gamma, Helms, Johnson,和 Vlissides(“四人帮”)所著的《设计模式》一书中首次提出。在这本书中,作者指出状态模式的意图是“当对象的内部状态改变时,允许改变它的行为。对象将看起来能够改变它的类”。

即使不了解状态模式,仅上面的描述就可能使你惊奇。“当对象的内部状态改变时,允许改变它的行为”听起来像是我们想要内容项完成很多功能。

应用状态模式解决问题

实质上,状态模式所做的就是允许我们将不同的状态和每个状态需要的行为封装到CFCs中,每个状态知道如何对不同的行为作出反应,且根据不同的环境设定内容项的状态。我们给每个内容项(为了清楚起见,在这种解决方案中我将它称为ContentItemContext)提供一个简单的接口,但是避免了上面例子中的条件爆炸问题。ContentItemContext见列表E,状态CFC代码如列表F,运行测试如列表G。

列表 E

<cfcomponent name="ContentItemContext" hint="I am a per-reqeust CFC that represents a Content Item. All instance data about the item would go here.">
<cffunction name="init" access="public" returntype="ContentItemContext" hint="Constructor.">
<cfargument name="draftState" type="DraftState" required="true" />
<cfargument name="reviewState" type="ReviewState" required="true" />
<cfargument name="approvePublishState" type="ApprovePublishState" required="true" />
<cfargument name="publishedState" type="PublishedState" required="true" />
<cfargument name="initialState" type="string" required="true" />
<cfset variables.instance.draftState = arguments.draftState />
<cfset variables.instance.reviewState = arguments.reviewState />
<cfset variables.instance.approvePublishState = arguments.approvePublishState />
<cfset variables.instance.publishedState = arguments.publishedState />
<cfset createStateNameMappings() />
<cfset setStateByName(arguments.initialState) />
<cfreturn this />
</cffunction>
<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfset getCurrentState().save(this) />
</cffunction>
<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfset getCurrentState().approve(this) />
</cffunction>
<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfset var local = structNew() />
<cfset getCurrentState().reject(this) />
</cffunction>

<!--- State-related methods --->
<cffunction name="createStateNameMappings" access="private" returntype="void" output="false" hint="">
<cfset var local = structNew() />
<cfset variables.stateMappings['draft'] = variables.instance.draftState />
<cfset variables.stateMappings['review'] = variables.instance.reviewState />
<cfset variables.stateMappings['publishApproval'] = variables.instance.approvePublishState />
<cfset variables.stateMappings['published'] = variables.instance.publishedState />
</cffunction>

<cffunction name="setStateByName" access="public" returntype="void" output="false" hint="">
<cfargument name="stateName" type="string" required="true" />
<cfset setCurrentState(getStateByName(arguments.stateName)) />
</cffunction>

<cffunction name="getStateByName" access="private" returntype="AbstractContentState" output="false" hint="">
<cfargument name="stateName" type="string" required="true" />
<cftry>
<cfreturn variables.stateMappings[arguments.stateName] />
<cfcatch type="any">
<cfthrow message="No state with a mapping of '#arguments.stateName#' could be found." />
</cfcatch>
</cftry>
</cffunction>

<cffunction name="getCurrentState" access="private" returntype="AbstractContentState" output="false" roles="" hint="I return the currentState.">
<cfreturn variables.instance.currentState />
</cffunction>

<cffunction name="setCurrentState" access="private" returntype="void" output="false" roles="" hint="I set the currentState.">
<cfargument name="currentState" type="AbstractContentState" required="true" hint="currentState" />
<cfset variables.instance.currentState = arguments.currentState />
</cffunction>

</cfcomponent>
台州维博网络(www.tzweb.com)专门运用PHP+MYSQL/ASP.NET+MSSQL技术开发网站门户平台系统等。
3#
 楼主| 发表于 2007-9-19 22:49:35 | 只看该作者
台州网址导航
列表 F
<cfcomponent output="false">

<cffunction name="init" access="public" returntype="AbstractContentState" hint="Constructor.">
<cfargument name="stateName" type="string" required="true" />
<cfset setStateName(arguments.stateName) />
<cfreturn this />
</cffunction>

<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the save method." />
</cffunction>

<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the approve method." />
</cffunction>

<cffunction name="deploy" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the deploy method." />
</cffunction>

<cffunction name="makeDraft" access="public" returntype="void" output="true" hint="">
<cfthrow message="State '#getStateName()#' does not implement the makeDraft method." />
</cffunction>

<cffunction name="getStateName" access="private" returntype="string" output="false" roles="" hint="I return the stateName.">
<cfreturn variables.instance.stateName />
</cffunction>

<cffunction name="setStateName" access="private" returntype="void" output="false" roles="" hint="I set the stateName.">
<cfargument name="stateName" type="string" required="true" hint="stateName" />
<cfset variables.instance.stateName = arguments.stateName />
</cffunction>

</cfcomponent>

<cfcomponent name="DraftState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">

<cffunction name="init" access="public" returntype="DraftState" hint="Constructor.">
<cfset super.init('Draft') />
<cfreturn this />
</cffunction>

<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' saving content item...<br/>
</cfoutput>
</cffunction>

<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' saving content item and alerting reviewer about content for review...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('review') />
</cffunction>

</cfcomponent>

<cfcomponent name="ReviewState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">

<cffunction name="init" access="public" returntype="ReviewState" hint="Constructor.">
<cfset super.init('Review') />
<cfreturn this />
</cffunction>

<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' alerting content author that content is approved...<br/>
State '#getStateName()#' marking content as ready for publish approval...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('publishApproval') />
</cffunction>

<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' alerting content author that content is rejected...<br/>
State '#getStateName()#' setting back to draft mode...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('draft') />
</cffunction>

</cfcomponent>

<cfcomponent name="ApprovePublishState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">

<cffunction name="init" access="public" returntype="ApprovePublishState" hint="Constructor.">
<cfset super.init('ApprovePublish') />
<cfreturn this />
</cffunction>

<cffunction name="approve" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' Marking content as deployed...<br/>
State '#getStateName()#' Pushing live and updating content cache...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('published') />
</cffunction>

<cffunction name="reject" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' alerting reviewer that publishing is rejected...<br/>
State '#getStateName()#' setting back to review mode...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('review') />
</cffunction>

</cfcomponent>

<cfcomponent name="PublishedState" extends="AbstractContentState" hint="I am a Singleton state object. I have no instance data, I only manage logic.">

<cffunction name="init" access="public" returntype="PublishedState" hint="Constructor.">
<cfset super.init('Published') />
<cfreturn this />
</cffunction>

<cffunction name="save" access="public" returntype="void" output="true" hint="">
<cfargument name="context" type="ContentItemContext" required="true" />
<cfset var local = structNew() />
<cfoutput>
State '#getStateName()#' setting the content to draft mode...<br/>
</cfoutput>
<cfset arguments.context.setStateByName('draft') />
</cffunction>

</cfcomponent>

列表G


<h2>Now, using the State pattern:</h2>
<cfset draftState = createObject('component','DraftState').init() />
<cfset reviewState = createObject('component','ReviewState').init() />
<cfset approvePublishState = createObject('component','ApprovePublishState').init() />
<cfset publishedState = createObject('component','PublishedState').init() />
States created...<br/>
<hr/>

<cfset content = createObject('component','ContentItemContext').init(draftState,reviewState,approvePublishState,publishedState,'draft') />
Context created in <strong>draft</strong>...<br/>
<cfset content.save() />
<cfset content.approve() />
<cfset content.approve() />
<cfset content.approve() />
<cfset content.save() />
<cfset content.approve() />
<cfset content.reject() />

<hr/>
<cfset content2 = createObject('component','ContentItemContext').init(draftState,reviewState,approvePublishState,publishedState,'review') />
Context created in <strong>review</strong>...<br/>
<cfset content2.reject() />
<cfset content2.save() />
<cfset content2.approve() />
<cfset content2.approve() />
<cfset content2.reject() />

ContentItemContext所做的是,当调用方法时将每个状态的参数“this”传递给自身。通过这种方式,状态CFC可以执行它的行为,然后指示ContentItemContext进入下个状态。

如果需要添加状态,我们只要创建一个新的状态对象,然后添加部分代码让ContentItemContext知道新添加的状态。所有将来添加的状态的行为被封装起来,尽量避免改变已经存在的代码。

当然,绝大多数情况下,每个状态是一个单独对象,也就是说它们不包含实例数据,且它们只需要创建一次。可能我们需要大量ContentItemContext CFCs(每个内容项对应一个),但是均可用同样一个实例。这就减少了我们需要创建的CFCs数量。虽然并不是总是如此,但也时有发生。

为了展示状态模式,为大家附加一个UML图,如下表A。对于熟悉模式的人,可能会注意到对象之间的关系与策略模式和桥模式类似。虽然它们间的关系类似,但是每种模式的用途不同。

表 A



状态模式UML图



值得一试

本文向大家展示了如何利用状态模式解决CFC中的方法或条件爆炸问题。虽然模式的应用可能会给系统带来另外的复杂性,但是对它所带来的优点来说这还是值得的。
台州维博网络(www.tzweb.com)专门运用PHP+MYSQL/ASP.NET+MSSQL技术开发网站门户平台系统等。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

网站推广
关于我们
  • 台州朗动科技(Tzweb.com)拥有多年开发网站平台系统门户手机客户端等业务的成功经验。主要从事:政企网站,系统平台,微信公众号,各类小程序,手机APP客户端,浙里办微应用,浙政钉微应用、主机域名、虚拟空间、后期维护等服务,满足不同企业公司的需求,是台州地区领先的网络技术服务商!

Hi,扫描关注我

Copyright © 2005-2026 站长论坛 All rights reserved

Powered by 站长论坛 with TZWEB Update Techonolgy Support

快速回复 返回顶部 返回列表