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.
菜鳥學堂: