Behavioral patterns

Behavioral patterns are patterns that describe how objects will communicate with each other. In other words, it is how one object will send information to another object, even if that information is just that some event has occurred. They help to lower the code's coupling by providing a more detached communication mechanism that allows one object to send information to another, while having as little knowledge about the other object as possible. The less any type knows about the rest of the types in the code base, the less it will depend on those types. These behavior patterns also help to increase cohesion by providing straightforward and understandable ways to send the information.

This can often be the difference between doing something, such as calling your sister to ask your mom to ask your grandpa what he wants for his birthday and being able to ask your grandpa directly because you have a good communication channel open with him. In general, we will want to have the direct channel of communication open but sometimes it is actually better design to interact with fewer people, as long as we don't put too much burden on the other components. Behavioral patterns can help us with this.

Iterator

The first behavioral pattern we will discuss is called the iterator pattern. We are starting with this one because we have actually already made use of this pattern in Chapter 6, Make Swift Work For You – Protocols and Generics. The idea of the iterator pattern is to provide a way to step through the contents of a container independent of the way the elements are represented inside the container.

As we saw, Swift provides us with the basics of this pattern with the GeneratorType and SequenceType protocols. It even implements those protocols for its array and dictionary containers. Even though we don't know how the elements are stored within an array or dictionary, we are still able to step through each value contained within them. Apple can easily change the way the elements are stored within them and it would not affect how we loop through the containers at all. This shows a great decoupling between our code and the container implementations.

If you remember, we were even able to create a generator for the infinite Fibonacci sequence:

struct FibonacciGenerator: GeneratorType {
    typealias Element = Int

    var values = (0, 1)

    mutating func next() -> Element? {
        self.values = (
            self.values.1,
            self.values.0 + self.values.1
        )
        return self.values.0
    }
}

The "container" doesn't even store any elements but we can still iterate through them as if it did.

The iterator pattern is a great introduction to how we make real world use of design patterns. Stepping through a list is such a common problem that Apple built the pattern directly into Swift.

Observer

The other behavioral pattern that we will discuss is called the observer pattern. The basic idea of this pattern is that you have one object that is designed to allow other objects to be notified when something occurs.

Callback

In Swift, the easiest way to achieve this is to provide a closure property on the object that you want to be observable and have that object call the closure whenever it wants to notify its observer. The property will be optional, so that any other object can set their closure on this property:

class ATM {
    var onCashWithdrawn: ((amount: Double) -> ())?

    func withdrawCash(amount: Double) {
        // other work

        // Notify observer if any
        if let callback = self.onCashWithdrawn {
            callback(amount: amount)
        }
    }
}

Here we have a class that represents an ATM that allows for withdrawing cash. It provides a closure property called onCashWithdrawn that is called every time cash is withdrawn. This type of closure property is usually called a callback. It is a good idea to make its purpose clear by its name. I personally choose to name all event-based callbacks by starting them with the word "on."

Now, any object can define its own closure on the callback and be notified whenever cash is withdrawn:

class RecordKeeper {
    var transactions = [Double]()

    func watchATM(atm: ATM) {
        atm.onCashWithdrawn = { [weak self] amount in
            self?.transactions.append(amount)
        }
    }
}

In this case, ATM is considered the observable object and the RecordKeeper is the observer. The ATM type is completely disconnected from whatever process might be keeping a record of its transactions. The record keeping mechanism can be changed without making any changes to the ATM and the ATM can be changed without any change to the RecordKeeper as long as the new ATM implementation still calls onCashWithDrawn whenever cash is withdrawn.

However, the RecordKeeper needs to be passed an ATM instance for this connection to be made. There can also only ever be one observer at a time. If we need to allow multiple observers, we can potentially provide an array of callbacks, but that can make removing observers more difficult. A solution that solves both of those problems is to implement the observer pattern using a notification center instead.

Notification center

A notification center is a central object that manages events for other types. We can implement a notification center for ATM withdrawals:

class ATMWithdrawalNotificationCenter {
    typealias Callback = (amount: Double) -> ()
    private var observers: [String:Callback] = [:]
    
    func trigger(amount: Double) {
        for (_, callback) in self.observers {
            callback(amount: amount)
        }
    }
    
    func addObserverForKey(key: String, callback: Callback) {
        self.observers[key] = callback
    }
    
    func removeObserverForKey(key: String) {
        self.observers[key] = nil
    }
}

With this implementation, any object can start observing by passing a unique key and callback to the addObserverForKey:callback: method. It doesn't have to have any reference to an instance of an ATM. An observer can also be removed by passing the same unique key to removeObserverForKey:. At any point, any object can trigger the notification by calling the trigger: method and all the registered observers will be notified.

If you really want to challenge yourself with advanced protocols and generics, you can try to implement a completely generic notification center that can store and trigger multiple events at once. The ideal notification center in Swift would allow any object to trigger an arbitrary event and any object to observe that arbitrary event, as long as it knows about it. The notification center should not have to know anything about any specific events. It should also allow an event to contain any type of data.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset