เพื่อให้เห็นภาพการทำงานของ MVP ชัดเจนที่สุด เขียนตัวอย่างด้วย JavaScript โดยทำระบบ “เครื่องนับเลข ” แบบง่าย ๆ ครับ
หัวใจสำคัญของ MVP คือ View และ Model จะไม่รู้จักกันเลย ทุกอย่างต้องวิ่งผ่าน Presenter ทั้งหมด
Model
มีหน้าที่แค่เก็บข้อมูลและจัดการตรรกะ ไม่สนว่าหน้าจอเป็นยังไง
class CounterModel { constructor () { this.count = 0; } // ตรรกะทางธุรกิจ: การเพิ่มค่า increment () { this.count++; } // ส่งคืนข้อมูลปัจจุบัน getCount () { return this.count; }
}
View
มีหน้าที่จัดการ HTML รับการคลิกจากผู้ใช้ และมีฟังก์ชันเตรียมไว้ให้ Presenter มาสั่งงาน
class CounterView { constructor () { // สมมติว่าใน HTML มี <h1 id="counter-display">0</h1> และ <button id="add-btn">Add</button> this.displayElement = document.getElementById ('counter-display') ; this.buttonElement = document.getElementById ('add-btn') ; } // เปิดช่องทางให้ Presenter เอาฟังก์ชันมาผูกกับปุ่ม bindIncrementEvent (handler) { this.buttonElement.addEventListener ('click', () => { handler () ; // เมื่อถูกคลิก จะเรียกฟังก์ชันที่ Presenter ส่งมาให้ }) ; } // เปิดช่องทางให้ Presenter สั่งอัปเดตหน้าจอ render (count) { this.displayElement.innerText = `จำนวนปัจจุบัน: ${count}`; }
}
Presenter
เป็นตัวกลางที่จับ Model และ View มาคุยกัน ควบคุม Flow การทำงานทั้งหมด แทน Controller
class CounterPresenter { constructor (model, view) { this.model = model; this.view = view; // 1. นำฟังก์ชัน handleIncrement ไปผูกกับปุ่มใน View this.view.bindIncrementEvent ( () => this.handleIncrement ()) ; // 2. สั่งแสดงผลครั้งแรกตอนเริ่มต้น this.updateView () ; } // ทำงานเมื่อ View แจ้งว่ามีการคลิก handleIncrement () { this.model.increment () ; // สั่ง Model ให้อัปเดตข้อมูล this.updateView () ; // สั่งหน้าจอให้อัปเดต } // ดึงข้อมูลจาก Model แล้วเอาไปสั่ง View ให้เรนเดอร์ updateView () { const currentCount = this.model.getCount () ; this.view.render (currentCount) ; }
}
การเรียกใช้งาน
ในหน้าหลักของเรา เราจะสร้างทั้ง 3 ส่วนขึ้นมาประกอบร่างกัน
// จำลองการเริ่มทำงานของระบบ
document.addEventListener ('DOMContentLoaded', () => { const model = new CounterModel () ; const view = new CounterView () ; // Presenter จะเป็นตัวจับ Model และ View เข้าด้วยกัน const presenter = new CounterPresenter (model, view) ;
}) ;
จุดสังเกตที่ทำให้ต่างจาก MVC ปกติ
จะเห็นว่าในฝั่ง CounterView ไม่มีการเรียกใช้ this.model.getCount () เลย หน้าจอทำงานโง่ ๆ แค่รอให้ผู้ใช้กดปุ่มแล้วตะโกนบอก Presenter จากนั้นก็รอ Presenter โยนข้อมูลที่เสร็จแล้วกลับมาให้ผ่านฟังก์ชัน render () อย่างเดียวครับ ทำให้เราสามารถเอา View ตัวอื่นมาเสียบแทนได้ง่ายมาก
Presenter VS Controller
ความแตกต่างระหว่าง Controller กับ Presenter เป็นเส้นบาง ๆ ที่ทำให้นักพัฒนาหลายคนสับสน จุดที่ทำให้สองตัวนี้แตกต่างกันอย่างชัดเจนที่สุดคือ “วิธีการที่พวกมันจัดการกับ View และ Model” ครับ
- การสื่อสารกับ Model
- Controller (MVC) : ในโครงสร้าง MVC แบบดั้งเดิม View สามารถมองเห็นและดึงข้อมูลจาก Model ได้โดยตรง Controller มีหน้าที่แค่รับคำสั่งจากผู้ใช้ แล้วไปสั่ง Model ให้อัปเดตตัวเอง เมื่อ Model เปลี่ยนแปลง มันจะแจ้งเตือน View ให้มาดึงข้อมูลใหม่ไปแสดงผล
- Presenter (MVP) : ตัดการเชื่อมต่อนี้ทิ้งอย่างเด็ดขาด! View และ Model จะไม่มีวันรู้จักกัน Presenter จะเป็น “คนกลาง ” เต็มตัว มันจะดึงข้อมูลจาก Model มาปรุงแต่ง แล้วค่อย “ป้อน ” ข้อมูลนั้นใส่ลงไปใน View
- ความฉลาดของหน้าจอ
- MVC (Active View) : View มีความฉลาดอยู่บ้าง มันรู้ว่าต้องไปดึงข้อมูลอะไรจาก Model มาแสดงผล
- MVP (Passive View) : View “โง่” มาก มันไม่มีตรรกะอะไรเลย ไม่รู้ว่าข้อมูลมาจากไหน มันมีหน้าที่แค่รับข้อมูลที่ Presenter โยนมาให้ แล้วแปะลงบนหน้าจอ และคอยส่งเสียงบอก Presenter เวลาผู้ใช้คลิกหรือพิมพ์อะไรบางอย่าง
- ความสัมพันธ์
- Controller: มักจะมีความสัมพันธ์แบบ 1 ต่อ หลาย คือ 1 Controller อาจจะควบคุมการทำงานของหลาย ๆ View ได้
- Presenter: มักจะมีความสัมพันธ์แบบ 1 ต่อ 1 คือ 1 Presenter จะจับคู่ดูแล 1 View อย่างใกล้ชิด ทำให้โค้ดผูกพันกันมากกว่าในแง่ของการจับคู่
- ความง่ายในการทดสอบ
- MVC: ทดสอบ ฝั่ง Controller ยากกว่าเล็กน้อย เพราะบางครั้งมันผูกติดกับระบบการทำงานของ View หรือ Framework มากเกินไป
- MVP: ทดสอบง่ายกว่ามาก! เพราะ Presenter ไม่ได้ไปแตะต้องหน้าจอ โดยตรงเลย มันแค่คุยผ่าน Interface เราจึงสามารถจำลอง View ปลอม ๆ ขึ้นมาเพื่อทดสอบตรรกะใน Presenter ได้สบาย ๆ
สรุปเปรียบเทียบแบบรวบยอด
| คุณสมบัติ | Controller | Presenter |
| View รู้จัก Model ไหม? | ✅ รู้จัก | ❌ ไม่รู้จักเด็ดขาด |
| ตรรกะของ View | มีบ้าง | ไม่มีเลย รอรับคำสั่งอย่างเดียว |
| คนอัปเดตหน้าจอ | View อัปเดตตัวเองเมื่อ Model เปลี่ยน | Presenter เป็นคนสั่งอัปเดตหน้าจอ |
| ความสัมพันธ์ (โดยทั่วไป) | 1 Controller : หลาย Views | 1 Presenter : 1 View |
| การเขียน Unit Test | ทำได้ แต่อาจจะยากกว่า | ทำได้ง่ายมาก |
สรุปง่าย ๆ คือ Presenter คือ Controller ที่มีความเป็นเผด็จการสูงกว่า ควบคุมทุกอย่างเบ็ดเสร็จ และไม่ยอมให้ลูกน้อง แอบคุยกันเองข้ามหัวมันครับ
อ่านเพิ่มเติม