- Notifications
You must be signed in to change notification settings - Fork 0
Observer Pattern
Observer Pattern เป็นหนึ่งใน pattern ที่ใช้กันมากบน Framework ต่างๆ (ไม่ว่าจะภาษาใดก็ตาม) เนื่องจากเป็น Pattern ที่ตอบโจยท์เรื่อง การแก้ไข/เพิ่มเติมความสามารถของ Core Framework โดยไม่ต้องเข้าไปแก้ไขใน Core จริงๆ หรือที่เรียกว่า Hacking
แม้ว่า Framework หลายๆ ตัว โดยเฉพาะ PHP Framework จะเป็น Open source ก็ตาม การที่เราจะแก้ไขหรือเข้าไปเพิ่มเติมความสามารถบางอย่างใน code โปรแกรมโดยตรงนั้น ถือว่าไม่ใช่วิธีที่ถูกต้อง เพราะแม้จะเป็น Open source เราก็ยังต้องมอง Framework ต่างๆ ในลักษณะของ software แบบ Black-box (กล่องดำ) ที่ไม่สามารถ (ไม่ควร) เข้าไปแก้ไขอะไรเลยอยู่ดี เนื่องจากว่าหากฝั่งผู้พัฒนา Framework นั้นๆ ออกซอฟต์แวร์เวอร์ชั่นใหม่มา ย่อมส่งผลกระทบต่อส่วนที่เราเข้าไปแก้ไขไว้แน่นอน
ทางออกคือ ใช้อย่างเดียว อย่าไปแก้ไขอะไร ยกเว้นจะจำเป็นจริงๆ ซึ่งคำว่าจำเป็นจริงๆ ก็คือ มันจำเป็นจริงๆ :)
ถ้าซอฟต์แวร์ตัวไหนที่เป็นแค่ Framework เพียวๆ เราก็คงไม่มีความจำเป็นอะไรต้องไปแก้ไข core โปรแกรม (ถ้าไม่จำเป็นจริงๆ หรือมันเป็น Bug) เนื่องจากเราจะใช้ Framework ตัวนั้นมาสร้างเป็น โปรแกรมสำเร็จรูปของเรา อีกทีนึง แต่ถ้ามันเป็น Software สำเร็จรูปมาแล้ว เช่น WordPress, Joomla หรือแม้กระทั่ง Windows Live Message รวมถึง Framework ในระดับบนหรือ Hi-Level Framework หลายๆ ตัวเช่น Sencha (JavaScript) ก็คงจะมีหลายส่วนที่เราอยากจะเขียนเพิ่มเติมเข้าไป หากต้องใช้มัน เราอาจจะ:
- อยากปรับเปลี่ยนบางส่วนของมัน
- เพิ่มความสมารถบางอย่างเข้าไป
- สร้างโปรแกรมให้สามารถเปิด/ปิด ความสามารถบางอย่างได้ (Plugins)
- ไม่สะดวกที่จะใช้วิธี extends/inherit เหล่านี้คือเหตุผลในหลายๆ อย่าง ที่เราจำเป็นต้องอยากแก้ไข/เพิ่มเติม Core Framework และวิธีการของ Observer Pattern สามารถช่วยเราแก้ปัญหานี้ได้ และการใช้ Observer Pattern นี้ยังเป็นเครื่องมือสำคัญในการโปรแกรมมิ่งเชิง Event-Driven อีกด้วย
ประกอบไปด้วย Class 2 ตัวหลักคือ
+--------------------+ +---------------+
| Subject | <>------------- | Observer |
+--------------------+ +---------------+
| + ObserverAdd | | + Update |
| + ObserverRemove | +---------------+
| + ObserverNotify |
+--------------------+
- Subject บางทีเรียกว่า Observable, Plugable เป็นต้น ความหมายคือ Object คอยเก็บหรือรับสมัคร objects อื่นๆ ไว้
+ ObserverAdd บางทีเรียกว่า add, addListener, addObject, subscribe, register...
+ ObserverRemove บางทีเรียกว่า remove, removeListener, removeObject, unsubscribe, unregister ...
+ ObserverNotify บางทีเรียกว่า fire, fireEvent, trigger, triggerEvent, notify ...
- Observer คือ object ที่สร้างขึ้นเพื่อเข้าไปจัดการ (แก้ไข/เพิ่มเติม) บางส่วนใน Observable Object หรืออาจจะเรียกว่า Handler Object ก็ได้
+ Update คำว่า update ในที่นี้เป็นตัวแทนความหมาย method ที่จะถูกเรียกใช้งาน ตามการค้นหาของ ObserverNotify โดยมากจึงเป็นชื่อของ Event ต่างๆ
แปลว่าในความเป็นจริง method นี้ไม่ได้ชื่อว่า update แต่อาจจะชื่อว่า onBeforLoad, onAfterLoad, onLoad ...
* ความหมายของ <>---------- หมายถึง ความสัมพันธ์ที่ขึ้นตรงต่อกัน ขาดกันไม่ได้ (Aggregate) คือต้องประกอบกันให้ครบถึงจะทำงานได้จริงตามคอนเส็บ ดูเพิ่มเติม http://alan-amin.net/uml.pdf
/* ลักษณะ class โดยรวม */interfaceObserver{functionUpdate();}interfaceObservable{functionObserverAdd(Observer $Observer);functionObserverRemove($ObserverKey);functionObserverNotify($NotifyName, $Args);}/* class ที่ใช้งานจริง สมมติว่าเราจะเขียนโปรแกรมให้มี Plugin ได้ *//** * @name Plugable class ที่สืบทอดจาก Observable จะเป็น class ที่อยู่ในส่วนของ Application ภายใน * โดยส่วนมาก class นี้จะสร้างไว้เพื่อให้ class อื่นมา inherit อีกที */classPlugableimplementsObservable{/** * สร้างตัวแปรสำหรับเก็บ object server ต่างๆ เสมือนเป็น Server */private $_servers =array();/** * เก็บ Object Server หรือ Handler Object ไว้ใน Server * * @param [Observer object] Object ที่สืบทอดจาก Observer class */functionObserverAdd(Observer $Observer){/** * ในขั้นตอนนี้อาจจะต้องตรวจสอบก่อนว่ามีการเก็บ observer ตัวนี้หรือยัง เพื่อความชัวร์ * การเพิ่ม server ส่วนนี้ข้อมูลจะเป็น array ควรจะจัดการด้วย class พวกที่ใช้จัดการ collection จะง่ายในการแก้ไข * ในที่นี้เราจะใส่ key ให้ array ด้วยเพื่อง่ายต่อการค้นหาภายหลัง โดยเอาชื่อ class observer เป็น key */
$k =get_class_name($Observer);
$this->_servers[$key]= $Observer;}/** * ลบ Object Server หรือ Handler Object ออกจาก Server * * @param [string] ชื่อของ Object Server (class name) */functionObserverRemove($ObserverName){/** * นี่คือวิธีการลบแบบง่ายๆ */if(isset($this->_servers[$ObserverName])unset($this->_servers[$ObserverName]);}/** * method นี้ใช้ในการค้นหา observer เพื่อทำการ notify, trigg observer นั้นๆ * โดยมากในการใช้งานจริงจะเป็นชื่อ event ซึ่งจะพ้องกับ ชื่อ method อันใดอันหนึ่งของ observer * จะเห็นว่าคอนเส็บคือ ทำการค้นหา method name ของ observer เพื่อทำการเรียกให้ทำงาน (call) * โดยที่ฝั่ง observable จะเป็นผู้ส่งพารามิเตอร์ไปให้ observer * ลักษระนี้เรียกว่าการ call-back ไปยัง observer เพื่อเรียกหาตัว handle * ผู้พัฒนาโปรแกรม (ฝั่ง observable) จึงต้องบอก (API) กับผู้ใช้ (ผู้เอาไปใช้) ว่าจะส่งพารามิเตอร์ใดไปให้บ้าง * เพื่อให้ไปจัดการแก้ไขต่อไป โดยสิ่งที่จะต้องส่งไปเสมอคือ obsevable ที่เป็นผู้ notify หรือผู้เรียก หรือเรียกว่า Sender */functionObserverNotify($EventName, $Args =null){/** * ค้นหา $EventName ของ observer ต่างๆ * * ในความเป็นจริงแล้ว observer (plugins) จะถูกเพิ่มเข้าไปใน server หลายตัว * และส่วนมากใน application หนึ่งตัวจะไม่ค่อยแยก event ออกเป็นส่วนๆ เท่าไร (ยกเว้นบางตัวเช่น Sencha) * การค้นหาจากชื่อ event อย่างเดียวอาจทำให้ผิดพลาดได้ วิธีการจริงๆ จึงต้องใส่ scope เพื่อระบุ observer ด้วย * จะทำการ scope ด้วย object หรือด้วย pre-fix, sup-fix ก็แล้วแต่เราออกแบบ * การ scope ด้วย object (sender) จะดีกว่าการ scope ด้วย pre-fix *//** สร้างตัวแปร results เพื่อเก็บผลลัพธ์จาก handler ซึ่งอาจต้องใช้ในบางกรณี **/
$results =array();/** ค้นจาก observer ทั้งหมด **/foreach($this->_serversas $obj){/** * ตรวจหา method ของ observer object * Observer->Update() **/if(in_array($EventName,get_class_methods($obj)){/*** * ทำการ Notify, Trig, Update ไปยัง Observer * ภาษา OOP เรียกว่า ส่ง message ก็คือ call method นั่นล่ะ (พูดตรงๆ) * ขั้นตอนนี้จะมีการส่ง Sender ไปแบบ pass by reference (&$this) เพื่อแก้ไข Observable Sender ***/
$results[]= $obj->$EventName(&$this, $Args);}}return $results;}}classPluginimplementsObserver{/** ไม่มีอะไรต้องทำ เพียงแต่เปลี่ยนชื่อให้สื่อความหมายกับความเป็นจริง **/}/** * class ฝั่งผู้พัฒนา class ไหนอยากให้มี event ก็ inherit จาก plugable ซึ่งเป็น Observable * CMS ที่เป็นที่นิยมโดยมากจะมีการเพิ่ม event หรือ สิ่งที่จะ notify ไปยัง subscriber (observer) ไว้อย่างครบครับ ทำให้ผู้พัฒนาต่อ * ในส่วนนี้เราอาจจะเพิ่ม (จริงๆ แล้วต้องเพิ่มเลยล่ะ เมื่อทำงานจริง) ส่วนที่เรียกว่า eventManager ขึ้นมา เพื่อกำหนด เหตุการต่างๆ ที่ * เรามี และให้ผู้อื่นมาสมัครรับข่าวสารจากเหตุการนั้นๆ * มีความยืดหยุ่นในการปรับแต่ง แต่ยังไงก็ตามผู้พัฒนาก็ต้องออกแบบให้ดีว่าจะมี listener อะไรไว้บ้าง * ซึ่งการออกแบบให้ดีที่ว่าก็ต้อง "นึกถึงหัวอกคนเอาไปใช้ หรือนึกซะว่าถ้าคนใช้เป็นเราจะอยากได้อะไร" ก็คงเท่านั้น :) */classFooPlugableextendsPlugable{public $SampleMember ='bla bla';functionstartFoo(){
$SampleParam =0;
$this->ObserverNotify('onBeforStartFoo',array($SampleParam));/** * โดยส่วนมาก event ที่เป็น before คือเริ่มต้นทำอะไรซักอย่าง มักจะสนใจ result หรือ return ของ observer * เพื่อกำหนดว่าจะทำงานต่อหรือไม่ เราอาจจะเขียนเช็ค result ก่อนตรงนี้ และบอก ฝั่งผู้ใช้ว่าเราสนใจ result อะไร * เช่น ถ้าเราสนใจว่าหากมีการ return false มาจากฝั่ง observer เราจะไม่ทำงานต่อก็เขียนได้ ดังนี้ * * $results = $this->ObserverNotify('onBeforStartFoo', array($SampleParam)); * if(in_array(false,$results)) return; // โดยปกติค่าreturnนิยมกำหนดให้เป็นtrue/false(ถ้าจะให้มี)
*/ // code
$this->ObserverNotify('onStartFoo',array($SampleParam,'33'));//code
$this->ObserverNotify('onAffterStartFoo',array($SampleParam,'55'));echo $this->SampleMember;}}/** * ฝั่งผู้ใช้ หรือผู้เขียน plugin เราจะเขียน methods Notify ในนี้ * ซึ่งจะเขียน method อื่นๆ ที่ไม่เกี่ยวกับ Notify ไว้ใช้งานภายในด้วยก็ได้ */classFooPluginextendsPlugin{/** * ถ้าต้องการ handle onBeforStartFoo * * @param [Observable object] ผู้ทำการ Notify หรือ Sender * @param [array] พารามิเตอร์ที่ส่งเข้ามา โดยมากนิยมเป็น array ซึ่งจะส่งอะไรมาบ้างผู้พัฒนาจะบอกไว้ */functiononBeforStartFoo($sender, $args){if($args[0]){
$sender->SampleMember='my goo';}else{
$sender->SampleMember='my girl';}}/** * ถ้าต้องการ handle จังหวะที่เกิด onStartFoo ก็ลักษณะเดียวกัน */functiononStartFoo($sender, $args){//dosomething ....}}/** * ตัวอย่างการทำแบบตรงไปตรงมา * แต่ในความเป็นจริงจะมีการสร้างระบบขึ้นมาบริหารจัดการต่างหาก อาจจะเรียกว่า PluginsManagement * เพื่อทำการโหลด observer และทำการเพิ่มเข้าไปใน Server */
$PlugAble =newFooPlugable();
$PlugAble->ObserverAdd(newFooPlugin());/*** now $PlugAble->SampleMember: 'bla bla'**/
$PlugAble->startFoo();/*** now $PlugAble->SampleMember: 'my girl'* startFoo output: my girl/**interfaceObserver{functionNotify();}interfaceObservable{functionObserverAdd(Observer $Observer);functionObserverRemove($ObserverKey);functionObserverNotify($NotifyName, $Args);}classPlugableimplementsObservable{private $_servers =array();functionObserverAdd(Observer $Observer){
$k =get_class_name($Observer);
$this->_servers[$key]= $Observer;}functionObserverRemove($ObserverName){if(isset($this->_servers[$ObserverName])unset($this->_servers[$ObserverName]);}functionObserverNotify($EventName, $Args =null){
$results =array();foreach($this->_serversas $obj){if(in_array($EventName,get_class_methods($obj)){
$results[]= $obj->$EventName(&$this, $Args);}}return $results;}}classPluginimplementsObserver{}classFooPlugableextendsPlugable{public $SampleMember ='bla bla';functionstartFoo(){
$SampleParam =0;
$this->ObserverNotify('onBeforStartFoo',array($SampleParam));/** * $results = $this->ObserverNotify('onBeforStartFoo', array($SampleParam)); * if(in_array(false,$results)) return; *///code
$this->ObserverNotify('onStartFoo',array($SampleParam,'33'));//code
$this->ObserverNotify('onAffterStartFoo',array($SampleParam,'55'));echo $this->SampleMember;}}classFooPluginextendsPlugin{functiononBeforStartFoo($sender, $args){if($args[0]){
$sender->SampleMember='my goo';}else{
$sender->SampleMember='my girl';}}functiononStartFoo($sender, $args){//dosomething ....}}
$PlugAble =newFooPlugable();
$PlugAble->ObserverAdd(newFooPlugin());/*** now $PlugAble->SampleMember: 'bla bla'**/
$PlugAble->startFoo();/*** now $PlugAble->SampleMember: 'my girl'* startFoo output: my girl/**http://en.wikipedia.org/wiki/Observer_pattern
http://www.ibm.com/developerworks/library/os-php-designptrns/
http://www.oodesign.com/observer-pattern.html
http://sourcemaking.com/design_patterns/observer
คราวหน้ามาลองทำ Observer + MVC
*** จำไว้ว่า: อย่ายึดติดกระบวนท่า ให้เข้าใจคอนเส็บ แล้วจะข้ามจากจอมยุทธ์ไปเป็นเซียน (อ. สุรเดช ม.นเรศวนฯ) ***
:) เอวัง