跟JBPM学设计模式之组合模式
模式简介
组合模式,将对象组合成树形结构以表示“部分与整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
合成模式属于对象的结构模式,合成模式将对象组织到树形的结构中,可以用来描述整体与部分的关系。合成模式可以是客户端将单纯元素和复合元素同等的看待。
树形结构在各种类型的语言中发挥了巨大的作用,一个基于集成类型的等级结构就是一个树结构;同样一个基于合成的对象的结构也是一个树结构。在编程中我们一般遇到的树结构都是连通的有方向的树形结构。
有向树结构可以分为三种,从上到下、从下到上、双向的。这三种图中,树的节点和他们之间的关系都是一样的,但是连接他们的关系的方向却是不一样的。
在由上到下的树图(如下图1-1)中,每一个树枝节点都有箭头指向它的所有子节点,从而客户可以要求每个树枝节点都给出自己所有的子节点,而一个节点却并不知道它的父节点。在这样的树结构中信息可以按照箭头自上向下传播。
图 1-1
在一个由下向上的树图(如下图1-2)中,每个节点的箭头都指向它的父节点,但是一个父节点却不知道其子节点。信息可以按照箭头所指的方向自下向上传播。
图 1-2
在一个双向的树图(如下图1-3)中,每一个节点都同时知道它的父节点和子节点。信息可以按照箭头的方向向两个方向传播。
图 1-3
一个树结构是由两种节点组成的,树枝节点和树叶节点。前者可以包括子节点,后者不能有子节点。所以可以说树枝节点是承载树叶节点的容器。
组合模式的结构如下图1-4中所示,在图中我们可以看到其涉及到三个角色:
抽象构件角色(Compnent):这是一个抽象角色,它给参加组合的对象规定共有的接口和默认行为。
树叶构件角色(Leaf):代表参加组合的树叶对象,树叶对象没有子对象,规定了参加组合的原始对象的行为。
树枝构件角色(Composite):代表参加组合的有子对象的对象,给出了树枝构件对象的行为.
图 1-4
合成模式可以不提供父对象的管理方法,但是必须提供诸如添加、删除、获取子对象的的管理方法;所以根据所实现的接口的是否提供相应的管理方法分为两种形式,分别称为安全式和透明式。虽然这是模式的实现问题,但是却影响到模式结构的细节。
透明式组合模式(如下图1-5),在Component里声明所有用来管理子类对象的方法。这样所有的构件类都具有相同的接口。从接口层次看来,树枝对象和树叶对象是没有区别的,客户可以同等的对待所有的对象。但是其缺点就是不够安全,因为树叶节点是不可能有子对象的,因此其管理子对象的方法是没有意义的,但是编译时期不会出错,而只会在运行时期才会出错。
图 1-5
安全式组合模式(如下图1-6),在Composite里声明所有管理子类对象的方法。这样的做法是安全的做法,因为树叶节点本来根本就没有管理子类对象的方法,因此,如果对树叶对象使用这些方法,程序就会在编译器出错,而不是等到运行时才出错。
图 1-6
JBPM中的组合模式
JBPM中的活动节点模型具有透明组合模式的特征。我们知道JBPM中的节点有复合类型,也就是可以承载子节点;虽然从业务上来说,只有group类型的节点才能承载子节点,但是从JBPM的ActivityImpl的模型设计上来看,任何类型的节点都有ActivityImpl承载,自然任何类型的节点都可以承载子节点。具体的结构图如下1-7所示
图1-7
抽象构件角色:这里由Activity和CompositeElement共同完成抽象构件角色。前者向客户提供节点相关的业务功能接口,后者提供树叶构件需要具有的管理子类对象的方法接口。
View Code
public
interface Activity {
/**
the short display name given to this element.
*/
String getName();
/**
the list of outgoing transitions.
* Caution: the actual member is returned. No copy is made.
*/
List<?
extends Transition> getOutgoingTransitions();
/**
the default outgoing transition.
*/
Transition getDefaultOutgoingTransition();
/**
the first leaving transition with the given name or null of no
* such leaving transition exists. If the multiple transitions have
* the given transition name, the first (in order of {
@link
#getOutgoingTransitions()})
* will be returned.
*
*
@param
transitionName is the name of the transition to take. A null value will
* match the first unnamed transition.
*/
Transition getOutgoingTransition(String transitionName);
/**
indicates if a leaving transition with the given transitionName exists.
* A null value matches an unnamed transition.
*/
boolean hasOutgoingTransition(String transitionName);
/**
indicates if this activity has leaving transitions
*/
boolean hasOutgoingTransitions();
/**
the leaving transitions, keyed by transition name. If a transition with
* the same name occurs mutltiple times, the first one is returned.
* Leaving transitions with a null value for their name are not included
* in the map.
* Beware: the actual member is returned. No copy is made. In fact, the
* returned map is maintained as a cache. So updates to the map will
* influence subsequent retrievals of outgoing transitions by name.
*/
Map<String, ?
extends Transition> getOutgoingTransitionsMap();
/**
searches for the given transitionName in this activity and then up the
* parent chain. Returns null if no such transition is found.
*/
Transition findOutgoingTransition(String transitionName);
/**
the list of arriving transitions.
* Beware: the actual member is returned. No copy is made.
*/
List<?
extends Transition> getIncomingTransitions();
/**
indicates if this activity has arriving transitions
*/
boolean hasIncomingTransitions();
/**
retrieve the parent activity in the composite activity structure. This is
* different from {
@link
ObservableElement#getParent()} in that it is restricted
* to the parent activities. It doesn't take into account the process definition.
*/
Activity getParentActivity();
/**
indicates if this processDefinition has activities.
*/
boolean hasActivities();
/**
the list of direct composite activities. Recursively contained
* activities are not included in the list.
* Beware: the actual member is returned. No copy is made.
*/
List<?
extends Activity> getActivities();
/**
indicates if an activity with the given name exists directly in
* this element. Only the direct contained activities are
* searched. No recursive search is made.
*/
boolean hasActivity(String activityName);
/**
the first composite activity with the given name or null of no
* such activity exists. Only the direct contained activities are
* searched. No recursive search is made.
*/
Activity getActivity(String activityName);
/**
searches for the given activity in this element recursively,
* including this activity and all child activities. The search
* is done depth-first. A null value for activityName matches a activity
* without a name.
*/
Activity findActivity(String activityName);
/**
the composite activities, keyed by activity name. If an activity
* with the same name occurs mutltiple times, the first in the list
* is included in the map. Activities with a null value for their name
* are not included in the map.
* Beware: the actual member is returned. No copy is made. In fact, the
* returned map is maintained as a cache. So updates to the map will
* influence subsequent retrievals of activities by name.
*/
Map<String, ?
extends Activity> getActivitiesMap();
/**
the type of this activity which corresponds to the xml tag
*/
String getType();
}
View Code
public
interface CompositeElement
extends ObservableElement {
/**
indicates if this processDefinition has activities.
*/
boolean hasActivities();
/**
the list of direct composite activities. Recursively contained
* activities are not included in the list.
* Beware: the actual member is returned. No copy is made.
*/
List<?
extends Activity> getActivities();
/**
indicates if an activity with the given name exists directly in
* this element. Only the direct contained activities are
* searched. No recursive search is made.
*/
boolean hasActivity(String activityName);
/**
the first composite activity with the given name or null of no
* such activity exists. Only the direct contained activities are
* searched. No recursive search is made.
*/
Activity getActivity(String activityName);
/**
searches for the given activity in this element recursively,
* including this activity and all child activities. The search
* is done depth-first. A null value for activityName matches a activity
* without a name.
*/
Activity findActivity(String activityName);
/**
the composite activities, keyed by activity name. If an activity
* with the same name occurs mutltiple times, the first in the list
* is included in the map. Activities with a null value for their name
* are not included in the map.
* Beware: the actual member is returned. No copy is made. In fact, the
* returned map is maintained as a cache. So updates to the map will
* influence subsequent retrievals of activities by name.
*/
Map<String, ?
extends Activity> getActivitiesMap();
}
树枝构件角色:树枝构件由CompositeElementImpl担当,其具体实现对节点的管理功能。同时ActivityImpl直接继承CompositeElementImpl,所以它也是树枝构件。
View Code
public
abstract
class CompositeElementImpl
extends ScopeElementImpl
implements CompositeElement {
private
static
final
long serialVersionUID = 1L;
protected List<ActivityImpl> activities;
transient
protected Map<String, ActivityImpl> activitiesMap;
//
nested activities
////////////////////////////////////////////////////////////
/
/**
* creates a nested activity. Also the nested activity's parent pointer will be set
* appropriatly.
*/
public ActivityImpl createActivity() {
return createActivity(
null);
}
/**
* creates a nested activity with the given name. Also the nested activity's parent pointer will be set
* appropriatly.
*
@param
activityName may be null.
*/
public ActivityImpl createActivity(String activityName) {
ActivityImpl activity =
new ActivityImpl();
activity.setName(activityName);
addActivity(activity);
return activity;
}
public Activity addActivity(ActivityImpl activity) {
activity.setProcessDefinition(processDefinition);
if (activities==
null) {
activities =
new ArrayList<ActivityImpl>();
}
if (! activities.contains(activity)) {
activities.add(activity);
}
activitiesMap =
null;
return activity;
}
/**
removes the given activity from the nested activities.
* Also the activity's parent will be nulled.
* This method will do nothing if the activity is null or if
* the activity is not in the list of nested activities.
* If the activity is actually removed from the list of
* activities, the activity's source will be nulled.
* In case this is the activity that was in the
* activitiesMap and another activity exists with the same
* name, that activity (the first) will be put in the
* activitiesMap as a replacement for the removed activity.
*/
public
boolean removeActivity(ActivityImpl activity) {
if ( (activity!=
null)
&& (activities!=
null)
) {
boolean isRemoved = activities.remove(activity);
if (isRemoved) {
activity.setParentActivity(
null);
if (activities.isEmpty()) {
activities =
null;
}
activitiesMap =
null;
}
return isRemoved;
}
return
false;
}
/**
the first nested activity with the given name or null of no
* such activity exists.
*/
public ActivityImpl getActivity(String activityName) {
return (getActivitiesMap()!=
null ? activitiesMap.get(activityName) :
null);
}
/**
is this activity present ?
*/
public
boolean hasActivity(String activityName) {
return ((getActivitiesMap()!=
null) && (activitiesMap.containsKey(activityName)));
}
public ActivityImpl findActivity(String activityName) {
if (activities!=
null) {
for(ActivityImpl n : activities) {
ActivityImpl activity = n.findActivity(activityName);
if (activity!=
null) {
return activity;
}
}
}
return
null;
}
/**
the list of nested activities.
* Beware: the actual member is returned. No copy is made.
*/
public List<?
extends Activity> getActivities() {
return activities;
}
/**
the nested activities, keyed by activity name. If a activity with
* the same name occurs mutltiple times, the first in the list
* is included in the map.
* Activities with a null value for their name are not included
* in the map.
* Beware: the actual member is returned. No copy is made.
*/
public Map<String, ?
extends Activity> getActivitiesMap() {
if (activitiesMap ==
null) {
this.activitiesMap = ActivityImpl.getActivitiesMap(activities);
}
return activitiesMap;
}
/**
indicates if this processDefinition has activities.
*/
public
boolean hasActivities() {
return ((activities!=
null) && (!activities.isEmpty()));
}
}
树叶构件角色:树叶构件角色由ActivityImpl来担当,ActivityImpl在没有子节点的时候,就是真正的树叶构件了。
View Code
public
class ActivityImpl
extends CompositeElementImpl
implements Activity {
private
static
final
long serialVersionUID = 1L;
protected ActivityBehaviour activityBehaviour;
protected
boolean isActivityBehaviourStateful =
false;
protected Descriptor activityBehaviourDescriptor;
protected List<TransitionImpl> outgoingTransitions =
new ArrayList<TransitionImpl>();
protected List<TransitionImpl> incomingTransitions =
new ArrayList<TransitionImpl>();
protected TransitionImpl defaultOutgoingTransition;
protected ActivityImpl parentActivity;
protected String type;
protected Continuation continuation = Continuation.SYNCHRONOUS;
protected ActivityCoordinatesImpl coordinates;
//
Do not initialize. Caching is based on the nullity of this map
transient
protected Map<String, TransitionImpl> outgoingTransitionsMap =
null;
/**
* Use {
@link
ProcessDefinitionImpl#createActivity()} or {
@link
ActivityImpl#createActivity()} instead.
*/
public ActivityImpl() {
super();
}
//
specialized activity containment methods
////////////////////////////////////
/
public ActivityImpl addActivity(ActivityImpl activity) {
activity.setParentActivity(
this);
super.addActivity(activity);
return activity;
}
public ActivityImpl findActivity(String activityName) {
if (activityName==
null) {
if (name==
null) {
return
this;
}
}
else
if (activityName.equals(name)) {
return
this;
}
return
super.findActivity(activityName);
}
//
outgoing transitions
//////////////////////////////////////////////////////
/**
creates an outgoing transition from this activity.
*/
public TransitionImpl createOutgoingTransition() {
//
create a new transition
TransitionImpl transition =
new TransitionImpl();
transition.setProcessDefinition(processDefinition);
//
wire it between the source and destination
addOutgoingTransition(transition);
//
if there is no default transition yet
if (defaultOutgoingTransition==
null) {
//
make this the default outgoing transition
defaultOutgoingTransition = transition;
}
return transition;
}
/**
* adds the given transition as a leaving transition to this activity.
* Also the source of the transition is set to this activity.
* Adding a transition that is already contained in the leaving
* transitions has no effect.
*
@return
the added transition.
*
@throws
NullPointerException if transition is null.
*/
public Transition addOutgoingTransition(TransitionImpl transition) {
if (! outgoingTransitions.contains(transition)) {
transition.setSource(
this);
transition.setSourceIndex(outgoingTransitions.size());
outgoingTransitions.add(transition);
clearOutgoingTransitionsMap();
}
return transition;
}
/**
* removes the given transition from the leaving transitions.
* Also the transition's source will be nulled.
* This method will do nothing if the transition is null or if
* the given transition is not in the list of this activity's leaving
* transitions.
* In case this is the transition that was in the
* outgoingTransitionsMap and another transition exists with the same
* name, that transition (the first) will be put in the
* outgoingTransitionsMap as a replacement for the removed transition.
* If the transition is actually removed from the list of
* leaving transitions, the transition's source will be nulled.
*/
public
boolean removeOutgoingTransition(TransitionImpl transition) {
if (transition!=
null) {
boolean isRemoved = outgoingTransitions.remove(transition);
if (isRemoved) {
transition.setSource(
null);
clearOutgoingTransitionsMap();
}
return isRemoved;
}
return
false;
}
/**
the first leaving transition with the given name or null of no
* such leaving transition exists.
*/
public TransitionImpl getOutgoingTransition(String transitionName) {
return (getOutgoingTransitionsMap()!=
null ? outgoingTransitionsMap.get(transitionName) :
null);
}
/**
searches for the given transitionName in this activity and then up the
* parent chain. Returns null if no such transition is found.
*/
public TransitionImpl findOutgoingTransition(String transitionName) {
TransitionImpl transition = getOutgoingTransition(transitionName);
if (transition!=
null) {
return transition;
}
if (parentActivity!=
null) {
return parentActivity.findOutgoingTransition(transitionName);
}
return
null;
}
/**
searches for the default transition in this activity and then up the
* parent chain. Returns null if no such transition is found.
*/
public TransitionImpl findDefaultTransition() {
if (defaultOutgoingTransition!=
null) {
return defaultOutgoingTransition;
}
if (parentActivity!=
null) {
return parentActivity.findDefaultTransition();
}
return
null;
}
/**
the list of leaving transitions.
* Beware: the actual member is returned. No copy is made.
*/
public List<?
extends Transition> getOutgoingTransitions() {
return outgoingTransitions;
}
/**
indicates if a leaving transition with the given transitionName exists.
*/
public
boolean hasOutgoingTransition(String transitionName) {
return (getOutgoingTransition(transitionName)!=
null);
}
/**
indicates if this activity has leaving transitions
*/
public
boolean hasOutgoingTransitions() {
return !outgoingTransitions.isEmpty();
}
/**
sets the outgoingTransitions to the given list of outgoingTransitions.
* A copy of the collection is made. Also the outgoingTransitionsMap will
* be updated and the source of all the transitions in the given list will
* be set to this activity.
* In case there was a leaving transitions list present, these transition's
* source will be nulled.
*/
public
void setOutgoingTransitions(List<TransitionImpl> outgoingTransitions) {
if (!
this.outgoingTransitions.isEmpty()) {
List<TransitionImpl> removedTransitions =
new ArrayList<TransitionImpl>(outgoingTransitions);
for (TransitionImpl removedTransition: removedTransitions) {
removeOutgoingTransition(removedTransition);
}
}
if (outgoingTransitions!=
null) {
this.outgoingTransitions =
new ArrayList<TransitionImpl>();
for (TransitionImpl addedTransition: outgoingTransitions) {
addOutgoingTransition(addedTransition);
}
}
else {
this.outgoingTransitions =
new ArrayList<TransitionImpl>();
}
clearOutgoingTransitionsMap();
}
//
arriving transitions
////////////////////////////////////////////////////
/
/**
* adds the given transition as an arriving transition to this activity.
* Also the source of the transition is set to this activity.
*
@return
the added transition.
*
@throws
NullPointerException if transition is null.
*/
public Transition addIncomingTransition(TransitionImpl transition) {
transition.setDestination(
this);
incomingTransitions.add(transition);
return transition;
}
/**
removes the given transition if it is contained in the arriving
* transitions of this activity. If this transition was actually removed,
* its destination pointer is nulled.
*
@return
true if a transition was removed.
*/
public
boolean removeIncomingTransition(TransitionImpl transition) {
if ( (transition!=
null) && (incomingTransitions.remove(transition))) {
transition.setDestination(
null);
return
true;
}
return
false;
}
/**
the list of arriving transitions.
* Beware: the actual member is returned. No copy is made.
*/
public List<?
extends Transition> getIncomingTransitions() {
return incomingTransitions;
}
/**
indicates if this activity has arriving transitions
*/
public
boolean hasIncomingTransitions() {
return !incomingTransitions.isEmpty();
}
/**
sets the incomingTransitions to the given list of incomingTransitions.
* A copy of the collection is made. Also the destination of all the transitions
* in the given list will be set to this activity.
* In case there was an arriving transitions list present, these transition's
* destination will be nulled.
*/
public
void setIncomingTransitions(List<TransitionImpl> incomingTransitions) {
if (!
this.incomingTransitions.isEmpty()) {
for (TransitionImpl removedTransition:
this.incomingTransitions) {
removedTransition.setDestination(
null);
}
}
if (incomingTransitions!=
null) {
this.incomingTransitions =
new ArrayList<TransitionImpl>(incomingTransitions);
for (TransitionImpl addedTransition: incomingTransitions) {
addedTransition.setDestination(
this);
}
}
else {
this.incomingTransitions =
null;
}
}
/**
the leaving transitions, keyed by transition name. If a transition with
* the same name occurs mutltiple times, the first one is returned.
* Leaving transitions with a null value for their name are not included
* in the map.
* Beware: the actual member is returned. No copy is made.
*/
public Map<String, ?
extends Transition> getOutgoingTransitionsMap() {
if(outgoingTransitionsMap ==
null){
outgoingTransitionsMap =
new HashMap<String, TransitionImpl>();
for (TransitionImpl transition: outgoingTransitions) {
if (!outgoingTransitionsMap.containsKey(transition.getName())) {
outgoingTransitionsMap.put(transition.getName(), transition);
}
}
}
return outgoingTransitionsMap;
}
void clearOutgoingTransitionsMap() {
outgoingTransitionsMap =
null;
}
//
various helper methods
//////////////////////////////////////////////////
/
static Map<String, ActivityImpl> getActivitiesMap(List<ActivityImpl> activities) {
Map<String, ActivityImpl> map =
null;
if (activities!=
null) {
map =
new HashMap<String, ActivityImpl>();
for (ActivityImpl activity: activities) {
if (! map.containsKey(activity.getName())) {
map.put(activity.getName(), activity);
}
}
}
return map;
}
public String toString() {
if (name!=
null)
return "activity("+name+")";
if (dbid!=0)
return "activity("+dbid+")";
return "activity("+System.identityHashCode(
this)+")";
}
/**
collects the full stack of parent in a list. This activity is the
* first element in the chain. The process definition will be the last element.
* the chain will never be null.
*/
public List<ObservableElementImpl> getParentChain() {
List<ObservableElementImpl> chain =
new ArrayList<ObservableElementImpl>();
ObservableElementImpl processElement =
this;
while (processElement!=
null) {
chain.add(processElement);
processElement = processElement.getParent();
}
return chain;
}
public
boolean isAsync() {
return ! (continuation==Continuation.SYNCHRONOUS);
}
public
boolean contains(ActivityImpl activity) {
while (activity!=
null) {
if (activity.getParent()==
this) {
return
true;
}
activity = activity.getParentActivity();
}
return
false;
}
//
customized getters and setters
//////////////////////////////////////////
/
public ActivityBehaviour getActivityBehaviour() {
if (activityBehaviour!=
null) {
return activityBehaviour;
}
if (activityBehaviourDescriptor!=
null) {
ActivityBehaviour createdBehaviour = (ActivityBehaviour) ReflectUtil.instantiateUserCode(activityBehaviourDescriptor, processDefinition,
null);
if (!isActivityBehaviourStateful) {
activityBehaviour = createdBehaviour;
}
return createdBehaviour;
}
return
null;
}
//
getters and setters
//////////////////////////////////////////////////////
public ObservableElementImpl getParent() {
return (parentActivity!=
null ? parentActivity : processDefinition);
}
public String getName() {
return name;
}
public
void setName(String name) {
this.name = name;
}
public TransitionImpl getDefaultOutgoingTransition() {
return defaultOutgoingTransition;
}
public
void setDefaultOutgoingTransition(TransitionImpl defaultOutgoingTransition) {
this.defaultOutgoingTransition = defaultOutgoingTransition;
}
public ActivityImpl getParentActivity() {
return parentActivity;
}
public
void setParentActivity(ActivityImpl parentActivity) {
this.parentActivity = parentActivity;
}
public String getType() {
return type;
}
public
void setType(String type) {
this.type = type;
}
public ActivityCoordinatesImpl getCoordinates() {
return coordinates;
}
public
void setCoordinates(ActivityCoordinatesImpl coordinates) {
this.coordinates = coordinates;
}
public Continuation getContinuation() {
return continuation;
}
public
void setContinuation(Continuation continuation) {
this.continuation = continuation;
}
public
void setActivityBehaviour(ActivityBehaviour activityBehaviour) {
this.activityBehaviour = activityBehaviour;
}
public Descriptor getActivityBehaviourDescriptor() {
return activityBehaviourDescriptor;
}
public
void setActivityBehaviourDescriptor(Descriptor activityBehaviourDescriptor) {
this.activityBehaviourDescriptor = activityBehaviourDescriptor;
}
public
boolean isActivityBehaviourStateful() {
return isActivityBehaviourStateful;
}
public
void setActivityBehaviourStateful(
boolean isActivityBehaviourStateful) {
this.isActivityBehaviourStateful = isActivityBehaviourStateful;
}
}
这里JBPM并没有严格按照模式的定义进行实现,而是根据业务进行了模型的分离。由于对子节点的管理是由流程引擎内部进行管理的,是不允许客户进行更改的;只需要向客户开放节点相关的业务接口;所以进行这样的实现还是符合业务实际场景的。
JBPM中的对多历史数据库支持的session模型具有安全组合模式的特征的。具体的结构如下图(1-8)所示
图 1-8
抽象构件角色:这里由HistorySession担当,其提供启动记录历史信息的接口。每个该类的实例都会对应一个独立的数据库。
View Code
public
interface HistorySession {
void process(HistoryEvent historyEvent);
}
树叶构件角色:这里由HistorySessionImpl担当,其提供启动记录历史信息的具体实现。
View Code
public
class HistorySessionImpl
implements HistorySession {
public
void process(HistoryEvent historyEvent) {
historyEvent.process();
}
}
树枝构件角色:这里由HistorySessionChain担当,其作为承载子对象的容器,可以承载代表不同历史库的HistorySession的对象。
View Code
public
class HistorySessionChain
implements HistorySession {
protected List<HistorySession> historySessions =
new ArrayList<HistorySession>();
public HistorySessionChain(List<HistorySession> historySessions) {
this.historySessions = historySessions;
}
public
void process(HistoryEvent historyEvent) {
for (HistorySession historySession: historySessions) {
historySession.process(historyEvent);
}
}
}