My thoughts about Object Oriented Programming has evolved over the years. Especially in the past 6 months. My development of MicroObjects has caused a lot of thinking about Object Oriented Programming and how to develop better and more maintainable software.
A lot of great discussions that have certainly helped drive the MicroObject concept to where it is now.
One of the practices in Object Oriented Programming is Composition over Inheritance. I don't see the emphasis a lot in discussions or what I read. Maybe I'm not reading enough, or seeing enough. I try to default to "Why am I wrong?" when I have an idea. I totally accept this as a possibility.
I appreciate any efforts to demonstrate my wrongness. :)
Being a big fan of Composition over Inheritance, I feel the need to bring up a question from a blog post I stumbled on recently.
I don't really agree with the author on interfaces being harmful; but I've also worked most of my career in interface capable langauges. I'm probably leaning a bit towards what I'm comfortable with.
I've done some C++ and faked interfaces with abstract classes; I find value in a mechanic that limits the interface to just a contract. Java goes a little overboard with their interfaces allowing too much inside of them. I like C#'s contract only form. Which is totally violated in C# 8's "Default Interface Implementations". If you need a default implementation - it's a damn abstract class.
This might get around the point of the article that you can't inherit multiple implementations; at least a little.
Either way - I'm not a fan of this. I expect it's due to my desire to use MicroObjects. :)
What actually prompted me to do this little write up is where the failing of not being able to use multiple inheritance is highlighted.
public class Subject {
private List<Observer> observers = new ArrayList<>();
private void register(Observer o) {
observers.add(o);
}
private void notify() {
for (Observer o : observers)
o.update();
}
}
public class MyWidget {...}
public class MyObservableWidget extends MyWidget, Subject {
...
}
I look at MyObservableWidget
and see an object that is a relationship. I look at this and think it should be implemented as
public class Subject implements ISubject{...}
public class MyWidget implements IWidget{...}
public class ObservableWidget implements IWidget, ISubject{
private final IWidget _widget;
private final ISubject _subject;
public ObservableWidget(IWidget widget, ISubject subject){...}
...
}
We encapsulate the relationship between IWidget
and ISubject
in an object. Doing this as a generic class allows it to work for any implementation of IWidget
and ISubject
. Not just making an ObservableMyWidget
class for the specific MyWidget
and Subject
.
While I'm not looking to pull out re-usability; I look for good object oriented design and if I can tweak it for wider usability; I'll do so. In this instance, our ObservableWidget
is an example of that. It will work for any IWidget
ISubject
combination.
The author specifically addresses the idea of having
public class MyObservableWidget extends MyWidget
hold a reference of Subject
.
Well then have MyObservableWidget hold a reference to Subject and delegate to it?
What? And duplicate the delegation code in every one of my observers? How crass. How degenerate. Ugh.
I agree that doing so is undesirable. It's crass, but I think so for a different reason. Our ObservableWidget
IS both a widget and observable. It SHOULD be treatable as both.
Holding a reference to the observable, we can't. We don't expose the contract of observability. Our consumers MUST know our type; we become tightly coupled.
With the ObservableWidget
implementing only interfaces, we don't have to duplicate code. If it tighly coupled itself to MyWidget
and Subject
then it would end up duplicating for every observed class, which is what the objection seems to be.
Using only interfaces We don't tightly couple ourselves to concrete implementations.
Having the widget and observer as collaborators; we use composition, over inheritance, to encapsulate the behavior of a relationship.
I see encapsulating the relationship as a stronger design than the tight coupling of implementation. interface
keyword or multiple inheritance - this is the design I prefer.
I don't think the interface
keyword is harmful. I think it's a restriction which forces a different way of thinking. It's our behavioral contract. There's no assumption or possible implementation (curse you C# 8) associated with it. No data expectations possible.
I'm advocating for a relationship to be represented by a new object who's single responsibility is knowing how the collaborators interact.
That's what I've shown here. This is an alternative to either violating the serparation of concerns or duplication of code.