LEGO Architecture คืออะไรและทำไมต้องใช้ใน Flutter
ลองนึกภาพตอนเราต่อเลโก้ (ตัวต่อพลาสติก) ดูนะ เราหยิบชิ้นส่วนสองชิ้นมาประกบกันจนได้ยินเสียงคลิก มันก็ยึดติดกันแน่นโดยที่เราไม่ต้องสนเลยว่าโรงงานผลิตพลาสติกแบบไหน หรือข้างในชิ้นส่วนนั้นมีกลไกอะไร เราสนใจแค่ว่า จุดต่อ (Studs) ของมันตรงกันไหมก็พอ
ในโลกของ การเขียนโปรแกรม (Coding) สิ่งนี้เรียกว่า LEGO Architecture ซึ่งเป็นการออกแบบโค้ดให้แต่ละส่วนทำงานแยกกันเป็นอิสระ โดยมีจุดเชื่อมต่อที่ชัดเจน การทำแบบนี้จะช่วยให้แอปพลิเคชันของเราไม่พังง่ายเมื่อต้องแก้ไขโค้ด เพราะเราแค่ถอดเปลี่ยนชิ้นส่วนที่เสียออกแล้วใส่ชิ้นใหม่เข้าไปแทนเหมือนเลโก้เป๊ะ
มือใหม่หลายคนมักจะเขียนโค้ดทุกอย่างรวมไว้ในไฟล์เดียว ยิ่งแอปโตขึ้น ไฟล์นั้นจะกลายเป็น "ไฟล์ปีศาจ" ที่ไม่มีใครกล้าเปิดดู เพราะแค่เปลี่ยนจุดเดียวก็อาจทำให้ทั้งแอปพังได้ การเรียนรู้ LEGO Architecture จะช่วยให้เราจัดระเบียบโค้ดให้สะอาดและดูแลรักษาง่ายในระยะยาว
การคิดแบบเลโก้ในระดับ Widget
จริงๆ แล้วเราใช้หลักการเลโก้กันอยู่แล้วโดยไม่รู้ตัว อย่างเช่นการใช้ Padding (ตัวเว้นระยะขอบ) ใน Flutter สังเกตไหมว่า Padding ไม่ได้สนใจเลยว่าข้างในจะเป็นรูปภาพ ข้อความ หรือปุ่ม มันแค่มีหน้าที่เว้นระยะให้สิ่งที่ใส่เข้ามาเท่านั้น นี่คือตัวอย่างที่ดีของการแยกหน้าที่ออกจากกัน
ปัญหาจะเกิดเมื่อเราเขียนโค้ดแบบ "ผูกมัด (Tight Coupling)" คือการเขียนหน้าที่หลายอย่างปนกันในคลาสเดียว เช่น การสร้างกล่องใส่ราคาที่ผสมทั้งการจัดรูปทรงและการดึงข้อมูลราคาไว้ด้วยกัน ถ้าวันหนึ่งเราอยากใช้กล่องทรงเดิมแต่เปลี่ยนข้อมูลข้างใน เราจะทำไม่ได้เลยเพราะโค้ดมันยึดติดกันแน่นเกินไป
วิธีแก้คือเราต้องแยกส่วนที่แสดงผล (หน้าตา) ออกจากส่วนที่ทำงาน (ตรรกะ) ให้ชัดเจน เหมือนเราแยกชิ้นส่วนเลโก้ฐานกับเลโก้ตกแต่งออกจากกัน เพื่อให้เราหยิบไปใช้ซ้ำได้ในส่วนอื่นของแอปโดยไม่ต้องเขียนโค้ดใหม่ทั้งหมด
// แบบที่ผิด: รวมทุกอย่างไว้ในที่เดียว
class PriceTag extends StatelessWidget {
final double price;
const PriceTag({super.key, required this.price});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.white), // ตกแต่ง
child: Text('\$${price}'), // ดึงข้อมูล
);
}
}
// แบบที่ถูก: แยกส่วนประกอบออกมา
class SurfaceCard extends StatelessWidget {
final Widget child; // รับอะไรก็ได้เข้ามา
const SurfaceCard({super.key, required this.child});
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(color: Colors.white),
child: child,
);
}
}
ในโค้ดตัวอย่างแรก เราเอาการตกแต่งและข้อมูลมาปนกัน ทำให้ใช้ซ้ำไม่ได้ ส่วนตัวอย่างที่สองเราสร้าง SurfaceCard ที่รับ child (ลูกของ Widget) เข้ามา ทำให้เราเปลี่ยนสิ่งที่อยู่ในกล่องได้อิสระ
ผลลัพธ์ที่ควรเห็นคือ SurfaceCard สามารถนำไปวางล้อมรอบอะไรก็ได้ ไม่ว่าจะเป็น Text, Image หรือแม้แต่ปุ่มกด ทำให้โค้ดของเรานำกลับมาใช้ใหม่ได้ง่ายขึ้นมาก
สร้างสัญญาการเชื่อมต่อด้วย Interface
ในโลกของเลโก้ สัญญา (Contract) คือขนาดของจุดต่อที่ต้องเท่ากันเพื่อให้ประกอบกันได้ ในการเขียนโค้ดเราใช้ abstract class (คลาสแม่แบบที่ไม่มีตัวตน) เป็นตัวกำหนดสัญญาว่า "ถ้าใครจะมาเชื่อมต่อกับฉัน ต้องมีฟังก์ชันชื่อนี้และรับค่าแบบนี้เท่านั้นนะ"
การใช้สัญญาช่วยให้เราไม่ต้องสนใจว่าโค้ดข้างในทำงานอย่างไร เราแค่รู้ว่าถ้าเรียกใช้ฟังก์ชันนี้แล้ว ผลลัพธ์ที่ได้จะเป็นแบบที่ตกลงกันไว้เสมอ วิธีนี้ช่วยลดความซับซ้อน เพราะเราสามารถเปลี่ยนโค้ดข้างในได้โดยไม่กระทบกับส่วนอื่นที่มาเชื่อมต่อเลย
มือใหม่มักจะเผลอเขียนโค้ดที่เรียกใช้คลาสอื่นโดยตรง ซึ่งถ้าคลาสนั้นเปลี่ยนชื่อหรือเปลี่ยนวิธีทำงาน โค้ดเราจะพังทันที การใช้สัญญาเป็นเหมือนการสร้างตัวกลางที่ทำให้โค้ดแต่ละส่วนคุยกันได้โดยไม่ต้องรู้จักกันเป็นการส่วนตัว
// กำหนดสัญญาว่าใครจะดึงข้อมูล ต้องมีฟังก์ชันนี้
abstract class DataProvider {
Future<String> fetchData();
}
// คลาสที่ทำหน้าที่ดึงข้อมูลจริง
class ApiService implements DataProvider {
@override
Future<String> fetchData() async {
return "ข้อมูลจากเน็ต";
}
}
บรรทัดแรกเราสร้าง abstract class เพื่อกำหนดว่าต้องมีเมธอด fetchData ในบรรทัดที่สองเราสร้าง ApiService ที่ยอมรับทำตามสัญญานี้โดยใช้คำสั่ง implements
ผลลัพธ์คือเมื่อเรามี DataProvider เราสามารถสลับเปลี่ยนไปใช้ MockService (คลาสจำลองข้อมูล) เพื่อทดสอบโค้ดโดยไม่ต้องต่ออินเทอร์เน็ตจริงได้เลย
การจัดโครงสร้างโฟลเดอร์แบบเลโก้
เมื่อโปรเจกต์ใหญ่ขึ้น การวางไฟล์รวมกันในโฟลเดอร์เดียวจะทำให้หาอะไรไม่เจอ เราควรแยกไฟล์ตามหน้าที่การใช้งาน เหมือนการแยกเลโก้ใส่กล่องตามประเภทชิ้นส่วน เช่น โฟลเดอร์สำหรับหน้าจอ โฟลเดอร์สำหรับดึงข้อมูล และโฟลเดอร์สำหรับเก็บสถานะของแอป
กฎเหล็กคือแต่ละโฟลเดอร์ต้องเป็นอิสระต่อกัน ถ้าโฟลเดอร์หน้าจอต้องการข้อมูล มันไม่ควรไปดึงไฟล์จากโฟลเดอร์ฐานข้อมูลโดยตรง แต่มันควรคุยผ่าน Repository (ตัวกลางจัดการข้อมูล) ที่เราสร้างไว้ตามสัญญาที่เราตกลงกันไว้ในหัวข้อก่อนหน้านี้
สิ่งนี้จะช่วยให้เราทำงานเป็นทีมได้ง่ายขึ้น เพื่อนร่วมทีมจะรู้ทันทีว่าต้องไปหาโค้ดส่วนไหนในโฟลเดอร์ไหน ไม่ต้องมานั่งไล่เปิดดูทุกไฟล์ในโปรเจกต์ให้ปวดหัว และช่วยให้เราสามารถเพิ่มฟีเจอร์ใหม่โดยไม่ไปกระทบฟีเจอร์เดิมที่มีอยู่แล้ว
รู้จัก Clean Architecture แบบย่อ
Clean Architecture (โครงสร้างโค้ดที่สะอาด) คือแนวคิดที่แบ่งแอปออกเป็นชั้นๆ เหมือนหัวหอม โดยมีกฎว่าชั้นในสุด (หัวใจของแอป) ต้องไม่รู้จักชั้นนอกสุดเลย เช่น ตรรกะของแอปไม่ควรต้องรู้ว่าเราใช้ฐานข้อมูลประเภทไหนหรือใช้ Flutter อยู่ด้วยซ้ำ
เหตุผลที่ต้องทำแบบนี้คือเพื่อให้เราเปลี่ยนเทคโนโลยีได้ในอนาคต เช่น ถ้าวันหนึ่งเราอยากเปลี่ยนจาก SQLite (ฐานข้อมูลบนมือถือ) ไปใช้ Firebase (ฐานข้อมูลบนคลาวด์) เราก็แค่เปลี่ยนเฉพาะชั้นนอกสุด ส่วนตรรกะข้างในแอปของเราจะยังคงใช้งานได้เหมือนเดิมเป๊ะ
สำหรับมือใหม่ การเริ่มทำ Clean Architecture เต็มรูปแบบอาจจะดูยากเกินไป ให้เริ่มจากการแยกส่วนดึงข้อมูล (Data) ออกจากส่วนแสดงผล (UI) ก่อน แล้วค่อยๆ เพิ่มชั้นอื่นๆ เข้าไปเมื่อเราเริ่มคล่องแล้ว อย่าเพิ่งรีบทำทุกอย่างให้สมบูรณ์ตั้งแต่โปรเจกต์แรก
สรุป: เริ่มต้นก้าวแรกสู่โปรแกรมเมอร์มืออาชีพ
การฝึกเขียนโค้ดแบบเลโก้ไม่ได้หมายความว่าคุณต้องเขียนให้ซับซ้อนตั้งแต่ต้น แต่มันคือการสร้าง วินัย (Discipline) ในการแยกส่วนประกอบให้ชัดเจน และการใช้สัญญา (Interface) เพื่อให้โค้ดแต่ละส่วนทำงานร่วมกันได้โดยไม่ต้องยึดติดกัน
ลองนำแนวคิดนี้ไปใช้กับโปรเจกต์เล็กๆ เช่น แอปรายการสิ่งที่ต้องทำ (To-do list) แทนที่จะเขียนโค้ดดึงรายการมาโชว์ในไฟล์เดียว ให้ลองแยกส่วนดึงข้อมูลออกมาเป็นคลาสต่างหาก แล้วสร้างสัญญาให้หน้าจอเรียกใช้ผ่านคลาสนั้น
เมื่อคุณทำแบบนี้บ่อยๆ คุณจะพบว่าการเพิ่มฟีเจอร์ใหม่หรือการแก้บั๊ก (ข้อผิดพลาดในโปรแกรม) จะกลายเป็นเรื่องสนุกและปลอดภัยขึ้นมาก นี่คือทักษะที่โปรแกรมเมอร์เก่งๆ ทุกคนมี และเป็นก้าวสำคัญที่จะทำให้คุณพร้อมสำหรับการทำงานในบริษัทซอฟต์แวร์ระดับมืออาชีพอย่างแน่นอน
ที่มา: How to Implement LEGO Architecture in Flutter [Full Handbook] — freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More