在组合模式中实现访问者(Visitor)模式

在组合模式中实现访问者(Visitor)模式

 

本文从一个给定的实现了组合(Composite)模式的例子开始,说明怎么在这个数据结构上实现业务逻辑代码。依次介绍了非面向对象的方式、在组合结构 中加入方法、使用访问者(Visitor)模式以及用改进后的访问者(Visitor)模式来实现相同的业务逻辑代码,并且对于每种实现分别给出了优缺 点。
kD6z;t mJ'S
{4`UzI2^  读者定位于具有Java程序开发和设计模式经验的开发人员。
n[Ly0jJAVA中文站社区门户Q!aU bUH fN
  读者通过本文可以学到如何在组合(Composite)模式中实现各种不同的业务方法及其优缺点。JAVA中文站社区门户OZj.~8q,_
JAVA中文站社区门户 mL-lD ^,U&v
  组合(Composite)模式JAVA中文站社区门户P*C{mp'v%E*G/ f

+/,Q+XQ:JZ'qlU  组合模式是结构型模式中的一种。GOF的《设计模式》一书中对使用组合模式的意图描述如下:将对象组合成树形结构以表示"部分-整体"的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
AE G_:]$a#rr4Y@
B*ui'pm5S#T]+x3Y   组合模式应用广泛。根据GOF中对组合模式的定义,Composite模式一般由Component接口、Leaf类和Composite类组成。现在 需要对一个软件产品管理系统的实体建模:某公司开发了一系列软件集(SoftwareSet),包含了多种品牌(Brand)的软件产品,就象IBM提供 了Lotus、WebsPhere等品牌。每个品牌下面又有各种产品(Product),如IBM的Lotus下面有 Domino Server/Client产品等。建模后的类图如下(代码可以参见随本文带的附件中,包com.test.entity下所有的源文 件):JAVA中文站社区门户 }K/aY!@%nR8j1e'H

!}4S7h]b5@UTJJAVA中文站社区门户D? A3k.gI&]a,tk-v
JAVA中文站社区门户^3H#i)P^f,p
JAVA中文站社区门户u$I)Pke1`:P

I8a5m]iR0FQ  如图所示:JAVA中文站社区门户Y7AaNi,p}GWEd

(@3Z7{qH-FidI  (1)接口SoftwareComponent就是对应于组合模式中的Component接口,它定义了所有类共有接口的缺省行为
Z%r+I(`t R!q R.ES_n
9~.p&Dp!d /!vu  (2)AbsSoftwareComposite类对应于Composite类,并且是抽象类,所有可以包含子节点的类都扩展这个类。这个类的主要功能是用来存储子部件,实现了接口中的方法,部分可以重用的代码写在此类中
u3VV E9W wJAVA中文站社区门户Z8/i4X�CKkPX _
  (3)SoftwareSet类继承于AbsSoftwareComposite类,对应于软件集,软件集下直接可以包含品牌(Brand),也可以直接包含不属于任何品牌的产品(Product)JAVA中文站社区门户P2vi x!M*b

DJN.m5B$/.e/ W  (4)Brand类继承于AbsSoftwareComposite类,对应于品牌,包含了品牌名属性,并且用来存储Product类的实例JAVA中文站社区门户�Ie5Z9lv

9sQC+i |r;~  (5)Product类就是对应的Leaf类,表示叶子节点,叶子节点没有子节点JAVA中文站社区门户PQnS@B/kh

N AR8z5jl5|6B*i/G  用不同的方法实现业务逻辑
!K`T@:S7h x;K_
4/ n"[,k#E9F  数据结构建立好之后,需要在这个数据结构上添加方法实现业务逻辑。比如现在的这个例子中,有这样的需求:给定一些用户选择好的产品,需要计算出这些选中后软件的总价格。下面开始介绍如何使用各种不同的方法来实现这个业务逻辑。
T U^3F�L^ t!d.kJAVA中文站社区门户 m_ ~+d7`i j
  非面向对象的编程方式
%L/`:MN@|1~~"ma
Z2k e&_na  这种方式下,编程思路最简单:遍历SoftwareSet实例中的所有节点,如果遍历到的当前对象是Product的话就累加,否则继续遍历下一层直到全部遍历完毕。代码片断如下:JAVA中文站社区门户tY mF,C}(y

7X"T d@)s.@-P(tI(h/** 
6}%y*y%sx * 取得某个SoftwareComponent对象下面所有Product的价格 JAVA中文站社区门户!p(_eH S$QKt }/J
 * @param brand JAVA中文站社区门户.oe U_b8x
 * @return 
5g{q*n H)JS */ 
&x1r;^3NyKYpublic double getTotalPrice(SoftwareComponent softwareComponent) { 
^9DmDhtP6u    SoftwareComponent temp = softwareComponent; 
:{"jcX!T!c2[0I    double totalPrice = 0; 
Wj.H^9qf3g7^    //如果传入的实例是SoftwareSet的类型 
"I"p#E.y#y-g    if (temp instanceof SoftwareSet) { JAVA中文站社区门户dZ1r*D xw
        Iterator it = ((SoftwareSet) softwareComponent).getChilds() JAVA中文站社区门户&|O_"K9S
                .iterator(); 
%a:q-{5s i:[        while (it.hasNext()) {//遍历 JAVA中文站社区门户'J'Cv {6T#W
            temp = (SoftwareComponent) it.next(); JAVA中文站社区门户 ntTkY&C,lwA
            //如果子对象是Product类型的,直接累加 JAVA中文站社区门户Jk(f"J!cQ
            if (temp instanceof Product) { 
)Az-CX)k.Hd                Product product = (Product) temp; JAVA中文站社区门户v's?v TY8k R/}7f
                totalPrice += product.getPrice(); JAVA中文站社区门户[�NY8u [!Nz
            } else if (temp instanceof Brand) {  
L P9}G&^:C bP�e6].C.S            //如果子对象是Brand类型的,则遍历Brand下面所有的产品并累加 
yoep�C t*_"c+Q                Brand brand = (Brand) temp; 
$oldZ8B                totalPrice += getBrandPrice(brand); JAVA中文站社区门户#sn"?J"e[0l
            } 
mrpT,} FI        } 
&ezN4^2P6[%IJ�Gi    } else if (temp instanceof Brand) { JAVA中文站社区门户:f5j8kR~D F
        //如果传入的实例是SoftwareSet的类型,则遍历Brand下面所有的产品并累加 
2DYPW8l4T        totalPrice += getBrandPrice((Brand) temp); JAVA中文站社区门户c]+yqs y%U
    } else if (temp instanceof Product) { JAVA中文站社区门户,NP(YN'R
        //如果子对象是Product类型的,直接返回价格 JAVA中文站社区门户4f }9Mv&h5yn7y
        return ((Product) temp).getPrice(); 
WJ%E3B}1z,aTY    } JAVA中文站社区门户1oD3r#{J9?l
    return totalPrice; 
&/dBO*?�i}
? CH9})sQ,I C"m0p
k{T e0H&rJ/** 
!vx�FO}b!q o"n * 取得某个Brand对象下面所有Product的价格 JAVA中文站社区门户Gw"A3S(hqE
 * @param brand 
wU,Hx9F%` * @return JAVA中文站社区门户'P-Y,LW nI
 */ 
H'y`YX.^n2fmFprivate double getBrandPrice(Brand brand) { JAVA中文站社区门户7^VV js
    Iterator brandIt = brand.getChilds().iterator(); 
ZL'X9|&n9bf    double totalPrice = 0; JAVA中文站社区门户4~T!e%SXt
    while (brandIt.hasNext()) { JAVA中文站社区门户3a�Oc ?@7^S
        Product product = (Product) brandIt.next(); 
5L*w6G:tc#b        totalPrice += product.getPrice(); JAVA中文站社区门户I S%m1v0b(p,WV]K
    } 
G)QYq'Un    return totalPrice; 
2GM{/.Z T.BJAVA中文站社区门户{)_n#]%FPyA.DT [W

wx^(~ux,i  这段代码的好处是实现业务逻辑的时候无需对前面已经定好的数据结构做改动,并且效率比较高;缺点是代码凌乱而且频繁使用了instanceof判断类型和强制类型转换,代码的可读性不强,如果层次多了代码就更加混乱。
1[!i:n/Y.@)L e
(b-h0pz'?A(aeoD  面向对象的编程方式(将计算价格的方法加入数据结构中)JAVA中文站社区门户 t v$H3Ulg4m%R
JAVA中文站社区门户x2@Ez;v*nS cHloL
  下面我们采用面向对象的方式,可以这么做:在接口SoftWareComponent中加入一个方法,名叫getTotalPrice,方法的声明如下:
G_8? kRbQe
-Pw{k'j)N9KA-o/c/** JAVA中文站社区门户9tT&v2e8SD/H
 * 返回该节点中所有子节点对象的价格之和 JAVA中文站社区门户 ANK F&u"A
 * @return 
2~0|-E;g0X */ 
ufo5X[%r-Y,Dpublic double getTotalPrice(); 
v&w.m!Q{F-c-/G  由于类Brand和SoftwareSet都继承了AbsSoftwareComposite,我们只需在类AbsSoftwareComposite中实现该方法getTotalPrice方法即可,如下:
+i;} GyT;a1kJAVA中文站社区门户(g!Ae6@rS
public double getTotalPrice() { 
[` H[XFMx    Iterator it = childs.iterator(); 
$vF+d q4|BHz(I    double price = 0; 
DAJt)oiV.Aq~@    while (it.hasNext()) { JAVA中文站社区门户2A8j4jV3e8cY
        SoftwareComponent softwareComponent = (SoftwareComponent) it.next(); 
`aW[.T"Df                       //自动递归调用各个对象的getTotalPrice方法并累加 JAVA中文站社区门户+g/`!wX7o*Yyq!I
        price += softwareComponent.getTotalPrice(); JAVA中文站社区门户 S dro4/#q
    } JAVA中文站社区门户%{ ];{r uHU2F
    return price; 
'HV9Xv7wyR
Wo|4qdhQ  在Product类中实现如下:JAVA中文站社区门户p,^(K8M4R/k/T?*`
JAVA中文站社区门户Cn*Z[u)fK,gBY
public double getTotalPrice(){ JAVA中文站社区门户�Ew8HCMhr
    return price; JAVA中文站社区门户{}aea%u

HXm"J@I6m&?z  在外面需要取得某个对象的总价格的时候只需这样写(在本文的例子com.test.business.SoftwareManager中可以找到这段代码):JAVA中文站社区门户cD.n6?m0w

X2E^-O8@aM$v
[W"DZ�YL// getMockData()方法返回数据 JAVA中文站社区门户;G'W${@._.c
SoftwareComponent data = getMockData(); JAVA中文站社区门户X7Ii*KV,c
//只需直接调用data对象的getTotalPrice 方法就可以返回该对象下所有product对象的价格 
?]9R+jy-S ^#xfydouble price = data. getTotalPrice(); 
Z2R-]Kq/L//找到某个对象后直接调用其getTotalPrice方法也可以返回总价格 
c"p$~kqhNH Ud$Uprice = data. findSoftwareComponentByID("id").getTotalPrice(); 
-`][Ah!n7x   现在把业务逻辑的实现都放在了数据结构中(组合模式的结构中),好处很明显,每个类只管理自己相关的业务代码的实现,跟前面举的面向过程方式的实现方式 相比,没有了instanceof和强制类型转换。但是不好的地方是如果需要增加新的业务方法的话就很麻烦,必须在接口 SoftWareComponent中首先声明该方法,然后在各个子类中实现并且重新编译。JAVA中文站社区门户 s#vd&GR*_

t3y3/f:v%hO,U  使用访问者模式JAVA中文站社区门户4oI0km-De y
JAVA中文站社区门户wE`l-/+/"W}Rz
   使用访问者模式就能解决上面提到的问题:如果要经常增加或者删除业务功能方法的话,需要频繁地对程序进行重新实现和编译。根据面向对象设计原则之一的 SRP(单一职责原则)原则,如果一个类承担了多于一个的职责,那么引起该类变化的原因就会有多个,就会导致脆弱的设计,在发生变化时,原有的设计可能会 遭到意想不到的破坏。下面我们引入了一个叫做Visitor的接口,该接口中定义了针对各个子类的访问方法,如下所示:
u3zR4T3NJAVA中文站社区门户~-aW6y$`.t0K mXN
public interface Visitor { JAVA中文站社区门户.[!r/my"u)b
    public void visitBrand(Brand brand); 
iZ~ zh    public void visitSoftwareSet(SoftwareSet softwareSet); JAVA中文站社区门户'N6h?K4~ Fi
    public void visitProduct(Product product); JAVA中文站社区门户9HV'{ ]yzC%m?8y!~
JAVA中文站社区门户0}F*{ f2v5U7BZ3G!s
  visitBrand方法是访问Brand对象节点的时候用的,剩下的方法依次类推。并在接口SoftwareComponent中增加一个方法:
;z$Sad ~_G"o8w#a3p2E oJAVA中文站社区门户oL_4Er$b4D3/;u'wc
public void accept(Visitor visitor); 
8eK z$JYY9I]*y  在SoftwareSet中实现接口中的accept方法,首先直接调用Visitor接口中的visitSoftwareSet方法,传入的参数是本身对象,然后递归调用子对象的accept方法:
0}$[Q4t gV?JAVA中文站社区门户d2eJW:v8S/iI#C

L }my2snYL`public void accept(Visitor visitor) { 
!Ij%gl+el    visitor.visitSoftwareSet(this); JAVA中文站社区门户ys5S,GT&q;w1c
    Iterator it = childs.iterator(); 
7k q6]^"K9YN$@    while (it.hasNext()) { JAVA中文站社区门户_k/[a@pi+e'C+W
        SoftwareComponent component = (SoftwareComponent)it.next(); JAVA中文站社区门户 U$QBXmVL^0@
        component.accept(visitor); JAVA中文站社区门户l0q-VT5q E$Y
    } JAVA中文站社区门户&Z hG:n)m7M~5[9{
JAVA中文站社区门户,hx2E+aXxts l
   在Brand中实现接口中的accept方法,首先直接调用Visitor接口中的visitBrand方法,传入的参数是本身对象,然后递归调用子对象的accept方法:JAVA中文站社区门户o.L6g!bvb8//^
JAVA中文站社区门户%P&M`F}(j{
public void accept(Visitor visitor) { JAVA中文站社区门户MjO[;M?,B$UVj&h
    visitor.visitBrand(this); 
$s2jI$I`d,u    Iterator it = childs.iterator(); 
!Y*Z}�`d5rF dU-I    while (it.hasNext()) { JAVA中文站社区门户nRD-TN0v&}PO
        SoftwareComponent component = (SoftwareComponent)it.next(); 
weq:usw        component.accept(visitor); JAVA中文站社区门户Xe/Z9x2C5Ws
    } JAVA中文站社区门户;G ]H`3q$S`h
JAVA中文站社区门户|4j#W+a%QT
   其实在上面的两个类的实现中可以将遍历子节点并调用其accept方法的代码写到父类AbsSoftwareComposite中的某个方法中,然后直接调用父类中的这个方法即可。这里为了解释方便分别写在了两个子类中。
/L/N cl)G[^aY$] C8_O  
P!n4Zv(I*BUrz  在Product中实现接口中的accept方法,直接调用Visitor接口的visitProduct方法即可:JAVA中文站社区门户(pc_3d)mt+{

"K(v.F*G(D
n T"n!W*M [!Ypublic void accept(Visitor visitor) { JAVA中文站社区门户:} I)ef3@-x4HC$u
    visitor.visitProduct(this); JAVA中文站社区门户3E}0m5~2a B I%I ~
JAVA中文站社区门户9V Y$u*rv5MT.e'O/^-}
   下面需要实现Visitor接口,类名是CaculateTotalPriceVisitor,实现了计算总价格的业务逻辑,实现代码如下所示:JAVA中文站社区门户W6y"rm,Z

6K*w-b%]*P
uN,t#D3GjBpublic class CaculateTotalPriceVisitor implements Visitor { 
n_S+DG:SVrz|g/k|    private double totalPrice;     
;M`�I6Nl,b8?    public void visitBrand(Brand brand) { 
R,h6E/ Fht'u*^    } JAVA中文站社区门户,] ]+e�lAhu2td)C&/;{&V
    public void visitSoftwareSet(SoftwareSet softwareSet) { 
E4J W3YJ Qc I    } JAVA中文站社区门户2u[D h*Nr3k�q
    public void visitProduct(Product product) { 
P!sT&e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值