首页 > 编程 > .NET > 正文

VB.Net中文教程(2) Composite样式

2024-07-10 13:01:18
字体:
来源:转载
供稿:网友
1. 从whole-part关系谈起
回想传统的软件师﹐常甚专注于撰写程序(procedure) 来处理某些资料(data)﹐较少关心软件的整体结构(architecture)。在现在的oo软件中﹐把资料及其相关的程序结合在一起﹐封装(encapsulate) 在对象之中。软件师在使用对象时﹐通常把对象视为黑箱(black-box) ﹐不会关心于对象内部之细节﹔因之能专注于对象之间的关系。软件师的主要工作﹐就是在于建立对象之间的互助合作(collaboration) 关系﹐为对象安排应尽之角色(role)﹐至于对象内部之细节﹐反而不顶重要。如此﹐可让软件师着重于软件的整体架构上﹐而不会一头栽进程序的执行细节之中。这避免了见树不见林的缺点﹐放宽了软件师的眼界﹐为软件的多用途(reusability) 及弹性(flexibility) 着想﹐可创造长寿的软件﹗
对象之间的常见关系有许多种﹐其中之一就是whole-part关系。像一朵花是由花蕊、花瓣、衬叶等所构成的﹐这朵花是个「整体」(whole)﹐而花蕊、花瓣等则是这整体的「一部分」(part)。再如﹐下图的windows画面上﹐form1 对象包含着3 个控制对象(control) ﹐这些控制对象成为form1 的一部分。因之﹐form1 是个整体﹐而各控制对象则是form1 对象的一部分。


图1 、form1 对象包含3 个控制对象

我们可使用uml图形表示为﹕

图2、whole-part关系与继承关系

这图包括了whole-part关系﹐以及继承关系。
● 菱形 符号表示whole-part关系﹔就是form1 对象可包含有数个control 对象。
● 箭头 符号表示继承关系﹔就是control 对象可细分为数个种类。

本文专注于whole-part关系﹐为composite样式建立基础。whole-part关系可分为两种﹕
◎「部分」对象之个数是确定的。例如﹐1 辆汽车含有1 个方向盘﹐1 个form对象含有1 个抬头(caption) 对象﹐1 只青蛙有4 条腿等等。在这种确定情形﹐可视为下述「可变」情形的特例。
◎「部分」对象之个数是可变的。例如﹐1 个form对象内含多个控制对象﹐1 棵树含有成千上万叶子﹐1 位学生的成绩单上列有各科目的成绩等等。这种whole-part关系通常得藉集合(collection)对象来表达之﹐如vb 的arraylist对象就可派上用场了。


2. 简单的whole-part关系

最单纯的whole-part关系就是某对象「内含」(contain) 其它对象。例如﹐
s 1 张订单上面列示着多个采购的产品项目
s 1 支棒球队拥有数字教练及多位球员
s 学生的成绩单上印有多项成绩
s 1 篇文章是由许多句子所组成
s 屏幕上的选择表(menu)内含数个选择项
s 1 份考卷包含数十道考题
......

就拿订单(order form)的例子来说﹐订单上常列有多个采购项目(order line)。


图3、 订单的例子

每个采购项目通常包括有产品名称、采购数量、金额等。为了简单起见﹐假设采购项目只含产品名称及金额两个项目﹐可藉uml图表示「订单」与「采购项目」之间的whole-part关系﹐如下图所示﹕


图4、以uml表达简单的whole-part关系

在以vb落实这个uml模式时,则可藉vb的arraylist集合对象来实作(implement)「订单」与「采购项目」之间的whole-part关系﹐如下图所示﹕


图5、以uml表达简单的whole-part关系

现在的vb软件师需要很习惯于掌握这种whole-part关系﹐且常藉集合对象来表示之。请看实际的vb程序﹐其中定义order 及orderline 两个类别﹕

'ex01.bas
imports system.componentmodel
imports system.drawing
imports system.winforms
imports system.collections
'----------------------------------------------------
class orderline
private pname as string
private amt as double
public sub new(byval na as string, byval am as double)
pname = na
amt = am
end sub
public function name() as string
name = pname
end function
public function amount() as double
amount = amt
end function
end class

class order
private orderid as string
private lines as arraylist
public sub new(byval id as string)
orderid = id
lines = new arraylist()
end sub
public sub addline(byval ln as orderline)
lines.add(ln)
end sub
public function amount() as double
dim total as double = 0
dim ln as orderline
for each ln in lines
total = total + ln.amount()
next
amount = total
end function
public function getorderid() as string
getorderid = orderid
end function
end class
'------------------------------------------------------------------------
public class form1
inherits system.winforms.form

public sub new()
mybase.new()

form1 = me
'this call is required by the win form designer.
initializecomponent()
'todo: add any initialization after the initializecomponent() call
end sub
'form overrides dispose to clean up the component list.
public overrides sub dispose()
mybase.dispose()
components.dispose()
end sub
#region " windows form designer generated code "
......
#end region
protected sub form1_click(byval sender as object, byval
e as system.eventargs)
dim ord as new order("order777")
dim pline as orderline
pline = new orderline("pencil", 88.25)
ord.addline(pline)

pline = new orderline("ballpen", 110.5)
ord.addline(pline)
messagebox.show(ord.getorderid + "'s amount = " + str(ord.amount()))
end sub
end class

此程序输出:
order777's cost = 198.75

在order 类别中定义了lines变量﹐其型态arraylist﹐表示lines将可代表1 个arraylist之对象﹐其本质上就是lines变量,内含一个参考值﹐参考到arraylist之对象。在order类别的建构程序 ---- new()里,诞生了arraylist对象﹐并将参考值存入lines里。各程序之定义如下﹕
order 类别的amount()程序计算出该订单的总金额。addline() 则在订单上新增一个采购项目。在form1_click()中﹐ord 对象内含一个lines集合对象﹐它容纳2 个orderline对象。如此就表达了订单与采购项目之间的whole-part关系了。
上述程序相当于 -----

'ex02.bas
imports system.componentmodel
imports system.drawing
imports system.winforms
imports system.collections
'----------------------------------------------------
class order
class orderline
public pname as string
public amt as double
end class

private orderid as string
private lines as arraylist

public sub new(byval id as string)
orderid = id
lines = new arraylist()
end sub
public sub addline(byval pna as string, byval am as double)
dim ln as orderline
ln = new orderline()
ln.pname = pna
ln.amt = am
lines.add(ln)
end sub
public function amount() as double
dim total as double = 0
dim ln as orderline
for each ln in lines
total = total + ln.amt
next
amount = total
end function
public function getorderid() as string
getorderid = orderid
end function
end class
'-----------------------------------------------------
public class form1
inherits system.winforms.form

public sub new()
mybase.new()

form1 = me
'this call is required by the win form designer.
initializecomponent()
'todo: add any initialization after the initializecomponent() call
end sub
'form overrides dispose to clean up the component list.
public overrides sub dispose()
mybase.dispose()
components.dispose()
end sub
#region " windows form designer generated code "
......
#end region
protected sub form1_click(byval sender as object, byval e as system.eventargs)
dim ord as new order("order777")
ord.addline("pencil", 88.25)
ord.addline("ballpen", 110.5)
messagebox.show(ord.getorderid + "'s amount = " + str(ord.amount()))
end sub
end class

此程序输出:
order777's cost = 198.75




3. 递归式whole-part关系

whole-part关系内含别的whole-part关系﹐且允许有多层次的whole-part关系﹐通称为递归式的whole-part关系。在自然界中常见这种关系﹐例如﹐树叶是树的一部分﹐但树叶又是个整体﹐其内含着叶脉、叶绿素等「部分」对象。


图6、自然界的多层次whole-part关系

在企业界﹐最典型的例子是「对象结构表」(bill of material简称bom)﹐如下﹕


图7、企业物料表(bom)的whole-part关系

乍看之下,这些结构似乎很复杂﹐但从这些图形中﹐可看出这些对象可依其角色而分为两类﹕

1. leaf对象。如上图里的「白色」类别之对象﹐它们不具有whole 之角色﹐只具有part之角色。这通称为「基本组件」(primitive component) 。

2. composite 对象。如上图中的「灰色」类别之对象﹐它们具有whole之角色﹐也可能具有part之角色。这通称为「复合组件」(composite component) 。

因之﹐只需定义两个类别──leaf及composite 类别即行。




4. 以vb落实composite样式

上述递归whole-part关系是很常见的﹐是软件设计师惯用的手艺。因之﹐在gamma 的"design patterns" 一书〔注1 〕中﹐也将之收录为重要的「样式」(pattern) 之1。 该书所画的样式结构图如下图:


图8、composite样式

这样式建议我们应定义add() 、remove()和getchild()三个基本的可再定义的(overridable) 程序﹐以及其它的程序。专家们把这表示法视为样式﹐就意谓着﹕这是专家们所认为最理想的表达方式。现在﹐实际以依循这个样式来表达上述bom 结构,必须定义下述类别:
u part类别 ----- 对应到component
u piecepart类别 ----- 对应到leaf
u assemblypart类别 ----- 对应到composite

再将之落实为vb程序,如下:
'ex03.bas
imports system.componentmodel
imports system.drawing
imports system.winforms
imports system.collections
'----------------------------------------------------
interface ipart
sub add(byval p as ipart)
function getchild(byval n as integer) as ipart
function cost() as double
function name() as string
end interface

class part
private pname as string
protected sub new(byval na as string)
pname = na
end sub
protected function getname() as string
getname = pname
end function
end class

class piecepart
implements ipart
inherits part
private pcost as double

public sub new(byval na as string, byval c as double)
mybase.new(na)
pcost = c
end sub
public sub add(byval p as ipart) implements ipart.add
messagebox.show("parts do not have subparts")
end sub
public function subpart(byval n as integer) as ipart implements ipart.getchild
subpart = nothing
end function
public function cost() as double implements ipart.cost
cost = pcost
end function
public function name() as string implements ipart.name
name = mybase.getname()
end function
end class

class assemblypart
implements ipart
inherits part

private children as arraylist

public sub new(byval na as string)
mybase.new(na)
children = new arraylist()
end sub
public sub add(byval p as ipart) implements ipart.add
children.add(p)
end sub
public function subpart(byval n as integer) as ipart implements ipart.getchild
dim obj as object
obj = children.item(n)
subpart = ctype(obj, ipart)
end function
public function cost() as double implements ipart.cost
dim sum as double
dim ps as ipart
sum = 0
for each ps in children
sum = sum + ps.cost()
next
cost = sum
end function
public function name() as string implements ipart.name
name = mybase.getname()
end function
end class
'-----------------------------------------------------
public class form1
inherits system.winforms.form
public sub new()
mybase.new()
form1 = me
'this call is required by the win form designer.
initializecomponent()
'todo: add any initialization after the initializecomponent() call
end sub
'form overrides dispose to clean up the component list.
public overrides sub dispose()
mybase.dispose()
components.dispose()
end sub
#region " windows form designer generated code "
......
#end region
protected sub form1_click( byval sender as object, byval
e as system.eventargs )
dim aly1, aly2, p1, p2 as ipart
dim ps as ipart

aly1 = new assemblypart("bulb")
p1 = new piecepart("body", 88.25)
aly1.add(p1)
p1 = new piecepart("head", 100.5)
aly1.add(p1)

aly2 = new assemblypart("light")
aly2.add(aly1)
p1 = new piecepart("cover", 10)
aly2.add(p1)
messagebox.show(aly2.name() + "'s cost = " + str(aly2.cost()))

p1 = aly2.getchild(0)
messagebox.show(p1.name + "'s cost = " + str(p1.cost()))

p2 = aly2.getchild(1)
messagebox.show(p2.name + "'s cost = " + str(p2.cost()))

p2 = p1.getchild(0)
messagebox.show(p2.name + "'s cost = " + str(p2.cost()))
end sub
end class

此程序输出: light's cost = 198.75
bulb's cost = 188.75
cover's cost = 10
body's cost = 88.25

piecepart 代表最下层的基本对象。而assemblypart则代表中层的半成品对象组件﹐或代表成品。part为一个抽象类别,定义piecepart与assemblypart类别的共同部份(包括属性和行为),供piecepart与assemblypart子类别来继承之。此外,提供一个接口ipart给client使用,以封装part、piecepart和assemblypart类别,创造part、piecepart和assemblypart类别的弹性调整空间,这是非常重要的。
form1_click()程序建立了有关「汽车车灯」的对象结构表﹕


图8、vb程序所诞生的对象关系图

p1 = aly2.getchild(0)取出「灯泡」小对象﹐并由p1代表这个小对象。p2 = p1.getchild(0) 取出「灯帽」小对象﹐并由p2代表之。依上述之样式﹐可表达出无限层次的递归式whole-part关系。
为了让软件能永续生存下去﹐必须特别重视软件的组织与其整体架构(architecture)。于是﹐设计软件时,必须专注于对象之间的互助合作关系。一旦建立了理想的关系﹐就可以让对象之间互相传递讯息、互相沟通了。例如﹐form1 对象传送cost讯息给aly2对象﹐aly2对象就依循arraylist对象所建立的关系来将cost讯息递给内含之各小对象。当各小对象回报其成本金额﹐aly2将之累计再传送回到form1 对象﹐然后显示在窗口画面上。此刻﹐相信您已经进一步了解whole-part关系﹐善用arraylist集合类别来表达之,并更会运用vb来落实样式,创造出更美好的软件。■

[注1] erich gamma,design patterns: elements of reusable object-oriented software, addition-wesley, 1995.

菜鸟学堂:
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表