เจ้าของบล็อกได้อ่านบทความ Android Code That Scales, With MVP ของทีม Rewind ซึ่งได้มีการเล่าให้ฟังเกี่ยวกับการจัดการกับโปรเจคแอนดรอยด์ขนาดใหญ่ โดยใช้ MVP ซึ่งน่าสนใจดีครับ เลยอยากจะหยิบมาเล่าสู่กันฟังซักหน่อย
ถ้าพูดถึงการเขียน Android App แล้ว คงไม่มีผู้ที่หลงเข้ามาอ่านคนไหนที่ยังไม่รู้จัก MVC หรอกเนอะ เพราะมันคือ Pattern พื้นฐานที่ใช้กันเยอะมาก ซึ่งมันจะช่วยให้การเขียนโค๊ดแบ่งส่วนออกตามหน้าที่เนอะ
ซึ่งเจ้าของบล็อกก็รู้สึกว่า MVC เป็นวิธีการเขียนโค๊ดที่เข้าใจได้ง่ายดีใน Android Development เพราะมันจะแบ่งโค๊ดออกเป็น 3 ส่วนด้วยกันคือ Model, View และ Controller ซึ่ง
• Model ก็เป็นพวก POJO
• View ก็คือ XML Layout
• Controller ก็คือ Activity หรือ Fragment
สรุปแบบคร่าวๆก็คือ Controller คอยจัดการ Model และจัดการการแสดงผลของ View เนอะ
ซึ่งถ้าเจ้าของบล็อกเขียนแอพที่มีขนาดไม่ใหญ่มากก็โอเคนะ แต่พอเข้าไปเขียนแอพที่มีขนาดใหญ่มากๆเนี่ยสิ ก็เริ่มรู้สึกว่า MVC ชักจะไม่ตอบโจทย์แล้วล่ะ
เพราะอะไรน่ะหรอ?
ให้ลองนึกดูว่าถ้าต้องเขียนแอพที่มีขนาดใหญ่มากๆ มี Logic ซับซ้อน มีฟีเจอร์ต่างๆนานา แล้วโค๊ดส่วนใหญ่มันจะตกไปอยู่ที่ส่วนใดครับ?
ใช่ครับ Controller รับภาระไปเต็มๆ ซึ่งก็คือ Activity และ Fragment นั่นเอง ลองนึกภาพ Activity ที่ต้องมีทั้ง Business Logic, คอยกำหนด View ว่าจะให้แสดงผลยังไง, อาจจะมี Animation เพื่อให้แอพดูสวยงาม และยังมีพวก Event ต่างๆไม่ว่าจะ Request ข้อมูลจากเซิฟเวอร์หรือ User Interaction กับหน้าจอแอพ
นี่ยังไม่รวมไปถึง List View กับ Recycler View อีกนะ เพราะมันทำหน้านี้เป็นทั้ง View และ Controller ในตัวมันเองได้เลย
อาห์ มันช่างสนุกสนานเหลือเกิน เพราะเจ้าของบล็อกจะได้ลุยโค๊ดเป็นพันๆบรรทัด และส่วนใหญ่ก็จะอยู่ใน Activity หรือ Fragment ด้วย
แต่นั่นก็เป็นแค่ส่วนหนึ่งของความบันเทิงจากทั้งหมด เพราะไหนจะมีเรื่องการเขียนโค๊ดให้ Maintain ได้ง่าย แต่ละส่วน Loose coupling กัน และที่สำคัญคือโค๊ดต้อง Testing ได้อีก
ทำไมมันดูวุ่นวายชิบหายจังเลยนะ... จนกระทั่งถึงจุดๆหนึ่งที่แอพมีขนาดใหญ่มากพอ ก็จะพบว่า MVC มันชักจะเริ่มไม่ตอบโจทย์แล้วล่ะ
รู้จักกับ MVP กันหรือยัง
บางคนก็รู้จักแล้ว บางคนก็อาจจะเคยได้ยิน หรือบางคนก็ไม่เคยได้ยินคำนี้มาก่อนเลย ดังนั้นขอธิบายเกี่ยวกับคำว่า MVP ก็ให้รู้จักกันคร่าวๆก่อนเนอะMVP นั้นย่อมาจากคำว่า Most Valuable Player ครับ เป็นรางวัลสำหรับผู้เล่นที่มีความโดดเด่นครับ อย่างเช่นคุณไปล่า Ancient Mummy แล้วคุณสามารถทำ Damage รวมได้มากที่สุด คุณก็จะได้ MVP ครับ และบอสก็อาจจะดรอปการ์ดหรือว่า Old Blue Box ให้คุณไปตั้งร้านขายที่ Morroc
ถุยยยยย!! นั่นมันคนละ MVP!!
กลับเข้าสาระดีกว่าเนอะ
MVP เป็นอีกหนึ่ง Pattern ที่ฟังดูคล้ายๆกับ MVC ครับ แต่สำหรับ MVP จะประกอบไปด้วย Model, View และ Presenter แทน
มันก็แค่เปลี่ยนคำว่า Presenter เป็นคำว่า Controller นี่ แล้วมันจะแตกต่างกันยังไง?
เนอะ ไม่ค่อยรู้สึกต่างเลยอ่ะ ก็แค่เปลี่ยนชื่อแค่นั้นเอง มันจะกลายเป็น Pattern ใหม่ได้ยังไงล่ะเนอะ แต่จริงๆแล้วความต่างไม่ได้อยู่แค่ที่ชื่อเรียกแน่นอนครับ ลองมาดูนิยามของแต่ละส่วนกันดีกว่า
สำหรับ Model มันก็ยังคงเป็น POJO เหมือนเดิมแหละ ไม่ได้เปลี่ยนเป็นอย่างอื่นใด
โค๊ด Low level ที่หน้าที่จัดการกับ UI และ User Interaction จะเป็น View
โค๊ด High level คอยจัดการการทำงานของแอพว่าจะให้ทำอะไรบ้าง รวมไปถึง Business Logic ด้วย จะเป็น Presenter
และที่สำคัญ ใน MVC จะมองว่า Activity คือ Controller ใช่มั้ยล่ะครับ แต่สำหรับ MVP จะมองว่า Activity คือ View แทนครับ Fragment ก็มองว่ามันคือ View เช่นกัน
เริ่มรู้สึกว่ามันแตกต่างจาก MVC แล้วเนอะ?
ภาพแบบนี้น่าจะช่วยให้เข้าใจง่ายขึ้นเนอะ แต่อาจจะยังงงๆกันอยู่บ้างว่าทำไมต้องแยกเป็นแบบนี้ ดังนั้นมาดูหน้าที่ในแต่ละส่วนที่ทีม Remind ได้ให้นิยามไว้ดีกว่าครับ
View
• Instantiating the Presenter, and its binding/unbinding mechanism• Informing the Presenter of relevant lifecycle events
• Informing the Presenter of input events
• Laying out the views and binding data to them
• Animations
• Event tracking
• Navigating to other screens
Presenter
• Loading models• Keeping a reference to the model, and the state of the view
• Formatting what should be displayed, and instructing the View to display it
• Interacting with the repositories (database, network, etc.)
• Deciding the actions to take when input events are received
จากนิยามดังกล่าวจะเห็นว่า View มีหน้าที่คอยควบคุมสั่งงานและรับค่าจาก UI หรือ View ทั้งหมด ไม่ว่าจะเป็นการทำ Animation หรือ Event ต่างๆจาก View เช่นผู้ใช้กดปุ่ม รวมไปถึงสั่งให้แอพเปลี่ยนหน้าด้วย และที่ขาดไม่ได้คือคอยสร้าง Presenter และคอย Bind/Unbind ให้ Presenter ด้วย รวมไปถึงส่ง Lifecycle Event ที่เกี่ยวข้องให้ Presenter
ตัวอย่าง Interface สำหรับ View
interface AwesomeView {
// Methods for updating the view
void setMessageBody(String body);
void setAuthorName(String name);
void showTranslationButton(boolean shouldShow);
// Navigation methods
void goToUserProfile(User user);
}
และ Presenter ก็จะทำงานในส่วนที่ต้องมีการตัดสินใจทั้งหมด เช่น View ส่ง Event มาบอกว่าผู้ใช้กดปุ่ม OK มาให้ Presenter ก็ต้องกำหนดว่าเมื่อผู้ใช้กดปุ่มนั้นแล้วจะให้ทำงานยังไง คอยสร้างและกำหนด Model ที่จะต้องใช้งาน และรวมไปถึงข้อมูลที่ใช้ภายในแอพด้วย ไม่ว่าจะเป็นข้อมูลจากฐานข้อมูลหรือจากเซิฟเวอร์ก็ตาม
ตัวอย่าง Interface สำหรับ Presenter
interface MessagePresenter {
// Lifecycle events methods
void onStart();
// Input events methods
void onAuthorClicked();
void onThreeFingersSwipe();
}
เจ้าของบล็อกยกตัวอย่าง Interface ข้ึนมา นั่นก็หมายความว่าจะมี Interface ที่เชื่อมการทำงานระหว่าง Presenter และ View นั่นเอง
คำสั่ง Update View ควรเป็นคำสั่งที่ฟังดูเข้าใจง่ายและเป็นการสั่งงานอย่างใดอย่างหนึ่งเท่านั้น (Simple and single targeted element) เช่น hideConfirmButton หรือ showConfirmButton แยกกันไปเลย
Input Event ที่ส่งไปให้ Presenter จะเป็น High level ดังนั้นควรส่ง Event ที่เป็นผลลัพธ์เลย เช่น ปุ่มอะไรถูกกดก็ควรส่งมาเป็น Event ที่บอกเลยว่าปุ่มนั้นๆถูกกดเช่น onDetailButtonClicked หรือถ้าเป็นพวก Event อย่างเช่น Gesture โค๊ดที่ตรวจสอบการทำ Gesture ให้อยู่ใน View แล้วส่งผลลัพธ์มาบอก Presenter เมื่อ Gesture ตรงตามเงื่อนไขแทน
และถ้าสมมติว่ากดปุ่ม OK แล้วข้อความที่กรอกในช่องกรอกชื่อจะถูกนำมาแสดงในชื่อผู้ใช้งาน ห้ามใส่คำสั่งทั้งหมดไว้ใน View ถึงแม้ว่ามันจะดูง่ายกว่าก็ตาม แต่ให้ส่ง Event มาบอก Presenter ว่าปุ่มถูกกด แล้ว Presenter ก็สั่ง View กลับไปว่าให้อัพเดทชื่อผู้ใช้งานจากช่องกรอกชื่อ เพื่อให้โค๊ดที่กำหนดการทำงานอยู่ที่ฝั่ง Presenter ซึ่งเป็นไปตาม Pattern ของ MVP
ดังนั้นถ้าโค๊ดใน Presenter มีคำสั่งของ Android Framework หรือ View มีคำสั่งของ Model อยู่ ก็แปลว่าผู้ที่หลงเข้ามาอ่านกำลังเขียนผิด เพราะทั้งสองอย่างนี้ต้องแยกจากกันอย่างสิ้นเชิง (กรณีของ View สามารถโยน Model เข้ามาเพื่อส่งต่อได้นะ แต่ไม่ควรทำอะไรกับ Model ตัวนั้นๆเด็ดขาด)
MVP ช่วยอะไรได้?
จากรูปแบบการทำงานที่ทางทีม Remind ได้บอกไว้จึงทำให้โค๊ดที่ต้องมีการเขียนเทสอยู่ใน Presenter เกือบทั้งหมด และที่เจ๋งยิ่งกว่าคือโค๊ดที่อยู่ใน Presenter นั้นเป็น Pure Java ทั้งหมด จึงใช้ JUnit ได้เลย เพราะไม่มีโค๊ดของ Android Framework อยู่ใน Presenterจึงทำให้การเทสในส่วนของ Presenter ทำได้ไวขึ้น ไม่ต้องใช้อุปกรณ์แอนดรอยด์ในการเทส ซึ่งเป็นสาเหตุหนึ่งที่ทำให้การเทสนั้นกินเวลาเกินจำเป็น เพราะปกติการใช้ MVC จะแยกโค๊ดส่วนที่เป็น Android Framework กับ Pure Java ออกจากกัน ทำได้ค่อนข้างยากกว่า แถม Presenter ก็สามารถ Mock Data และ Mock View Interface เพื่อเทสได้ง่ายด้วย
ส่วน View ก็เทสแต่ Instrumentation Test กับ UI Test ซึ่งทั้งคู่ต้องใช้อุปกรณ์แอนดรอยด์ในการเทส
เรียกได้ว่าการเขียนแบบ MVP จะช่วยแยกส่วนของโค๊ดออกจากกันไม่พอ ยังจะแยกการเทสออกจากกันให้โดยอัตโนมัติอีกด้วย แถมยังแยก Business Logic ออกมาอยู่ใน Presenter ให้เลย คำสั่งส่วนใหญ่ก็อยู่ในรูป Function เกือบทั้งหมดจึงทำให้กลับมาดูใหม่ในทีหลังก็สามารถไล่โค๊ดได้ง่าย
MVC กับ MVP แบบไหนดีกว่ากันล่ะ?
คงตอบไม่ได้หรอกครับว่าแบบไหนดีกว่ากัน เพราะทั้งคู่ก็มีข้อดีและข้อเสียที่ต่างกันถ้าใช้ MVC ก็เหมาะกับโปรเจคที่มีขนาดไม่ใหญ่ สามารถพัฒนาได้ไวกว่า เป็น Pattern ที่เข้าใจได้ง่าย จึงหาคนมารับช่วงต่อโค๊ดได้ง่าย
ถ้าใช้ MVP ก็จะเหมาะกับโปรเจคที่มีขนาดใหญ่ต้องพัฒนาเป็นระยะเวลานานและดูแลอย่างต่อเนื่อง สามารถ Maintain ได้ง่าย เขียนเทสก็ง่าย แต่อาจจะมีโครงสร้างโค๊ดที่ดูเข้าใจยากหน่อยสำหรับหลายๆคน เพราะ MVP เป็น Pattern ที่มีความซับซ้อนกว่า
ดังนั้นการจะเอาแบบไหนไปใช้งานก็ควรดูความเหมาะสมด้วย ไม่ใช่แค่ว่าขนาดของโปรเจคเท่านั้น แต่รวมไปถึงคนในทีมด้วยกันว่ามีความพร้อมมั้ย อยากจะใช้แบบไหน เพราะมันไม่ใช่เรื่องที่จะมีแค่คนๆเดียวที่จะมานั่งเลือกครับ และควรศึกษาให้เข้าใจถึงข้อดีข้อเสียก่อนที่จะนำมาใช้งานครับ
แต่ถึงจะเลือกแบบไหน สุดท้ายมันก็ต้องเจอบั๊กเหมือนๆกันแหละน่า ฮาๆ
ส่วนตัวอย่างการเขียนแบบ MVP บนโปรเจคแอนดรอยด์ เดี๋ยวเจ้าของบล็อกจะขอแยกเป็นอีกบทความไปเลย เพราะว่ามันค่อนข้องวุ่นวาย อาจจะทำให้มีเนื้อหายาว