每一個階段,我對設計模式都有不同的理解。
隨著對函數式編寫的熱愛,對關系型數據庫和文檔型數據庫的對比,我對設計模式又有了新的感覺。
我覺得范例總是最有效的說明,假設我們以一個餐館來作為對象(我的英文不怎么樣,就不要計較拼寫了):
Eatery (餐館類)
首先,有了一個餐館類,然后加上餐館提供的接口,餐館就可以工作了。目前,先提供:
milk() // 來份牛奶
rice() // 來份炒飯
當然,還得有餐館建立的時間、城市、老板、雇員、價格...于是還得加上下面的:
build_date // 建立日期
city // 所在城市
boss_a // 老板a
employee_a // 雇員a
employee_b // 雇員b
employee_c // 雇員c
price_milk // 牛奶價格
price_rice // 炒飯價格
menu_milk // 牛奶菜單
menu_rice // 炒飯菜單
另外,客人進來餐館,還要報價不是,所以,還得加上:
get_price() // 報價
如果要做成程序,還得報告建立的日期、城市...所以,還得加上:
get_build_date() // 報告建立日期
get_city() // 報告所在城市
get_boss() // 報告有哪幾個老板
get_employee() // 報告有哪幾個雇員
get_price() // 報告菜品價格
get_menu() // 報告菜單上的菜
先大致如此吧,于是,得到了一個可以使用的餐館了:
Eatery:
build_date // 建立日期
city // 所在城市
boss_a // 老板a
employee_a // 雇員a
employee_b // 雇員b
employee_c // 雇員c
price_milk // 牛奶價格
price_rice // 炒飯價格
menu_milk // 牛奶菜單
menu_rice // 炒飯菜單
get_build_date() // 報告建立日期
get_city() // 報告所在城市
get_boss() // 報告有哪幾個老板
get_employee() // 報告有哪幾個雇員
get_price() // 報告菜品價格
get_menu() // 報告菜單上的菜
milk() // 來份牛奶
rice() // 來份炒飯
目前,餐館應該是這么運作的:
告訴我餐館建立日期 get_build_date()
告訴我餐館所在城市 get_city()
告訴我餐館菜單上的菜 get_menu()
告訴我餐館菜單上的價格 get_price()
給我來一個牛奶 milk()
給我來一份炒飯 rice()
現在,先不管面向對象,先來想想關系型數據庫,要做個餐館的數據庫該怎么打草稿?
table_eatery: 起個名字, 從餐館的基礎來開始吧
id // 現在當然只有一個餐館
build_date // 建立日期
city // 所在城市
是不是應該是這樣的?
我們總是保持基礎的那個表格盡可能簡單高效.
這份表格告訴我們 : 餐館建立日期和所在城市,還有通過id號能獲取到這個餐館。
就這么多!
如果想知道餐館的老板怎么辦?
簡單!
建個表格,show me the info:
table_boss:
id
boss_name
eatery_id
應該是這樣的。
通過id號取到某個老板,然后還有一個關聯的餐館號碼eatery_id.
老板肯定是某個餐館的老板,
老板boss_a當然是eatery_id號碼這個餐館的老板。
更多的先不討論了。
因為,這里,
有意思的地方已經出來了。
table_eatery和table_boss全面的揭示了面向對象的本質:
擴展和關聯
我們先建立起基礎:eatery,然后呢?
當我們需要新的東西的時候,擴展eatery。
怎么擴展呢?
通過建立一個新的表格。
那新的表格table_boss怎么跟table_eatery建立聯系呢?
通過在新擴展出來的table_boss塞入eatery_id來指向table_eatery。
乍一看,是不是就是繼承呢?
先弄出個table_eatery對象,然后繼承這個對象,在上邊再弄上個boss_name。
不過,仔細觀察 ,當然知道不是繼承。 (而且,都知道,多數時候繼承不是好的面向對象)
這里是"組合"。
在新的對象里放入舊有對象的一個引用,然后,成了。
objectA ---> objectB [放入objectA的一個引用]
以此類推,我們加深下關系型數據庫表格的建立。
建立一個雇員表格:
id
employee_name
eatery_id
建立一個餐館菜單:
id
menu_item
menu_price
eatery_id
...
這其實就是面向對象的過程。
所以,對于程序來說,設計這個餐館,可以這樣面向對象:
Eatery (build_date, city)
build_date // 建立日期
city // 所在城市
get_build_date() // 報告建立日期
get_city // 報告所在城市
Boss (eatery)
name // 老板名字
set_name // 任職老板
get_name // 當前老板的名字
Employee (eatery)
names // 雇員們的名字
add_name // 增加一個雇員
remove_name // 開除一個雇員
get_name(i) // 報告第幾號雇員的名字
get_names() // 報告所有的雇員名字列表
Food (name, price)
name // 菜的名字
price // 菜的價格
get_name() // 報告菜的名字
get_price() // 報告菜的價格
Menu (eatery)
foods // 菜列表
add_food(food) // 加入菜
remove_food(food) // 刪除菜
get_food_price(i) // 報告第幾號菜的價格
get_food_prices // 報告所有菜的價格
get_food_names // 報告所有菜的名字
get_food_name(i) // 報告第幾號菜的名字
Cook (name, food_names)
name // 廚師名字
food_names // 廚師擅長做的菜名
get_name() // 報告廚師的名字
get_food_names() // 報告廚師擅長做的菜名列表
make(food_name) // 廚師制作名為food_name的菜
Cooklist (eatery)
cooks // 餐館entery雇傭的廚師名字列表
add_cook(cook) // 增加一個廚師
get_cook_names() // 報告所有的餐館廚師名字
get_cook_name(i) // 報告第幾號廚師的名字
OK. 餐館構造完畢,現在,開始運營:
var entery_pie = new Eatery('2014-5-1', 'Beijing'); // 2014年5月1日,在北京建立餐館pie
entery_pie.get_build_date(); // => 2014-5-1
entery_pie.get_city(); // => Beijing
var boss = new Boss(eatery_pie); // 為餐館pie指認老板
boss.set_name('Tom'); // 老板是Tom
var employee = new Employee(eatery_pie); // 為餐館pie雇傭員工
employee.add_name('Lili'); // 雇傭Lili
employee.add_name('Lina'); // 雇傭Lina
employee.add_name('Jerry'); // 雇傭Jerry
employee.get_names(); // => Lili,Lina,Jerry
employee.get_name(1); // => Lina
var menu = new Menu(eatery_pie); // 為餐館pie開設菜單
menu.add_food(new Food('milk', '12.00¥')); // 加入價格12.00¥的牛奶
menu.add_food(new Food('rice', '26.00¥')); // 加入價格26.00¥的炒飯
menu.get_food_names(); // => 牛奶,炒飯
menu.get_food_prices(); // => 12.00¥,26.00¥
menu.get_food_name(1); // => 炒飯
var cooklist = new Cooklist(eatery_pie); // 為餐館pie增加廚師
cooklist.add_cook(new Cook('Daxiong', ['milk', 'rice', 'tomoto'])); // 增加一個會做牛奶、炒飯、西紅柿的廚師
cooklist.add_cook(new Cook('Xiaoxiao', ['milk', 'rice'])); // 增加一個會做牛奶、炒飯的廚師
cooklist.get_cook_name(1).make('milk'); // Xiaoxiao廚師來一份牛奶
cooklist.get_cook_name(0).make('tomoto'); // Daxiong廚師來一份西紅柿
這就是我現在思想中的面向對象,更可能簡單的模型,更多的擴展和關聯。
在很長時間之前,我收集到的信息告訴我,盡可能的封裝,并且 a.get().method() 表現的不夠隱蔽。
如果有a.get().nethod(),應該盡可能換成a.method()來隱藏。
然而,現在我卻感覺到這樣教條式的做法,在許多時候是多余和低效的。
比起把許多小對象裝起來,扔到一個大對象里封裝,我現在更喜歡建立一大堆的小對象,然后分批指派責任。
從許多方面來看,簡化代碼行數的外觀模式都是反模塊化的,只用一個對象來操縱一大堆函數。
事實上,底層要通過層層傳遞,才能夠到達真正實現功能的地方。
這既是低效的,在編寫擴展的時候而且很困難。
如果你寫完一個夠大的對象,當需要增加內容的時候,就會對這個對象的關聯“鏈”一籌莫展。
而打散的小對象群,更貼近函數式的風格,只需要指定源對象的引用,就可以擴展ta,而且是采用組合而不是繼承。
而且,在某些方面,你會發現這其實就是函數式.
一個數據結構Eatery,以及一大堆圍繞數據結構Eatery的函數群:
eatery
fn1(eatery, arg1, arg2, ...)
fn2(eatery, arg1, arg2, ...)
fn3(eatery, arg1, arg2, ...)
...
好處是,非常易于擴展(增加功能,增加內容,增加關聯,...)和修改.
壞處是? e, ... ,相比較傳統的OO教學,會有一大堆“小星星”散亂在宇宙世界里,變得不那么透明,但是依然可以設定一個邊界來限定這個外層。
但是,我覺得,好處是絕對的,因為這樣更符合函數的特性:
輸入一個值,show me the result
fn1(eatery_pie, arg1, arg2) => 餐館pie的信息