Facade เป็นแพทเทิร์นการออกแบบเชิงโครงสร้าง (structural design pattern) ที่ให้อินเทอร์เฟซที่เรียบง่ายสำหรับระบบย่อยของคลาสที่ซับซ้อน ในการพัฒนาโมบาย Facade มักถูกนำไปใช้เป็น Service Layer หรือ UseCase ซึ่งซ่อนการโต้ตอบกับเครือข่าย ฐานข้อมูล และระบบวิเคราะห์ข้อมูล ตามข้อมูลของ Martin Fowler (Patterns of Enterprise Application Architecture, 2003) Facade เป็นหนึ่งในแพทเทิร์นหลักสำหรับการจัดโครงสร้างชั้นบริการ
สาระสำคัญ
Facade เป็นแพทเทิร์นเชิงโครงสร้างที่ให้อินเทอร์เฟซแบบรวมศูนย์สำหรับกลุ่มของอินเทอร์เฟซในระบบย่อย มันกำหนดอินเทอร์เฟซระดับสูงที่ทำให้การใช้งานระบบย่อยง่ายขึ้น Facade ไม่ได้เพิ่มฟังก์ชันใหม่ — มันจัดระเบียบส่วนประกอบที่มีอยู่ โดยซ่อนความซับซ้อนของการโต้ตอบของพวกมันจากไคลเอนต์
// ระบบย่อยที่ซับซ้อน
class AuthApi {
suspend fun login(email: String, pass: String): TokenResponse
}
class UserDao {
suspend fun saveUser(user: UserEntity)
suspend fun getUser(id: Long): UserEntity?
}
class AnalyticsTracker {
fun track(event: String, params: Map )
}
// Facade — อินเทอร์เฟซที่เรียบง่ายสำหรับ UI
class AuthService(
private val api: AuthApi,
private val dao: UserDao,
private val analytics: AnalyticsTracker
) {
suspend fun loginUser(email: String, password: String): Result {
return runCatching {
val token = api.login(email, password)
val user = User(token.userId, email, token.accessToken)
dao.saveUser(user.toEntity())
analytics.track("login_success", mapOf("method" to "email"))
user
}
}
}AuthService เป็น Facade ที่ซ่อน AuthApi UserDao และ AnalyticsTracker จาก ViewModel UI เรียกใช้ loginUser(email, password) แทนการส่งคำขอแยกกันสามคำขอไปยัง API ฐานข้อมูล และระบบวิเคราะห์ข้อมูล สิ่งนี้ลดการผูกโยง: ถ้าพรุ่งนี้ AuthApi กลายเป็น FirebaseAuth หรือ UserDao ย้ายไปยัง Room ก็จะมีเพียง Facade ที่เปลี่ยนไป ไม่ใช่ UI
Service Layer เป็นการนำ Facade ไปใช้ที่พบบ่อยในแอปพลิเคชันโมบาย มันห่อหุ้มตรรกะทางธุรกิจและการประสานงานระหว่างชั้นต่าง ๆ ใน Android Service Layer มักถูกนำไปใช้ผ่าน UseCase (Clean Architecture) และใน iOS ผ่านโปรโตคอล Manager หรือ Service
| ส่วนประกอบ | บทบาทในระบบย่อย | สิ่งที่ Facade ซ่อน |
|---|---|---|
| AuthApi | คำขอเครือข่ายไปยังเซิร์ฟเวอร์ | รูปแบบคำขอ endpoint การจัดการข้อผิดพลาด HTTP |
| UserDao | การจัดเก็บโทเคนในเครื่อง | สคีมาฐานข้อมูล คำสั่ง SQL การย้ายข้อมูล |
| AnalyticsTracker | การส่งอีเวนต์การวิเคราะห์ | Firebase/AppMetrica SDK รูปแบบอีเวนต์ |
| NetworkMonitor | การตรวจสอบความพร้อมใช้งานของเครือข่าย | ConnectivityManager, BroadcastReceiver |
AuthService รวมส่วนประกอบทั้งสี่เข้าด้วยกัน ViewModel เรียกใช้เมธอดหนึ่งเมธอดโดยไม่รู้ว่าเบื้องหลังมีการส่งคำขอเครือข่าย การเขียนฐานข้อมูล การติดตาม และการตรวจสอบเครือข่าย เมื่อทดสอบ สามารถแทนที่ AuthService ด้วย mock เพื่อตรวจสอบตรรกะการยืนยันตัวตนทั้งหมดโดยไม่ต้องบูรณาการกับส่วนประกอบจริง
Facade, Adapter และ Mediator เป็นแพทเทิร์นเชิงโครงสร้าง แต่แก้ปัญหาที่แตกต่างกัน พวกมันมักถูกสับสนเพราะทั้งสามต่างก็แนะนำวัตถุตัวกลาง มาวิเคราะห์ความแตกต่างด้วยตัวอย่างแอปพลิเคชันโมบายกัน
| ด้าน | Facade | Adapter | Mediator |
|---|---|---|---|
| วัตถุประสงค์ | ทำให้อินเทอร์เฟซระบบย่อยง่ายขึ้น | แปลงอินเทอร์เฟซ | ลดการผูกโยงระหว่างส่วนประกอบ |
| ทิศทาง | หนึ่งอินเทอร์เฟซ → ระบบย่อย | ไคลเอนต์ → Adaptee | N ส่วนประกอบ ↔ Mediator |
| การเปลี่ยนอินเทอร์เฟซ | สร้างใหม่ที่เรียบง่าย | แปลงอินเทอร์เฟซที่มีอยู่ | ไม่เปลี่ยน แต่ประสานงาน |
| ระบบย่อยรู้จักแพทเทิร์นหรือไม่? | ไม่ | ไม่ | ใช่ สื่อสารผ่าน Mediator |
| ตัวอย่างในการพัฒนาโมบาย | UseCase / Service Layer | RecyclerView.Adapter | Coordinator ใน iOS |
Facade ไม่ได้ซ่อนระบบย่อย — ไคลเอนต์สามารถเข้าถึง AuthApi ได้โดยตรงเมื่อจำเป็น Adapter จำเป็นต้องเปลี่ยนอินเทอร์เฟซของ Adaptee Mediator ประสานงานการโต้ตอบที่ซับซ้อนระหว่างวัตถุจำนวนมากที่อาจไม่รู้จักกันและกัน
การนำ Facade ไปใช้ ใน Kotlin สำหรับ Android ด้วย Clean Architecture ใช้ UseCase เป็นจุดเข้าสำหรับแต่ละสถานการณ์ธุรกิจ UseCase คือ Facade ที่ซ่อน repository mapper และการพึ่งพาอื่น ๆ จากชั้น UI
// Repository ก็คือ Facade แต่ระดับต่ำกว่า
class UserRepositoryImpl(
private val local: UserLocalDataSource,
private val remote: UserRemoteDataSource,
private val mapper: UserMapper
) : UserRepository {
override suspend fun getUserProfile(id: String): UserProfile {
val cached = local.getUser(id)
if (cached != null && !cached.isStale) {
return mapper.toProfile(cached)
}
val dto = remote.fetchUser(id)
val entity = mapper.toEntity(dto)
local.saveUser(entity)
return mapper.toProfile(entity)
}
}
// UseCase — Facade สำหรับสถานการณ์ธุรกิจ
class LoadUserProfileUseCase(
private val repo: UserRepository,
private val analytics: AnalyticsTracker
) {
suspend operator fun invoke(userId: String): Result {
return runCatching {
val profile = repo.getUserProfile(userId)
analytics.track("profile_loaded", mapOf("user_id" to userId))
profile
}
}
}LoadUserProfileUseCase เป็น Facade สำหรับสถานการณ์การโหลดโปรไฟล์ มันซ่อนตรรกะการแคช (local → remote) การแมป DTO → Entity → Profile และการติดตามการวิเคราะห์ ViewModel เรียกใช้ invoke(userId) และได้รับ UserProfile ที่พร้อมใช้งานหรือข้อผิดพลาด สามารถทดสอบ UseCase แบบแยกส่วนได้โดยการแทนที่ repository ด้วยวัตถุ mock
Facade ใน iOS มักถูกนำไปใช้เป็น Manager หรือ Service ต่างจาก Android ตรงที่ iOS ใช้โปรโตคอลเพื่อกำหนดอินเทอร์เฟซของ Facade ซึ่งทำให้ง่ายต่อการสลับการนำไปใช้ในการทดสอบ ลองดู Facade สำหรับการทำงานกับสื่อ — การโหลด การแคช และการแสดงผล
protocol MediaServiceProtocol {
func loadImage(from url: URL) async -> Result<UIImage, Error>
}
final class MediaService: MediaServiceProtocol {
private let cache: ImageCache
private let downloader: ImageDownloader
private let decoder: ImageDecoder
func loadImage(from url: URL) async -> Result<UIImage, Error> {
// 1. ตรวจสอบแคช
if let cached = cache.image(for: url) {
return .success(cached)
}
// 2. โหลดข้อมูล
let result = await downloader.download(from: url)
guard case let .success(data) = result else {
return .failure(MediaError.downloadFailed)
}
// 3. ถอดรหัส
guard let image = decoder.decode(data) else {
return .failure(MediaError.decodeFailed)
}
// 4. บันทึกลงแคช
cache.setImage(image, for: url)
return .success(image)
}
}MediaService ห่อหุ้มกระบวนการสามขั้นตอน: แคช → โหลด → ถอดรหัส UI เรียกใช้เมธอดเดียว loadImage(from:) แทนการจัดการ ImageCache, URLSession และ ImageDecoder เมื่อทดสอบ สามารถแทนที่ MediaServiceProtocol ด้วย mock ที่คืนรูปภาพที่กำหนดไว้ล่วงหน้าโดยไม่ต้องโหลดจริง
ข้อผิดพลาด ในการออกแบบ Facade ทำให้ข้อดีของมันไร้ประโยชน์: แทนที่จะง่ายขึ้น คุณกลับได้ God Object ที่ทั้งระบบต้องพึ่งพา มาวิเคราะห์ปัญหาหลักสามข้อกัน
เมื่อ Facade เดียวมีเมธอดสำหรับการยืนยันตัวตน การโหลดโปรไฟล์ การส่งข้อความ และการซิงโครไนซ์ — นี่คือ God Object สัญญาณ: มีเมธอดสาธารณะมากกว่า 15 เมธอดในคลาสเดียว วิธีแก้: แบ่งเป็น Facade เฉพาะทางหลายตัวตามขอบเขตความรับผิดชอบ — AuthService, ProfileService, MessagingService
ถ้า Facade คืนประเภทที่เฉพาะเจาะจงกับระบบย่อย (เช่น FirebaseUser หรือ RealmObject) ไคลเอนต์ก็ยังผูกติดกับการนำไปใช้แบบเฉพาะเจาะจง วิธีแก้: Facade ควรคืนเฉพาะประเภทของตัวเอง (data class / struct) โดยแยกไคลเอนต์ออกจากรายละเอียดของระบบย่อยอย่างสมบูรณ์
เมื่อ Facade ห้ามการเข้าถึงระบบย่อยโดยตรง มันกลายเป็นคอขวด บางครั้งไคลเอนต์ต้องการเมธอดเฉพาะของระบบย่อย และการบังคับให้มันผ่าน Facade ก็เกินจำเป็น Facade ไม่ควรเป็นผู้เฝ้าประตูที่เข้มงวด: มันให้อินเทอร์เฟซที่สะดวก แต่ไม่ได้ปิดกั้นการเข้าถึงส่วนประกอบโดยตรง
คำถามที่พบบ่อย
Facade ให้อินเทอร์เฟซที่เรียบง่ายแก่ระบบย่อย โดยมักสร้างชุดเมธอดใหม่ Proxy คงอินเทอร์เฟซเดียวกันกับวัตถุดั้งเดิม แต่เพิ่มการควบคุมการเข้าถึงหรือการโหลดแบบขี้เกียจ Facade มีไว้เพื่อความง่าย Proxy มีไว้เพื่อการควบคุม
Service Layer คือการนำแพทเทิร์น Facade ไปใช้ในระดับสถาปัตยกรรมของแอปพลิเคชัน มันกำหนดขอบเขตระหว่าง UI และตรรกะทางธุรกิจ โดยซ่อนรายละเอียดการนำไปใช้ของบริการ ใน Android Service Layer มักถูกนำไปใช้ผ่าน UseCase; ใน iOS — ผ่านโปรโตคอล Manager หรือ Service
God Facade เกิดขึ้นเมื่อคลาสหนึ่งรับผิดชอบระบบย่อยหลายระบบที่ไม่เกี่ยวข้องกัน สัญญาณ: มีเมธอดสาธารณะมากกว่า 15 เมธอด เมธอดจากโดเมนต่าง ๆ (การยืนยันตัวตน + การชำระเงิน + การแจ้งเตือน) คลาสที่ทดสอบยาก (มีการพึ่งพามากกว่า 10 รายการ) วิธีแก้: แบ่งเป็น Facade ตามโดเมน
ในแอปพลิเคชันที่มี 1-2 หน้าจอ Facade เกินจำเป็น — การเรียก API และฐานข้อมูลโดยตรงจาก UI นั้นง่ายและชัดเจนกว่า Facade คุ้มค่า เมื่อมี 5+ หน้าจอและ 3+ ระบบย่อย ในโปรเจกต์ขนาดเล็กและขนาดกลาง Repository เดียวเป็นชั้น Facade ก็เพียงพอแล้ว โดยไม่ต้องใช้ UseCase wrapper เพิ่มเติม
Facade ทำให้การทดสอบง่ายขึ้น เพราะมันแทนที่ระบบย่อยทั้งหมดด้วยวัตถุ mock เดียว แทนที่จะ mock สามส่วนประกอบ (เครือข่าย + ฐานข้อมูล + การวิเคราะห์) แค่ mock Facade ตัวเดียวก็เพียงพอ ใน Swift ใช้โปรโตคอลสำหรับสิ่งนี้ ใน Kotlin ใช้ interface Facade ยังสะดวกสำหรับการทดสอบการบูรณาการที่ตรวจสอบการจัดระเบียบของส่วนประกอบ
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม