How to expose different functionality of a single type to different modules?

71 Views Asked by At

I'm working on a small 2-player card game app for iOS. For now, I've separated this app into 3 modules.

  • UI
  • Game logic
  • Networking

I have two pubic protocols in the networking module:

public protocol ConnectionManager {
    init(name: String)
    var peers: Property<[String]> { get }
    func invitePeer(at index: Int)
    func respond(accept: Bool)
    var invitation: Signal<String, NoError> { get }
    var response: Signal<Bool, NoError> { get }
}

public protocol MessageTransmission {
    func send(_ data: Data)
    var data: Signal<Data, NoError> { get }
}

and there is actually only one concrete implementation which conforms to both two protocols. Lets' say:

class Foo: ConnectionManager, MessageTransmission {
    // ... codes omitted
}

The UI module receives a name from the user and uses it to initialize a Foo. Then it displays the nearby players according to the peers property. When a user commits an invitation to start a new game, UI module forwards this request to the ConnectionManager and the ConnectionManager handles those dirty works.

For the game logic module, it only cares about message transmission, but the transmission depends on the previous "invite-respond" step because we need a target to exchange message with. (the target related concepts are encapsulated, so the game logic only knows there is a thing that it can send message to and receive message from.)

My thought is: once a session is established, i.e., once an invitation is responded with true, the UI module initializes a game logic thing (maybe an instance of a type Game) and passes the ConnectionManager (although UI module initializes an instance of type Foo, it stores that instance as of type ConnectionManager) to it. But the problem is, the ConnectionManager has nothing to do with MessageTransmission when looked from the outside even if they're implemented by a single type internally.

  • I can do a force case at either side, but it looks tricky.
  • I can combine those two protocols, but then the UI module and the game logic module interacts with a surplus interface (interface that has more features than needed).

Which path should I take? or is there any better way to do this?

ps: The Foo can only be initialized by UI module because it's the only one who knows about the name. And it's also necessary to pass the same instance to the game logic module because there are some internal states (the target related thing) which is modified in the previous interaction with UI module and effects the later interaction with game logic module. I can't think of another way to do this.

1

There are 1 best solutions below

0
On

As I understood from your explanation you need to pass MessageTransmition implementation to game logic but do not want to pass ConnectionManager.

As soon as instance of MessageTransmission depends on result of invitation (or accepting invite) made by ConnectionManager you need to build object conformed to MessageTransmition from within ConnectionManager and pass to game logic. Yes, that means that you couple these two entities but they will have clean responsibilities in this case.

Consider this kind of protocol:

protocol ConnectionManager {
  func respond(accept: Bool) -> Signal<MessageTransmition, Error>
}

respond() method accepts invitation and builds MessageTransmission conforming object on success or returns error if accepting failed or if invitation declined. This object you can pass to game logic.