Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 122 additions & 39 deletions DesignPatterns/AppleStoreObserver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Collaborator

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?


- 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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()
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
```