Question

Disclaimer: the following is written without further distinction in the object oriented terminology as C# is implementing it: classes, interfaces and instances, etc.

Let's assume the following scenario: there are different classes Apple, Avocado and Egg with no shared inheritance hierarchy except for Object at the top. All those classes implement the interface IPeelable which obviously makes sense for all three. Each of the IPeelable implementations of course is using methods specific to their respective class.

The class MushyPulp wants to build on top of IPeelable classes but needs to bring its own member variables like float Volume. MushyPulp can e.g. increase in volume if you first peel a little more and then squeeze a little more, where squeeze is introduced only in MushyPulp and independent of the exact substance that is squeezed. A MushyPulp instance in my eyes in essence is a IPeelable object. My problem: I somehow don't understand how to define this relationship in C#. The only way I could make it work is to have a private IPeelable instance in MushyPulp and reroute all IPeelable methods of MushyPulp through to this object - the code of course is a entirely boiler-plate indirection.

From a technical perspective it looks like what I want would be multiple inheritance in disguise - is there a way to have that, restricted to interfaces (somehow) in C#?

EDIT: My creative translation of my actual programming problem towards a food based example didn't work out so well. I rephrase my question a bit towards more technical terms:

I have a range of otherwise completely unrelated producer classes which can put out bytes sequentially (they implement the interface IPeelable). And I want to have a more complex "something" that can put out bytes sequentially but also emit whole datagrams, costructed from a sequence of those bytes, depending on the type of data I am asking from it. So either a spoon- cup- or plateful of mushy pulp (bytes interpreted and transformed, e.g. type cast to a float) or a byte of peeled substance. The source of the substance is understandably anonymous beyond the IPeelable interface. The question is, what is the "something"? A class with a member of an IPeelable capable class as for now, but is that the final word on OO here?

Was it helpful?

Solution

A MushyPulp instance in my eyes in essence is a IPeelable object. My problem: I somehow don't understand how to define this relationship in C#. The only way I could make it work is to have a private IPeelable instance in MushyPulp and reroute all IPeelable methods of MushyPulp through to this object - the code of course is a entirely boiler-plate indirection.

What you are describing is the delegation pattern: type A delegates the handling of functionality to a private instance of type B. And you are correct that C# doesn't directly support the delegation pattern and instead requires you to write boilerplate code along the lines of:

public void peel(float x) => _peelable.peel(x);

There are tools, such as ReSharper, that support generating that boilerplate code for you, but you still end up with that boilerplate code.

Another option is to expose _peelable via a property in MushyPulp:

public IPeelable PulpProvidingObject { get => _peelable; }

though you then risk running into conflicts with the Law of Demeter.

Another alternative is to rethink those "is a " and "has a" relationships. A peelable item has a mushy pulp. So maybe your solution should be to have the IPeelable interface provide access to the MushyPulp instance that results from peeling and squeezing it.

OTHER TIPS

This can be handled using only interfaces, only abstract classes, or a combination of both interfaces and abstract classes. The two are not mutually exclusive. Which approach you use depends greatly on both how these classes will be used as well as personal preference (or company standards).

In addition, interfaces can inherit from other interfaces. Here is an example of using both:

    public interface IPeelable
    {
        bool IsTropical { get; set; }
        string PreferredTool { get; set; }
        void Peel();
    }
    public interface IMushyPulp : IPeelable
    {
        double Volume { get; set; }
        void Squeeze();
    }

    public abstract class Peelable : IPeelable
    {
        public bool IsTropical { get; set; }
        public string PreferredTool { get; set; }
        public abstract void Peel();
    }

    public class Apple : Peelable
    {
        public override void Peel() => throw new NotImplementedException();
    }

    public abstract class MushyPulp : Peelable, IMushyPulp
    {
        public double Volume { get; set; }
        public abstract void Squeeze();
    }

    public class Orange : MushyPulp
    {
        public override void Peel() => throw new NotImplementedException();
        public override void Squeeze() => throw new NotImplementedException();
    }

Note: In this example IMushyPulp inherits from IPeelable - but that isn't necessary for this example to work. It can, however, make using the IMushyPulp interface easier to use elsewhere.

Hope this helps!

You could have an interface and an abstract class

public interface IPeelable
{
    void A();
    void B();
}

public sealed class MushyPulp : IPeelable
{
    // Custom versions
    void IPeelable.A() {
    }

    // Custom versions
    void IPeelable.B() {
    }
}

You can look at your objects as IPeelable and when you call A or B methods the mushie ones will have the custom version of those methods.

It seems to me that the real issue here is a single responsibility problem. You have two distinct concepts, one represents a type of pulp and the other how it is delivered. So what you really want - if I understand correctly - is an instance method on your Peelable that is something like this:

void Peel(PulpDeliverer deliverer)
{
    deliverer.AddPulp(this.Pulp);
}

Where PulpDeliverer might be Spoon or Plate or whatever.

Whether this method lives on an abstract base class for your Peelable or whether you even need inheritance (maybe a single Peelable class with injected strategies and factory methods for each type of fruit?) is a separate issue.

Licensed under: CC-BY-SA with attribution
scroll top