-
Notifications
You must be signed in to change notification settings - Fork 0
Observer #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Observer #19
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,78 +10,161 @@ | |
## Pattern overview | ||
|
||
- The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. | ||
- For example, in a database driven application, the data layer can notify the UI layer when a record is updated. | ||
- The Observer pattern enables a specific object to notify a list of observer objects when a change in state occurs. | ||
|
||
- The specific object is called the subject. | ||
|
||
- The subject is able to attach, detach, and notify observers. | ||
|
||
## Problem statement | ||
|
||
- We would like to keep the bag badge count up to date. | ||
- The observer pattern allows us to notify the bag icon view with the updated badge count when a product is added or removed. | ||
- Each Apple Store customer's bag contains an array of products, which are stored on the server. | ||
|
||
- When on the Apple Store bag view, we would like to keep the array of products up to date, even if they are added or removed from another device. | ||
|
||
- In the top navigation, we would also like the bag icon badge count to be in sync with the bag product count. | ||
|
||
- Without implementing the observer pattern, views would need to actively retrieve data in order to stay up to date. | ||
|
||
- This may result in redundant fetching, or the views may end up out of sync with the underlying model. | ||
|
||
## Domain application | ||
- The Observer pattern solves this problem by creating a single source of truth for the bag product array and notifying all observers when a change occurs. | ||
|
||
Subject: | ||
- This allows for a decoupled design where there is a clear separation of concerns between the subject and the observers. | ||
|
||
- Knows its observers. Any number of Observer objects may observe a subject. | ||
- Provides an interface for attaching and detaching Observer objects. | ||
## Definitions | ||
|
||
#### Observer: | ||
|
||
Defines the protocol for objects that would like to be notified of changes to the bag products. | ||
|
||
```swift | ||
protocol BagNotifier { | ||
func attach(observer: BagObserver) | ||
func detach(observer: BagObserver) | ||
func notify() | ||
protocol Observer { | ||
func notificationWithObject(_ object: Any) | ||
} | ||
``` | ||
|
||
#### Concrete observer: | ||
|
||
- Maintains a reference to a subject. | ||
|
||
- Stores state that should stay consistent with the subject. | ||
|
||
- Implements the `bagUpdatedWithProducts` to update its state when the subject notifies it. | ||
|
||
- We attach the observer to the subject in the initializer, and detach it in the deinitializer. | ||
|
||
```swift | ||
class BagListViewModel: Observer { | ||
var products: [Product] = [] | ||
private let notifier: ObserverManagingNotifier | ||
|
||
init(notifier: ObserverManagingNotifier) { | ||
self.notifier = notifier | ||
self.notifier.attachObserver(self) | ||
} | ||
|
||
func notificationWithObject(_ object: Any) { | ||
if let products = object as? [Product] { | ||
self.products = products | ||
} | ||
} | ||
|
||
deinit { | ||
notifier.detachObserver(self) | ||
} | ||
} | ||
|
||
class BagIconViewModel: Observer { | ||
var badgeCount: Int = 0 | ||
private let notifier: ObserverManagingNotifier | ||
|
||
init(notifier: ObserverManagingNotifier) { | ||
self.notifier = notifier | ||
self.notifier.attachObserver(self) | ||
} | ||
|
||
func notificationWithObject(_ object: Any) { | ||
if let products = object as? [Product] { | ||
self.badgeCount = products.count | ||
} | ||
} | ||
|
||
deinit { | ||
notifier.detachObserver(self) | ||
} | ||
} | ||
``` | ||
|
||
Observer: | ||
#### Subject: | ||
|
||
- `ObserverManaging` defines the protocol for managing observers. | ||
|
||
Defines an updating interface for objects that should be notified of changes in a subject. | ||
- `Notifier` defines the protocol for notifying observers. | ||
|
||
```swift | ||
protocol BagObserver { | ||
func updateBagCount(_ count: Int) | ||
protocol ObserverManagingNotifier { | ||
func attachObserver(_ observer: Observer) | ||
func detachObserver(_ observer: Observer) | ||
func notify() | ||
} | ||
``` | ||
|
||
ConcreteSubject: | ||
#### Concrete subject: | ||
|
||
- Manages a list of observers. | ||
|
||
- Stores the state of interest to observer objects. | ||
|
||
- Stores state of interest to ConcreteObserver objects. | ||
- Sends a notification to its observers when its state changes. | ||
|
||
```swift | ||
class BagService: BagNotifier { | ||
private var observers: [BagObserver] = [] | ||
private var badgeCount: Int = 0 | ||
class WebSocketBagNotifier: ObserverManagingNotifier { | ||
private var observers: [Observer] = [] | ||
private var products: [Product] = [] | ||
|
||
func attach(observer: BagObserver) { | ||
observers.append(observer) | ||
func addProduct(_ product: Product) { | ||
self.products.append(product) | ||
} | ||
Comment on lines
+126
to
128
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When a product is added surely you want to notify observers?? There should be a call to notify in this method, and notify should be private. |
||
|
||
func detach(observer: BagObserver) { | ||
observers = observers.filter { $0 !== observer } | ||
func attachObserver(_ observer: Observer) { | ||
observers.append(observer) | ||
} | ||
|
||
func notify() { | ||
observers.forEach { $0.update(count: badgeCount) } | ||
func detachObserver(_ observer: Observer) { | ||
// Remove observer from the list | ||
} | ||
|
||
func addProduct() { | ||
badgeCount += 1 | ||
notify() | ||
func notify() { | ||
observers.forEach { $0.notificationWithObject(self.products) } | ||
} | ||
} | ||
``` | ||
|
||
ConcreteObserver: | ||
|
||
- Maintains a reference to a ConcreteSubject object. | ||
- Stores state that should stay consistent with the subject's. | ||
- Implements the Observer updating interface to keep its state consistent with the subject's. | ||
## Example | ||
|
||
```swift | ||
class BagIconView: BagObserver { | ||
func updateBagCount(_ count:) { | ||
// Update the badge count | ||
} | ||
struct Product { | ||
let name: String | ||
let price: Double | ||
} | ||
|
||
let notifier = WebSocketBagNotifier() | ||
let bagListViewModel = BagListViewModel(notifier: notifier) | ||
let bagIconViewModel = BagIconViewModel(notifier: notifier) | ||
|
||
// Default values | ||
|
||
print(bagListViewModel.products) // [] | ||
print(bagIconViewModel.badgeCount) // 0 | ||
|
||
notifier.addProduct( | ||
Product(name: "iPad Pro", price: 999.99) | ||
) | ||
notifier.notify() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't need to call notify on the notifier. The class should be set up to handle notifications when its data changes. |
||
|
||
// Values after product added to the bag | ||
|
||
print(bagListViewModel.products) // [Product(name: "iPad Pro", price: 999.99)] | ||
print(bagIconViewModel.badgeCount) // 1 | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bag? Do you mean cart?