Observer — แพทเทิร์นเชิงพฤติกรรมที่ออบเจ็กต์หนึ่ง (ผู้เผยแพร่) แจ้งให้ผู้สมัครสมาชิกหลายรายทราบเกี่ยวกับการเปลี่ยนแปลงสถานะของตน ในการพัฒนาแอปมือถือ Observer เป็นพื้นฐานของกลไกเชิงรับ: UI สมัครสมาชิกการเปลี่ยนแปลงข้อมูลและอัปเดตโดยอัตโนมัติ แพทเทิร์นนี้ถูกนำไปใช้ใน NotificationCenter บน iOS และ LiveData/Flow บน Android รายละเอียดเพิ่มเติมที่ Refactoring Guru: Observer
ประเด็นสำคัญ
Observer — แพทเทิร์นเชิงพฤติกรรม GoF ที่กำหนดความสัมพันธ์แบบหนึ่งต่อหลายระหว่างออบเจ็กต์ เมื่อออบเจ็กต์หนึ่ง (Subject หรือ Observable) เปลี่ยนสถานะ ออบเจ็กต์ที่ขึ้นต่อกันทั้งหมด (Observers) จะได้รับการแจ้งเตือนและอัปเดตโดยอัตโนมัติ แพทเทิร์นนี้ใช้การเชื่อมโยงแบบหลวม: ผู้เผยแพร่ไม่ทราบคลาสเฉพาะของผู้สมัครสมาชิก — เพียงแต่ว่าพวกเขาใช้อินเทอร์เฟซ Observer
โครงสร้าง Observer ประกอบด้วยอินเทอร์เฟซ Subject พร้อมเมธอด attach(), detach(), notify() และอินเทอร์เฟซ Observer พร้อมเมธอด update() ConcreteSubject เก็บสถานะและรายการผู้สมัครสมาชิก ConcreteObserver ใช้ update() และตอบสนองต่อการเปลี่ยนแปลง ในการพัฒนาแอปมือถือ การนำ GoF แบบคลาสสิกไปใช้นั้นหายาก — ถูกแทนที่ด้วยกลไกในตัว: NotificationCenter, Combine, Flow, LiveData ซึ่งใช้แนวคิดเดียวกันกับ API ที่ทันสมัย
โมเดล Push vs Pull — ในโมเดล Push Subject จะส่งข้อมูลไปยังผู้สมัครสมาชิกทั้งหมด (NotificationCenter.post) ในโมเดล Pull Subject จะแจ้งเตือนเท่านั้น และผู้สมัครสมาชิกจะขอข้อมูลเอง Android LiveData ใช้ Push (ข้อมูลถูกส่งใน observe()) RxJava/Flow รองรับทั้งสองโมเดล การเลือกขึ้นอยู่กับงาน: Push ง่ายกว่าสำหรับการอัปเดต UI Pull มีประสิทธิภาพมากกว่าสำหรับข้อมูลปริมาณมากที่ผู้สมัครสมาชิกอาจไม่ต้องการรับ
NotificationCenter — กลไกในตัวของ iOS/macOS สำหรับการใช้ Observer ผู้เผยแพร่ส่ง Notification ผ่าน NotificationCenter.default.post(name:, object:, userInfo:) ผู้สมัครสมาชิกลงทะเบียนผ่าน addObserver(forName:, queue:, using:) NotificationCenter รองรับการแจ้งเตือนที่มีชื่อ (Notification.Name) และสามารถส่งข้อมูลใด ๆ ใน userInfo UIKeyboardWillShowNotification, UIApplicationDidEnterBackgroundNotification — ตัวอย่างของระบบ
extension Notification.Name {
static let userDidLogin = Notification.Name("userDidLogin")
}
// ผู้เผยแพร่
NotificationCenter.default.post(
name: .userDidLogin,
object: nil,
userInfo: ["userId": "123"]
)
// ผู้สมัครสมาชิก
class ProfileViewModel {
private var observers: [NSObjectProtocol] = []
func startObserving() {
let observer = NotificationCenter.default.addObserver(
forName: .userDidLogin,
object: nil,
queue: .main
) { [weak self] notification in
guard let userId = notification.userInfo?["userId"] as? String else { return }
// ผู้สมัครสมาชิกตอบสนองต่อเหตุการณ์
self?.loadProfile(userId: userId)
}
observers.append(observer)
}
func stopObserving() {
observers.forEach { NotificationCenter.default.removeObserver($0) }
observers.removeAll()
}
}
เฟรมเวิร์ก Combine — ทางเลือกเชิงรับที่ทันสมัยสำหรับ NotificationCenter เปิดตัวใน iOS 13 Publisher (NotificationCenter, URLSession, Timer) — ผู้เผยแพร่, Subscriber (sink, assign) — ผู้สมัครสมาชิก Combine เพิ่มตัวดำเนินการ (map, filter, combineLatest) สำหรับการแปลงสตรีมข้อมูล @Published — property wrapper ที่แจ้งให้ผู้สมัครสมาชิกทราบเกี่ยวกับการเปลี่ยนแปลงโดยอัตโนมัติ ใน MVVM กับ SwiftUI Combine แทนที่ NotificationCenter สำหรับเชื่อมต่อ ViewModel และ View
KVO (Key-Value Observing) — กลไก ObjC/Swift รุ่นเก่าสำหรับสังเกตคุณสมบัติแต่ละรายการของออบเจ็กต์ @objc dynamic var name: String — คุณสมบัติที่สังเกตได้ observe(.name) — การสมัครสมาชิก KVO ทำงานเฉพาะกับคลาสที่เข้ากันได้กับ @objc และการสืบทอด ObjC Apple แนะนำ Combine และ @Published แทน KVO ในโปรเจ็กต์ใหม่ KVO ยังคงเกี่ยวข้องสำหรับความเข้ากันได้กับ UIKit ในโปรเจ็กต์แบบผสม
LiveData — คอมโพเนนต์ของ Android Architecture Components สำหรับการใช้ Observer คลาสที่สังเกตได้ซึ่งแจ้งให้ผู้สมัครสมาชิกทราบเกี่ยวกับการเปลี่ยนแปลงข้อมูล LiveData ตระหนักถึงวงจรชีวิต: ผู้สมัครสมาชิก (LifecycleOwner) ยกเลิกการสมัครโดยอัตโนมัติเมื่อถูกทำลาย LiveData ใช้โมเดล Push: ข้อมูลถูกส่งใน observe() LiveData เป็นบล็อกพื้นฐานของ MVVM ใน Android ก่อน Jetpack Compose
// ViewModel — ผู้เผยแพร่
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User?>(null)
val user: LiveData<User?> = _user
fun loadUser(id: String) {
viewModelScope.launch {
val result = userRepository.getUser(id)
_user.value = result
}
}
}
// Fragment — ผู้สมัครสมาชิก
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.user.observe(viewLifecycleOwner) { user ->
// ผู้สมัครสมาชิกตอบสนองต่อการเปลี่ยนแปลง
userName.text = user?.name
userEmail.text = user?.email
}
}
}
StateFlow และ SharedFlow — ชนิดเชิงรับจาก Kotlin Coroutines ที่แทนที่ LiveData ใน Jetpack Compose StateFlow — ตัวเก็บสถานะที่สังเกตได้พร้อมค่าปัจจุบันคงที่ SharedFlow — โฟลว์ร้อนที่กำหนดค่าได้โดยไม่มีสถานะ เหมาะสำหรับเหตุการณ์ครั้งเดียว (การนำทาง, โทสต์) ทั้งสองชนิดถูกรวมเข้ากับ Compose อย่างใกล้ชิด: collectAsState(), collectAsEffect() StateFlow เป็นข้อบังคับในโปรเจ็กต์ Android สมัยใหม่กับ Compose
LiveData vs StateFlow — LiveData ผูกกับวงจรชีวิต Android, StateFlow ไม่ขึ้นกับแพลตฟอร์ม StateFlow รองรับ coroutines, ตัวดำเนินการ (map, filter) และสามารถทดสอบได้โดยไม่ต้องพึ่งพา Android LiveData ง่ายกว่าสำหรับความเข้ากันได้กับ Java Google แนะนำ StateFlow สำหรับโปรเจ็กต์ใหม่บน Kotlin + Compose, LiveData สำหรับการบำรุงรักษาโปรเจ็กต์เก่าหรือโค้ด Java
หน่วยความจำรั่ว — ปัญหาหลักของ Observer หากไม่มีการจัดการการสมัครสมาชิกที่เหมาะสม หากผู้สมัครสมาชิก (Activity, Fragment, UIViewController) ถูกทำลายแต่ไม่ได้ยกเลิกการสมัคร ผู้เผยแพร่จะยังคงเก็บการอ้างอิงถึงมันและตัวเก็บขยะไม่สามารถปล่อยหน่วยความจำได้ ใน Android LifecycleOwner (Activity/Fragment) ควรเรียก removeObserver() หรือใช้ observe(viewLifecycleOwner) ใน iOS — removeObserver ใน deinit หรือ disposeBag ใน Combine
| แพลตฟอร์ม | กลไก Observer | การยกเลิกอัตโนมัติ | การยกเลิกด้วยตนเอง |
|---|---|---|---|
| iOS | NotificationCenter | ไม่ | removeObserver() ใน deinit |
| iOS | Combine (sink) | ไม่ | store(in: &bag) — DisposeBag |
| iOS | KVO | ไม่ | removeObserver() ใน deinit |
| Android | LiveData | ใช่ (LifecycleOwner) | removeObserver() ไม่บังคับ |
| Android | StateFlow | ผ่าน viewModelScope | cancel() Job เมื่อยกเลิก |
| Android | RxJava | ไม่ | dispose() ใน CompositeDisposable |
การอ้างอิงแบบอ่อนในผู้สมัครสมาชิก — เมื่อใช้ closure ในบล็อกการสมัครสมาชิก ให้ใช้ [weak self] ใน Swift และอ้างอิงขอบเขตวงจรชีวิตใน Kotlin LiveData จัดการการสมัครสมาชิกโดยอัตโนมัติผ่าน LifecycleOwner — การสมัครสมาชิกจะทำงานเฉพาะเมื่อ Lifecycle อยู่ในสถานะ STARTED หรือ RESUMED StateFlow ใน Compose ใช้ collectAsState() โดยคำนึงถึงวงจรชีวิต NotificationCenter ใน iOS ต้องใช้ [weak self] อย่างชัดเจนเนื่องจาก closure เก็บการอ้างอิงแบบแข็งไปยัง self
Observer (GoF) และผู้เผยแพร่-ผู้สมัครสมาชิก (PubSub) — แพทเทิร์นที่คล้ายกันแต่ต่างกัน ใน Observer ผู้เผยแพร่แจ้งผู้สมัครสมาชิกโดยตรงโดยเรียกเมธอดของพวกเขา ผู้เผยแพร่รู้เกี่ยวกับผู้สมัครสมาชิก (เก็บรายการ) ใน PubSub ผู้เผยแพร่และผู้สมัครสมาชิกไม่รู้จักกัน — มีตัวกลาง (Event Bus, Message Queue, NotificationCenter) อยู่ระหว่างพวกเขา ผู้เผยแพร่ส่งข้อความไปยังช่องทาง ผู้สมัครสมาชิกฟังช่องทาง PubSub มีการเชื่อมโยงที่หลวมกว่า
ตัวอย่างของ PubSub ในการพัฒนาแอปมือถือ — NotificationCenter ใน iOS ถือได้ว่าเป็น PubSub: ผู้เผยแพร่ไม่รู้จักผู้สมัครสมาชิก — เพียงแค่โพสต์การแจ้งเตือน EventBus หรือ Otto ใน Android (เลิกใช้แล้ว) SharedFlow กับ BroadcastChannel — PubSub ในโลก Kotlin ในระบบกระจาย PubSub ถูกนำไปใช้ผ่าน RabbitMQ, Kafka, Google PubSub สำหรับการพัฒนาแอปมือถือ PubSub มีประโยชน์ในสถาปัตยกรรมแบบโมดูลที่โมดูลไม่ควรพึ่งพากัน
เลือกอะไร — สำหรับการอัปเดต UI (ViewModel → View) ให้ใช้ Observer (LiveData, StateFlow, @Published) สำหรับเหตุการณ์ระหว่างโมดูล (การยืนยันตัวตน, การออกจากระบบ, การเปลี่ยนธีม) — PubSub (SharedFlow, NotificationCenter, EventBus) Observer ง่ายกว่าและมีประสิทธิภาพมากกว่าภายในหน้าจอเดียว PubSub ยืดหยุ่นกว่าสำหรับเหตุการณ์ทั่วโลกแต่ดีบักยากกว่าเนื่องจากการพึ่งพาโดยนัย
คำถามที่พบบ่อย
StateFlow เป็นชนิดที่ไม่ขึ้นกับแพลตฟอร์มจาก Kotlin Coroutines ในขณะที่ LiveData ผูกกับวงจรชีวิต Android StateFlow รองรับ coroutines และตัวดำเนินการ และสามารถทดสอบได้โดยไม่มี Android LiveData จัดการการสมัครสมาชิกโดยอัตโนมัติผ่าน LifecycleOwner Google แนะนำ StateFlow สำหรับโปรเจ็กต์ใหม่บน Kotlin + Compose และ LiveData สำหรับความเข้ากันได้กับ Java
ใช้ [weak self] ใน closure ตัวจัดการและเรียก removeObserver() ใน deinit เก็บการอ้างอิงไปยัง observer (NSObjectProtocol) และลบออกเมื่อออบเจ็กต์ถูกทำลาย ใน Combine ใช้ AnyCancellable และ store(in:) สำหรับการยกเลิกอัตโนมัติเมื่อ DisposeBag ถูกปล่อย
ได้ SwiftUI รองรับ ObservableObject กับ @Published และ @StateObject/@ObservedObject — นี่คือการนำ Observer ไปใช้ในตัว @Published แจ้งให้ View ทราบเกี่ยวกับการเปลี่ยนแปลงโดยอัตโนมัติ Combine ไม่จำเป็น: ObservableObject ใช้ Publisher objectWillChange ที่สร้างใน SwiftUI Combine เพิ่มตัวดำเนินการสำหรับการแปลงสตรีม
SharedFlow — สำหรับเหตุการณ์ครั้งเดียว (การนำทาง, โทสต์, Snackbar) ที่ไม่ต้องการค่าปัจจุบัน StateFlow — สำหรับสถานะ UI (รายการข้อมูล, ความคืบหน้าการโหลด) ที่ต้องการภาพรวมปัจจุบัน SharedFlow ไม่มีคุณสมบัติ value และไม่ส่งคืนค่าล่าสุดให้กับผู้สมัครสมาชิกใหม่
KVO เป็นกลไก ObjC ดั้งเดิม ต้องการ @objc dynamic และทำงานเฉพาะกับคลาสที่สืบทอดจาก NSObject Combine เป็นเฟรมเวิร์ก Swift ที่ทันสมัย ปลอดภัยชนิด พร้อมตัวดำเนินการและการรวมกับ SwiftUI Combine แทนที่ KVO และ NotificationCenter Apple แนะนำ Combine สำหรับโปรเจ็กต์ใหม่ KVO สำหรับการสนับสนุนรุ่นเก่าเท่านั้น
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ