How to encapsulate functionality with protocols?

Suppose I have a struct called Person defined as such:

struct Person {
     let name: String
}

And now suppose I have a protocol called Greeter, that can greet a person:

protocol Greeter {
      func greetPerson(of person: Person)
}

Well, the thing is now, whoever will implement this Greeter won't necessarily implement what I had in mind, namely:

      func greetPerson(of person: Person) {
                  print("Hello there \(person.name)!")
       }

One can just as easly say:

     func greetPerson(of person: Person) {
                 print("Bip bop!")
      }

So to solve this, I'm used to do something like:

protocol Greeter {
      var greet: (Person) -> Void { get }
}

And then inject the function myself when I'm creating instances that implement Greeter so that I can make absolutely sure, I don't leak information from the Person class (this is just an example that's not complex). Like:

class PersonGreeter: Greeter {
    let greet: (Person) -> Void

    init(greeter: @escaping (Person) -> Void) {
               self.greet = greeter
    }
}
struct Person {
     let name: String

    func makeGreeter() -> Greeter {
              PersonGreeter(greeter: { person in print("Hi there '(person.name!)") } )
    }
}

But I feel I'm going against the current here. Am I doing something wrong is there a better way to achieve this?

I'm a little confused by this example, as the point of the Greeter protocol seems to be that anyone can conform to it implement greet as they choose, which kind of feels like the point of a protocol. If you could give an example that's closer to a reality where this would make more sense that would be great.

1 Like

Hi @mattcurtis!
You're right! And yes I can. In my IRC client project. I have an IRCServer class that can send(message: String) messages to the server.
I also have a Room class, representing an IRC channel/room.
I want the Room to be able to send messages to the server, but only in the format of the following pattern:
server.send(message: "PRIVMSG \(room.name) :\(messageRoomWantsToSend)")
I would not like the room to be able to simply send any message it likes.
Now sure, I could transform the method from send(message: String) to send(message: String, as room: Room), but see that would still not be enough, because now someone could gain access to a reference to a room class, and pretend to be the room.
So my solution was this:

createRoom(named name: String) -> Room {
    Room(name: name, senderCallback: {message in server.send(message: "PRIVMSG \(room.name)  :\(message)"))})
}

But I was thinking maybe I cal solve the problems with PoP, and protocols, somehow.

I think in your example it would still be possible for someone to pretend to be the room — all they'd need to do is take a copy of senderCallback and invoke that at their will. The moment you give the consumer the ability to send messages you open yourself up to that messaging ability being misused. You could do other things, like tracking what rooms are allowed to send messages, or inverting way messaging sending is initiated so that rather than your rooms sending messages, they queue messages, and the server polls the valid list of rooms it knows about occasionally to see if they have pending messages, then sends them. But there are similar issues with even that approach.

At the end of the day though, it's usually not worth the effort to code in a way that assumes users are likely to act in such undesirable ways, unless the repercussions of them doing so are catastrophic (like in a high-security context.)

1 Like