0

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?

  • 2
    I find it hard to follow what MushyPulp is supposed to here - is there supposed to be some real world relationship that I'm supposed to already understand to get your question? What do you want when you say "MushyPulp wants to build on top of IPeelable classes"? Can you peel a MushyPulp? Is an Apple a MushyPulp? Do you get a MushyPulp by peel()ing an Apple? – Jeremy Hunt Sep 26 '19 at 10:40
  • 1
    "A MushyPulp instance in my eyes in essence is a IPeelable" This means that MushyPulp should implement IPeelable doesn't it? What is the issue? – Stop harming Monica Sep 26 '19 at 10:46
  • @JeremyHunt I tried to clarify a bit. – Vroomfondel Sep 26 '19 at 10:58
  • 2
    Sorry, I'm still not getting it. What does peeling a Peelable by x do? What does squeezing do? What do we hope to achieve by squeezing a Pulp instead of squeezing an Apple (and maybe in the process adding volume to a Pulp?) – Jeremy Hunt Sep 26 '19 at 11:20
  • 1
    @Vroomfondel Please dont transform your real problem into fruits, that just creates an XY problem. From your edit: You have producers that put out bytes. Lets call them IBytesProducer with a function ByteSequence produceBytes(). Then you have "something" that takes the bytes and constructs, for example, a datagram. Sounds to me like "something" is a simple function Datagram createDatagramFromBytes(ByteSequence) which is called like this createDatagramFromBytes(producer.produceBytes()). So the answer to what is "something"? is a simple function. – pschill Sep 26 '19 at 11:57
3

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.

| improve this answer | |
  • I added an edit to my question. May I kindly ask you to see if that changes your answer (a bit)? – Vroomfondel Sep 26 '19 at 11:44
  • 1
    Newer versions of Visual Studio will also write that boilerplate for you. There's now an "implement interface delegating from member" option when you have an unimplemented interface, which I was pleasantly surprised to see. – Eric Lippert Sep 26 '19 at 11:58
  • 1
    @Vroomfondel: No, it is a perfectly sensible feature request for the language and frankly, I was surprised when I discovered that it was not in v1.0. This pattern is common in COM -- the easiest way to implement it is via tear-off interfaces -- and I figured that a new language that was designed to be a better C++ with a better COM would have it as a language feature. Adding it was never a priority for the design team either. – Eric Lippert Sep 26 '19 at 12:27
  • 1
    @EricLippert, thanks for that tip. I didn't know that feature existed. – David Arno Sep 26 '19 at 15:25
  • 2
    @Vroomfondel, if it's of interest to you, there are issues on the C# language GitHub repo requesting this feature, eg Proposal: Allow auto implementing delegated interface. But as Eric says, there seems to be little interest amongst the language team to provide support for the pattern in C#. – David Arno Sep 26 '19 at 15:27
2

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!

| improve this answer | |
1

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.

| improve this answer | |
0

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.

| improve this answer | |
  • Sorry, I don't really understand this. IPeelable is a C# interface and thus can never have a base class. I think I goofed up with the translation to food examples. Did you read my edit? – Vroomfondel Sep 26 '19 at 12:55
  • @Vroomfondel I'm using Peelable as representing any implementation of IPeelable, or an abstract base class for IPeelable implementations, or as a concrete replacement (or single implementation) for IPeelable as a single class with injected strategies instead of having distinct classes per type of fruit - as you see fit. The detail of that is not really important. The point is for the IPeelable implementation to invoke the deliverer instead of trying to implement the deliverer as a wrapper or decorator for IPeelable, which is fundamentally the wrong model imo. – Ant P Sep 26 '19 at 13:27
  • But what you propose will mean that Peelable et al will need to use PulpDeliverer which to me looks like an inversion in the hierarchy - the interface provider must know about the interface user or the interface mediator - or do I get you wrong again? – Vroomfondel Sep 26 '19 at 14:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.