Search code examples
c#polymorphismfactory-pattern

Another example of virtual calls vs. type-checking


Problem

I swear, every time I get it pounded into my brain that I should be using virtual calls vs. type-checking (eg:

if (obj is Foo)
   ...
else if (obj is Bar)
   ...

... I come up with another example where I don't know how to implement the former.

I'm implementing a packetized protocol over a serial port. Some pseudo-code will explain this best:

OnDataReceived:
    RcvPacket p = RcvPacket.ReadPacket(comport);   // Call factory method
    if (p is RcvPacketFoo)
        OnFoo();
    if (p is RcvPacketBar)
        OnBar();

OnFoo:
    raise Foo event
OnBar:
    raise Bar event

Basically, ReadPacket is a factory method in the base class that determines the type of packet being received, and passes the buffer to the correct derived-type constructor. After this, I need to raise an event, depending on the type of packet. How can I do this without using the is operator? Is my method sound/sane?


Solution

The Visitor Pattern, of course! Thanks Pablo Romeo.

In this case, I made the controller, that is calling the factory method, is the visitor. My result:

public interface IPacketHandler {
    void Handle(FooPacket p);
    void Handle(BarPacket p);
}

public class Controller : IPacketHandler {
    OnDataReceived() {
        RcvPacket p = RcvPacket.ReadPacket(comport);   // Call factory method
        p.Handle(this);        // *** Virtual Call: The first dispatch ***
    }

    // Note the use of explicit interface implementation here.
    IPacketHandler.Handle(FooPacket p) {
       OnFoo();
    }
    IPacketHandler.Handle(BarPacket p) {
       OnBar();
    }
}

public abstract class RcvPacket {
    public static RcvPacket ReadPacket(...) { ... }   // Factory method
    public void Handle(IPacketHandler handler);
}
public class FooPacket : RcvPacket {
    public override void Handle(IPacketHandler handler) {
       handler.Handle(this);        // *** Overloaded call: The second dispatch ***
    }
}
public class BarPacket : RcvPacket {
    public override void Handle(IPacketHandler handler) {
       handler.Handle(this);        // *** Overloaded call: The second dispatch ***
    }
}

The fun thing here, is that by explicitly implementing the visitor interface, the Handle calls are essentially hidden. From MSDN:

A class that implements an interface can explicitly implement a member of that interface. When a member is explicitly implemented, it cannot be accessed through a class instance, but only through an instance of the interface.


Solution

  • Well, considering that you probably don't want much additional logic inside each packet, you can accomplish it through Double Dispatch.

    In that case, I'd probably create an interface, such as:

    public interface IPacketEvents 
    {
        void On(Foo foo);
        void On(Bar bar);
    }
    

    I'm assuming you have a base class for all packets. In it I would declare:

    public abstract void RaiseUsing(IPacketEvents events);
    

    And each subclass of package would implement the following:

    public override void RaiseUsing(IPacketEvents events) 
    {
        events.On(this);
    }
    

    You could either have a new class implement the IPacketEvents or that same class from where you call the factory could implement it. In that second case, your call would end up as:

    OnDataReceived:
        RcvPacket p = RcvPacket.ReadPacket(comport);   // Call factory method
        p.RaiseUsing(this);
    

    Using this type of dispatch, what you get, is that each type, calls the corresponding method, because it "knows" which one to call. It may confuse a bit that I used the same "On" name for all methods, but that's not entirely necessary. They could be OnFoo() and OnBar().

    This type of behavior is also used in the Visitor pattern.