DEV Community

Stoian Dan
Stoian Dan

Posted on • Updated on

Swift's POP (Protocol Oriented Programming) paradigm in rea life

Introduction

Imagine we want to implement a sort of postal system, where messages can be sent from someone to the post office; where it will be accordingly dispatched to the right destination.

Beginning

We could go ahead and define a simple data model, that represents a message.
This entity holds the actual message (we'll call it content), and some meta-data, i.e. data about the message itself.

Since we want this message to be immutable and we don't want shared references of it, we can make it a struct, as opposed to a class:

struct Message {
   // the meta-data; who is sending this message
   let sender: String
   // also meta-data; the receiver of the message
   let to: String 
   // the actual content of the message
   let content: String
}
Enter fullscreen mode Exit fullscreen mode

The Dispatcher

Now that we have a data model, which will be a shared entity between sender (a person) and dispatcher (the postal office).
We can go ahead and imagine our dispatcher:

class Dispatcher {
    public func dispatch(_ message: Message) {
       print("Sending \(message.content) to \(message.to) from \(message.from)")
    }
}
Enter fullscreen mode Exit fullscreen mode

The Sender

Finally let's imagine the person sending the message:

class Person {
     private unowned let dispatcher: Dispatcher
     let name: String
     init(dispatcher: Dispatcher, ) {
        self.dispatcher = dispatcher
        self.name = name
     }

     public func sendMessage(of message: Message) {
         dispacher.dispatch(message)
   }
}
Enter fullscreen mode Exit fullscreen mode

Example

This looks ok. Let's see how it will look in practice:

    let dispatcher = Dispatcher()
    let helvidius = Person(dispatcher: dispatcher, name: "Helvidius")
    let jovinian = Person(dispatcher: dispatcher, name: "Jovinian")

   helvidius.sendMessage(of: Message(sender: helvidius.name, to: "Jovinian", content: "Hi Jovinian! How are you?"))
Enter fullscreen mode Exit fullscreen mode

The actual problem

Notice however, it's a bit odd that Bob has to specify he's the sender.
A person has a name, and if a person sends a message, he's always going to be the sender.
In fact, the current code could be exploited, supposing we'd offer anyone the ability to send messages (i.e. Person class would be a public API):

    let dispatcher = Dispatcher()
    let evilMike = Person(dispatcher: dispatcher, name: "Evil Mike")    
   evilMike.sendMessage(of: Message(sender: "Andy", to: "Sandy", content: "Hi Sandy! I'm Andy, and I think you look ugly!"))
Enter fullscreen mode Exit fullscreen mode

The solution!

Protocols could make our lives much more easy. To start, we've noticed every person has a name; it also s seems maybe not just persons can send messages. why not have machines be able to send messages as well? All we need after all, besides the content and receiver, is for the sender to identify himself.
We could use Swift's UUID type, and that would be a great idea! However, for the sake of simplicity, well stick to a name field, of type string:

protocol Sender {
   var name: String { get }
}
Enter fullscreen mode Exit fullscreen mode

Great! Now we could have a ton of other types that can be senders, like machines:

class Machine: Sender {
   let name: String
   let dispacher: Dispatcher

   init(name: String, dispathcer: Dispathcer) {
       self.name = name
       self.dispathcer = dispatcher
   }

   public sendHappyNewYear() {
       let date = Date.now

       let components = Calendar.current.dateComponents([.month, .day], from: date)

         if components.day == 1 && components.month == 1 {
                dispatcher.dispatch(Message(sender: "Robot1", to: "Sandy", content: "Happy new Year Sandy!"))
         }

   }
}
Enter fullscreen mode Exit fullscreen mode

However, the actual problem still remains, just about anyone can create a message and pretend to be someone else!
So here's where POP (Protocol Oriented Programming) comes more handy in place.

We can make the initializer (or constructor, if you will) of Message entity fileprivate!

struct Message {
   // the meta-data; who is sending this message
   let sender: String
   // also meta-data; the receiver of the message
   let to: String 
   // the actual content of the message
   let content: String

   fileprivate init(sender: String, to: String, content: String) {
      self.sender = sender
      self.to = to
      self.content = content
   }
}
Enter fullscreen mode Exit fullscreen mode

Great, now we can define an extension method on the Sender protocol. That is a method that anyone who's a sender, can automatically benefit from, even retroactively! I.e. entities that already conform to the Sender protocol before the extension method is introduced can benefit from!:

struct Message {
   ...
}

extension Sender {
   func createMessage(to receiver: String, content message: Message) -> Message {
    Message(sender: self.name, to: receiver, content: message)
}
}

Enter fullscreen mode Exit fullscreen mode

Now any sender can call the createMessage method, that will automatically use the name the Sender has, because remember, the only thing we know about a Sender is that he has a name, and we can make use of that name in protocol extensions.

Top comments (0)