OO-question re: interfaces and is-a vs. has-a
https://softwareengineering.stackexchange.com/questions/398929
-
02-03-2021 - |
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 byte
s sequentially (they implement the interface IPeelable
). And I want to have a more complex "something" that can put out byte
s sequentially but also emit whole datagrams, costructed from a sequence of those byte
s, depending on the type of data I am asking from it. So either a spoon- cup- or plateful of mushy pulp (byte
s 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?
Solution
A
MushyPulp
instance in my eyes in essence is aIPeelable
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 privateIPeelable
instance inMushyPulp
and reroute allIPeelable
methods ofMushyPulp
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.