หมวดหมู่: Programs

MVP (Model-View-Presenter )MVP (Model-View-Presenter )

เพื่อให้เห็นภาพการทำงานของ 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” ครับ

  1. การสื่อสารกับ Model
    • Controller (MVC) : ในโครงสร้าง MVC แบบดั้งเดิม View สามารถมองเห็นและดึงข้อมูลจาก Model ได้โดยตรง Controller มีหน้าที่แค่รับคำสั่งจากผู้ใช้ แล้วไปสั่ง Model ให้อัปเดตตัวเอง เมื่อ Model เปลี่ยนแปลง มันจะแจ้งเตือน View ให้มาดึงข้อมูลใหม่ไปแสดงผล
    • Presenter (MVP) : ตัดการเชื่อมต่อนี้ทิ้งอย่างเด็ดขาด! View และ Model จะไม่มีวันรู้จักกัน Presenter จะเป็น “คนกลาง ” เต็มตัว มันจะดึงข้อมูลจาก Model มาปรุงแต่ง แล้วค่อย “ป้อน ” ข้อมูลนั้นใส่ลงไปใน View
  2. ความฉลาดของหน้าจอ
    • MVC (Active View) : View มีความฉลาดอยู่บ้าง มันรู้ว่าต้องไปดึงข้อมูลอะไรจาก Model มาแสดงผล
    • MVP (Passive View) : View “โง่” มาก มันไม่มีตรรกะอะไรเลย ไม่รู้ว่าข้อมูลมาจากไหน มันมีหน้าที่แค่รับข้อมูลที่ Presenter โยนมาให้ แล้วแปะลงบนหน้าจอ และคอยส่งเสียงบอก Presenter เวลาผู้ใช้คลิกหรือพิมพ์อะไรบางอย่าง
  3. ความสัมพันธ์
    • Controller: มักจะมีความสัมพันธ์แบบ 1 ต่อ หลาย คือ 1 Controller อาจจะควบคุมการทำงานของหลาย ๆ View ได้
    • Presenter: มักจะมีความสัมพันธ์แบบ 1 ต่อ 1 คือ 1 Presenter จะจับคู่ดูแล 1 View อย่างใกล้ชิด ทำให้โค้ดผูกพันกันมากกว่าในแง่ของการจับคู่
  4. ความง่ายในการทดสอบ
    • MVC: ทดสอบ ฝั่ง Controller ยากกว่าเล็กน้อย เพราะบางครั้งมันผูกติดกับระบบการทำงานของ View หรือ Framework มากเกินไป
    • MVP: ทดสอบง่ายกว่ามาก! เพราะ Presenter ไม่ได้ไปแตะต้องหน้าจอ โดยตรงเลย มันแค่คุยผ่าน Interface เราจึงสามารถจำลอง View ปลอม ๆ ขึ้นมาเพื่อทดสอบตรรกะใน Presenter ได้สบาย ๆ

สรุปเปรียบเทียบแบบรวบยอด

คุณสมบัติController Presenter
View รู้จัก Model ไหม?✅ รู้จัก ❌ ไม่รู้จักเด็ดขาด
ตรรกะของ Viewมีบ้าง ไม่มีเลย รอรับคำสั่งอย่างเดียว
คนอัปเดตหน้าจอView อัปเดตตัวเองเมื่อ Model เปลี่ยนPresenter เป็นคนสั่งอัปเดตหน้าจอ
ความสัมพันธ์ (โดยทั่วไป) 1 Controller : หลาย Views1 Presenter : 1 View
การเขียน Unit Testทำได้ แต่อาจจะยากกว่าทำได้ง่ายมาก

สรุปง่าย ๆ คือ Presenter คือ Controller ที่มีความเป็นเผด็จการสูงกว่า ควบคุมทุกอย่างเบ็ดเสร็จ และไม่ยอมให้ลูกน้อง แอบคุยกันเองข้ามหัวมันครับ


อ่านเพิ่มเติม