Another example of virtual calls vs. type-checking

泪湿孤枕 提交于 2019-12-08 00:20:31

问题


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.


回答1:


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.




回答2:


The only way I can think of is to move the implementation of OnFoo and OnBar to the RcvPacketFoo and RcvPacketBar classes.

public class RcvPacket{
     public abstract void On(RcvPacketHandler eh);
}
public class RcvPacketFoo : RcvPacket
{
     public override void On(RcvPacketHandler eh){eh.OnFoo();} //OnFoo implemenation
}

public class RcvPacketBar : RcvPacket
{
     public override void On(RcvPacketHandler eh){eh.OnBar();} //OnBar implemenation
}
//Update following your comment:
public class RcvPacketHandler
{
public void OnFoo(){}
public void OnBar(){}
//....
OnDataReceived:
    RcvPacket p = RcvPacket.ReadPacket(comport);   // Call factory method
    p.On(this);


来源:https://stackoverflow.com/questions/10746417/another-example-of-virtual-calls-vs-type-checking

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!