vendredi 26 février 2016

Command pattern with dependencies

I'm an old VB6 developer and am trying to learn some OOP methods. I am working on a Winforms application that has multiple objects that live for the life of the application, such as logging, audio, and socket connections to applications running on other machines. Certain things like a button press or socket message received can trigger actions that do things with one or more of these objects. For example, when a user clicks a Login button, I might want to log it to a file, open a socket connection, play audio to the user, and start up the microphone. It looks like the Command pattern might do what I want, but how do I pass the dependencies cleanly when there are so many? So it might look like:

Public Interface ICommand
    Sub Execute()
End Interface

Public Class LoginCommand
    Implements ICommand

    Protected _Receiver As LoginCommandReceiver

    Public Sub New(Receiver As LoginCommandReceiver)
        _Receiver = Receiver
    End Sub

    Public Sub Execute() Implements ICommand.Execute
        _Receiver.Login()
    End Sub
End Class

Public Class LoginCommandReceiver
    Protected _Logger As LogManager
    Protected _Gateway As IGatewayConnection
    Protected _Audio As IAudioPlayer
    Protected _VR As IVREngine

    Public Sub New(Logger As LogManager, Gateway As IGatewayConnection, Audio As IAudioPlayer, VR As IVREngine)
        _Logger = Logger
        _Audio = Audio
        _Gateway = Gateway
        _VR = VR
    End Sub

    Public Sub Login()
        _Logger.LogEvent("Starting login")

        _Audio.PlayRingtone()
        _Gateway.Connect()
        _VR.Start()

        _Logger.LogEvent("Login complete")
    End Sub
End Class

So now the constructor for the LoginCommandReceiver is getting messy, even in this simple example. I thought about putting the required dependencies into a container class. The constructor can then look like:

Public Sub New(Container As ApplicationContainer)
    With Container
        _Logger = .Logger
        _Audio = .Audio
        _Gateway = .Gateway
        _VR = .VR
    End With
End Sub

I actually have that container class built (without the Command pattern) but the container class is getting pretty big (almost 30 objects), and it doesn't seem right to pass that huge thing all over to classes that only need a fraction of what it contains. Service locator pattern seems appropriate but I'm reading that should be avoided.

I also thought about making each step of the process its own command, like LogCommand, PlayRingtoneCommand, GatewayConnectCommand, and VRStartCommand. Then each subcommand only needs to know about one of the application-level objects. For example, playing the ringtone only needs to know about the audio object:

Public Class PlayRingtoneCommand
    Implements ICommand

    Protected _Receiver As PlayRingtoneCommandReceiver

    Public Sub New(Receiver As PlayRingtoneCommandReceiver)
        _Receiver = Receiver
    End Sub

    Public Sub Execute() Implements ICommand.Execute
        _Receiver.PlayRingtone()
    End Sub
End Class

Public Class PlayRingtoneCommandReceiver
    Protected _Audio As IAudioPlayer

    Public Sub New(Audio As IAudioPlayer)
        _Audio = Audio
    End Sub

    Public Sub PlayRingtone()
        _Audio.PlayRingtone()
    End Sub
End Class

Now each command will have a single object in its constructor, which is cleaner. But somewhere, somebody still has to know about all the objects in order to create the list of commands. Outside of making this giant container, the only other time I have all objects instances together is at program startup when I'm wiring everything together. Do I need to create instances of all command types at that point? There could be a lot of them. Or is there some other design that will make this cleaner?

Aucun commentaire:

Enregistrer un commentaire