Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Designing Software with Swift (Part V): Wrapping Up

DZone's Guide to

Designing Software with Swift (Part V): Wrapping Up

Take a look at the final part of this 5-part Swift series with Christopher Lamb. Be sure to check out parts 1-4 if you haven't already!

· Mobile Zone
Free Resource

Discover how to focus on operators for Reactive Programming and how they are essential to react to data in your application.  Brought to you in partnership with Wakanda

Haven't read parts I, II, III, or IV yet? Be sure to click the links and check them out!

Essentially, the question is whether we should use type correctness, functional parameters, or protocols, or all three. If all three, what would that look like, and why? Well, first, the Type-based approach has the most intrinsic reuse. We not only used five different types of observers, all compliant with the defined observer type, but we could also easily use the system as a whole with any number of different message types. Protocol approaches have the most dynamic reuse, and they’re easier to make forward-compatible. If I have an observing system in place that I’ve already built, and it’s large enough I don’t want to replace it but I do need another messaging type, it’s better practice to implement a messaging protocol on a new messaging type than to inherit from a previous messaging type. The type-based system was also the most terse.

The Observer pattern was originally designed the way it was to support procedural object-oriented systems, not functional ones. The Observer interface is really a function, with a specific type. The use of an interface type stems from the environment it was developed in, rather than it being the best solution. Remember, even as early as GoF, engineers recognized that patterns existed to work around shortcomings in existing development environments.

The Type-based solution really describes the system the best. We need a first-order functional type, and that’s that Swift gives is the ability to describe.

So, what could an Observer implementation look like using Protocols and Generics?

public protocol Notifier {
  typealias KeyType
  typealias ObsType
  mutating func attach(key: KeyType, observer: ObsType)
  mutating func detach(key: KeyType)
}

public protocol Event {
  init(message: String)
  var message: String { get }
}


Here, we don’t define an observer type. We don’t need to. We use a function definition via functional typing in the generic class later. We do however define a common event protocol that can be extended for new message types. We also define the mutating functions in the Notifier protocol to enable protocol implementation via Structures, not just classes.

public class SimpleEvent : Event {

  public var message : String

  public required init(message: String) {
    self.message = message
  }
}


In this example, we define a SimpleEvent type for demonstration purposes. It implements the required constructor from the Event protocol as well.

public final class Subject<K: Hashable, T: Event> : Notifier {

  private var observers : [K: (T) -> Void] = [:]

  private var data : String?

  public var state : String? {
    get {
      return data
    }
    set (state) {
      data = state
      if let mydata = data { notify(mydata) }
    }
  }

  public func attach(key: K, observer: (T) -> Void) {
    observers[key] = observer
  }

  public func detach(key: K) {
    observers.removeValueForKey(key)
  }

  private func notify(data: String) {
    let msg = T(message: data)
    observers.forEach { $1(msg) }
  }
}


An implementation of the Notifier protocol.

func hook(evt: Event) {
  print("Received event, message: \(evt.message)")
}


An observing function that can receive notifications. This is a standalone function, but it could be implemented as a closure or class method too.

let subject = Subject<String,SimpleEvent>()
subject.attach("hook", observer: hook)
subject.state = "new state"


Exercising the code - we see “Received event, message: new state” on the console. Good news! So what design elements did we bring together here? First, we used a protocol to define the Notifier interface—this gives us more implementation flexibility. This is implemented as a single class, but there’s no reason you couldn’t implement this with the Bridge pattern. A protocol makes that easy to do, and easy to create different Subject types in the future. Second, we implemented the observer callback as a function type. This is the right way to do this—using an interface for this kind of thing was a workaround for programming languages that lacked appropriate type description capabilities. Swift has these capabilities, so we should use them. Third, we defined an Event protocol. We likely want to use richer semantics than those afforded us by simple strings, and defining Events like this makes that kind of richness possible.

Swift provides a wide range of design primitives for modern software development. We’ve scratched the surface of two of the more important here—Generics and Protocols. And we’ve used them to implement the Observer pattern in a slightly different way than developers usually do. Swift isn’t perfect, but it does have a wide range of design techniques we can use.

Learn how divergent branches can appear in your repository and how to better understand why they are called “branches".  Brought to you in partnership with Wakanda

Topics:
swift ,design ,pattern

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}