Skip to main content
added 857 characters in body
Source Link
Theraot
  • 9.2k
  • 2
  • 28
  • 35

StreamVehicle example

I will take the exampleI am writing about developers who tries to things the dumb way, for narrative purposes

Let us go for a variant of the classical examples that some OOP courses use… We have a StreamVehicle class. This class allows you to read, then we derive Car, Airplane, Balloon and write byte arrays from itShip.

Note: If you want to provide the ability to read and write custom struct without losing your abilityneed to read and write byte arrays, you may consider inheriting from it. If every developer doesground this example, it will lead to different classes to do different operations on thepretend these are kinds of objects in a video game.

Then StreamCar, and no way to reconcile them… if a developer wants to do operation XAirplane may have some common code, because they both can roll on wheel on land. The developers may consider creating an intermediary class in the inheritance chain for that. Yet, actually there is provided byalso some shared code between StreamXAirplane and operation Y that is provided by StreamYBalloon. They could consider creating another intermediary class in the inheritance chain for that.

Thus, the developer will startwould be looking for multiple inheritance. When that happensAt the point where the developers are looking for multiple inheritance, thingsthe design has already gone wrong.

Therefore, inheritance is not a good way to extend behavior. It would beis better to provide some form of utility methods. If we plan to use it to extendmodel this behavior as interfaces and statecomposition, so we should consider composition.

Let us say you did usecan reuse it without having to run into multiple class inheritance anyway. Now you have no way to decouple your code from If the developers, for example, create the StreamFlyingVehicule implementationclass. Disregarding the case whereThey would be saying that StreamAirplane changes, this can still cause trouble. In particular, if you want to test your code without creating an actualis a StreamFlyingVehicule, you cannot mock it, because it is in your (class inheritance chain.

Sometimes when people say "Prefer composition over inheritance", they actually mean to use composition and other things… For example), if I create classbut we could instead say that uses a StreamAirplane member, I have lost the ability to replacehas a SteamFlying object with one of mine. Given the situation, it can be better to use ancomponent IStream member(composition) and implement IStreamAirplane. Where is a IStreamIFlyingVehicule is an interface※(interface inheritance). This way

Using interfaces, if necessary, we can have multiple inheritance of(of interfaces is possible). In addition, you are not coupling to a particular implementation. Increasing reusability and testability of your code.

※: This also allows having multiple types of IStream. For example, one could read from a network socket, another from file system, and another could just store in memory. I am aware this could also work with an abstract class. As I said earlier, inheritance has its own merits. This is about situations that are better with composition.

Remember that inheritance is a tool for polymorphism. In addition, polymorphism is a tool for reusability. If you can increase the reusability of your code by using composition instead of inheritance, then do so. If you are not sure whatever or not composition provides better reusability, "Prefer composition over inheritance" is a good heuristic.

All that without mentioning Amphibious.

In fact, we may not need things that go off the ground. Stephen Hurn has a more eloquent example in his articles “Favor Composition Over Inheritance” part 1 and part 2.

Vehicule exampleSubstitutability and Encapsulation

Let us go for a variant of the classical examples that some OOP courses use… We have aShould VehicleA class, then we deriveinherit or compose CarB,?

If AirplaneA, Is an specialization of BalloonB andthat should fulfil the ShipLiskov substitution principle, inheritance is viable, even desirable. ThenIf there are situations where CarA andis not a valid substitution for AirplaneB may have some common codethen we should not use inheritance.

We might be interested in composition as a form of defensive programming, because they both can roll on wheel on landto defend the derived class. WeIn particular, once you start using B for other different purposes, there may consider creating an intermediary class in the inheritance chainbe pressure to change or extend it to be more suitable for thatthose purposes. Yet, actuallyIf there is also some shared code betweenthe risk that AirplaneB andmay expose methods that could result in an invalid state in BalloonA we should be using composition instead of inheritance.

Note: If you need to ground this example, pretend these Even if we are kindsthe author of objects in a video game.

It is better to model this behavior as interfacesboth B and compositionA, so we can reuse it without havingis one thing less to run into multiple class inheritance. If we, for exampleworry about, createtherefore composition eases the reusability of FlyingVehiculeB class. 

We would be sayingmay even argue that if there are features in AirplaneB is athat FlyingVehiculeA does not need (class inheritance), butand we do not know if those features could also say that Airplane has aresult in an invalid state for FlyingA component (composition, either in the present implementation or in the future) and Airplane, it is a good idea to use composition instead of inheritance.

Composition also has the advantages of allowing switching implementations, and easing mocking.

Note: there are situations where we want to use composition despite the substitution being valid. We archive that IFlyingVehiculesubstitutability by using interfaces or abstract classes (interface inheritancewhich one to use when is another topic) and then use composition with dependency injection of the real implementation.

AllFinally, of course, there is the argument that without mentioningwe should use composition Amphibiousto defend the parent class because inheritance breaks the encapsulation of the parent class:

Inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation'

-- Design Patterns: Elements of Reusable Object-Oriented Software, Gang of Four

Well, that is a poorly designed parent class. Which is why you should:

Design for inheritance, or prohibit it.

-- Effective Java, Josh Bloch

You can resolve this by using a form of the adapter pattern. Which is again the same situation I have been talking about, for example: Your class C will not inherit from class B. Instead your class C will have a member of type A, which may or may not be (or have) an object of type B. This way you will not be programming against the implementation detail of B, but against the contract that the interface (of) A offers.

AddendumAddendum: There some other ways one could allow extending ORM objects. Thus, I do not think inheritance is necessary on this case. It is cheaper.

In addition of what I said above, consider that many times we only want to override a few methods and leave everything else with the default implementations. Had we been using composition we would have to create all those methods, even if only to delegate to the wrapped object.

Stream example

I will take the example of a Stream class. This class allows you to read and write byte arrays from it.

If you want to provide the ability to read and write custom struct without losing your ability to read and write byte arrays, you may consider inheriting from it. If every developer does this, it will lead to different classes to do different operations on the Stream, and no way to reconcile them… if a developer wants to do operation X that is provided by StreamX and operation Y that is provided by StreamY that developer will start looking for multiple inheritance. When that happens, things has already gone wrong.

Therefore, inheritance is not a good way to extend behavior. It would be better to provide some form of utility methods. If we plan to use it to extend behavior and state, we should consider composition.

Let us say you did use inheritance anyway. Now you have no way to decouple your code from the Stream implementation. Disregarding the case where Stream changes, this can still cause trouble. In particular, if you want to test your code without creating an actual Stream, you cannot mock it, because it is in your inheritance chain.

Sometimes when people say "Prefer composition over inheritance", they actually mean to use composition and other things… For example, if I create class that uses a Stream member, I have lost the ability to replace a Steam object with one of mine. Given the situation, it can be better to use an IStream member and implement IStream. Where IStream is an interface※. This way, if necessary, multiple inheritance of interfaces is possible. In addition, you are not coupling to a particular implementation. Increasing reusability and testability of your code.

※: This also allows having multiple types of IStream. For example, one could read from a network socket, another from file system, and another could just store in memory. I am aware this could also work with an abstract class. As I said earlier, inheritance has its own merits. This is about situations that are better with composition.

Remember that inheritance is a tool for polymorphism. In addition, polymorphism is a tool for reusability. If you can increase the reusability of your code by using composition instead of inheritance, then do so. If you are not sure whatever or not composition provides better reusability, "Prefer composition over inheritance" is a good heuristic.

Vehicule example

Let us go for a variant of the classical examples that some OOP courses use… We have a Vehicle class, then we derive Car, Airplane, Balloon and Ship. Then Car and Airplane may have some common code, because they both can roll on wheel on land. We may consider creating an intermediary class in the inheritance chain for that. Yet, actually there is also some shared code between Airplane and Balloon.

Note: If you need to ground this example, pretend these are kinds of objects in a video game.

It is better to model this behavior as interfaces and composition, so we can reuse it without having to run into multiple class inheritance. If we, for example, create the FlyingVehicule class. We would be saying that Airplane is a FlyingVehicule (class inheritance), but we could also say that Airplane has a Flying component (composition) and Airplane is a IFlyingVehicule (interface inheritance).

All that without mentioning Amphibious.

You resolve this by using a form of the adapter pattern. Which is again the same situation I have been talking about: Your class C will not inherit from class B. Instead your class C will have a member of type A, which may or may not be (or have) an object of type B. This way you will not be programming against the implementation detail of B, but against the contract that the interface (of) A offers.

Addendum: There some other ways one allow extending ORM objects. Thus, I do not think inheritance is necessary on this case.

In addition of what I said above, consider that many times we only want to override a few methods and leave everything else with the default implementations. Had we been using composition we would have to create all those methods, even if only to delegate to the wrapped object.

Vehicle example

I am writing about developers who tries to things the dumb way, for narrative purposes

Let us go for a variant of the classical examples that some OOP courses use… We have a Vehicle class, then we derive Car, Airplane, Balloon and Ship.

Note: If you need to ground this example, pretend these are kinds of objects in a video game.

Then Car and Airplane may have some common code, because they both can roll on wheel on land. The developers may consider creating an intermediary class in the inheritance chain for that. Yet, actually there is also some shared code between Airplane and Balloon. They could consider creating another intermediary class in the inheritance chain for that.

Thus, the developer would be looking for multiple inheritance. At the point where the developers are looking for multiple inheritance, the design has already gone wrong.

It is better to model this behavior as interfaces and composition, so we can reuse it without having to run into multiple class inheritance. If the developers, for example, create the FlyingVehicule class. They would be saying that Airplane is a FlyingVehicule (class inheritance), but we could instead say that Airplane has a Flying component (composition) and Airplane is a IFlyingVehicule (interface inheritance).

Using interfaces, if necessary, we can have multiple inheritance (of interfaces). In addition, you are not coupling to a particular implementation. Increasing reusability and testability of your code.

Remember that inheritance is a tool for polymorphism. In addition, polymorphism is a tool for reusability. If you can increase the reusability of your code by using composition, then do so. If you are not sure whatever or not composition provides better reusability, "Prefer composition over inheritance" is a good heuristic.

All that without mentioning Amphibious.

In fact, we may not need things that go off the ground. Stephen Hurn has a more eloquent example in his articles “Favor Composition Over Inheritance” part 1 and part 2.

Substitutability and Encapsulation

Should A inherit or compose B?

If A Is an specialization of B that should fulfil the Liskov substitution principle, inheritance is viable, even desirable. If there are situations where A is not a valid substitution for B then we should not use inheritance.

We might be interested in composition as a form of defensive programming, to defend the derived class. In particular, once you start using B for other different purposes, there may be pressure to change or extend it to be more suitable for those purposes. If there is the risk that B may expose methods that could result in an invalid state in A we should be using composition instead of inheritance. Even if we are the author of both B and A, it is one thing less to worry about, therefore composition eases the reusability of B. 

We may even argue that if there are features in B that A does not need (and we do not know if those features could result in an invalid state for A, either in the present implementation or in the future), it is a good idea to use composition instead of inheritance.

Composition also has the advantages of allowing switching implementations, and easing mocking.

Note: there are situations where we want to use composition despite the substitution being valid. We archive that substitutability by using interfaces or abstract classes (which one to use when is another topic) and then use composition with dependency injection of the real implementation.

Finally, of course, there is the argument that we should use composition to defend the parent class because inheritance breaks the encapsulation of the parent class:

Inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation'

-- Design Patterns: Elements of Reusable Object-Oriented Software, Gang of Four

Well, that is a poorly designed parent class. Which is why you should:

Design for inheritance, or prohibit it.

-- Effective Java, Josh Bloch

You can resolve, for example: Your class C will not inherit from class B. Instead your class C will have a member of type A, which may or may not be (or have) an object of type B. This way you will not be programming against the implementation detail of B, but against the contract that the interface (of) A offers.

Addendum: There some other ways one could allow extending ORM objects. Thus, I do not think inheritance is necessary on this case. It is cheaper.

In addition, consider that many times we only want to override a few methods and leave everything else with the default implementations. Had we been using composition we would have to create all those methods, even if only to delegate to the wrapped object.

Source Link
Theraot
  • 9.2k
  • 2
  • 28
  • 35

"Prefer composition over inheritance" is just a good heuristic

You should consider the context, as this is not a universal rule. Do not take it to mean never use inheritance when you can do composition. If that were the case, you would fix it by banning inheritance.

I hope to make this point cleared along this post.

I will not try to defend the merits of composition by itself. Which I consider off topic. Instead, I will talk about some situations when developer may consider inheritance that would be better using composition. On that note, inheritance has its own merits, which I also consider off topic.


Stream example

I will take the example of a Stream class. This class allows you to read and write byte arrays from it.

If you want to provide the ability to read and write custom struct without losing your ability to read and write byte arrays, you may consider inheriting from it. If every developer does this, it will lead to different classes to do different operations on the Stream, and no way to reconcile them… if a developer wants to do operation X that is provided by StreamX and operation Y that is provided by StreamY that developer will start looking for multiple inheritance. When that happens, things has already gone wrong.

Therefore, inheritance is not a good way to extend behavior. It would be better to provide some form of utility methods. If we plan to use it to extend behavior and state, we should consider composition.

Let us say you did use inheritance anyway. Now you have no way to decouple your code from the Stream implementation. Disregarding the case where Stream changes, this can still cause trouble. In particular, if you want to test your code without creating an actual Stream, you cannot mock it, because it is in your inheritance chain.

Sometimes when people say "Prefer composition over inheritance", they actually mean to use composition and other things… For example, if I create class that uses a Stream member, I have lost the ability to replace a Steam object with one of mine. Given the situation, it can be better to use an IStream member and implement IStream. Where IStream is an interface※. This way, if necessary, multiple inheritance of interfaces is possible. In addition, you are not coupling to a particular implementation. Increasing reusability and testability of your code.

※: This also allows having multiple types of IStream. For example, one could read from a network socket, another from file system, and another could just store in memory. I am aware this could also work with an abstract class. As I said earlier, inheritance has its own merits. This is about situations that are better with composition.

Remember that inheritance is a tool for polymorphism. In addition, polymorphism is a tool for reusability. If you can increase the reusability of your code by using composition instead of inheritance, then do so. If you are not sure whatever or not composition provides better reusability, "Prefer composition over inheritance" is a good heuristic.


Vehicule example

Let us go for a variant of the classical examples that some OOP courses use… We have a Vehicle class, then we derive Car, Airplane, Balloon and Ship. Then Car and Airplane may have some common code, because they both can roll on wheel on land. We may consider creating an intermediary class in the inheritance chain for that. Yet, actually there is also some shared code between Airplane and Balloon.

Note: If you need to ground this example, pretend these are kinds of objects in a video game.

It is better to model this behavior as interfaces and composition, so we can reuse it without having to run into multiple class inheritance. If we, for example, create the FlyingVehicule class. We would be saying that Airplane is a FlyingVehicule (class inheritance), but we could also say that Airplane has a Flying component (composition) and Airplane is a IFlyingVehicule (interface inheritance).

All that without mentioning Amphibious.


Yo-Yo problem

Another case where composition helps is the Yo-Yo problem. This is a quote from Wikipedia:

In software development, the yo-yo problem is an anti-pattern that occurs when a programmer has to read and understand a program whose inheritance graph is so long and complicated that the programmer has to keep flipping between many different class definitions in order to follow the control flow of the program.

You resolve this by using a form of the adapter pattern. Which is again the same situation I have been talking about: Your class C will not inherit from class B. Instead your class C will have a member of type A, which may or may not be (or have) an object of type B. This way you will not be programming against the implementation detail of B, but against the contract that the interface (of) A offers.


Counter examples

Many frameworks favor inheritance over composition (which is the opposite of what we have been arguing). Developer may do this because they put a lot of work into their base class that having it implemented with composition would have increase the size of the client code. Sometimes this is due to limitations of the language.

For example, a PHP ORM framework may create a base class that uses magic methods to allow writing code as if the object had real properties. Instead the code handled by the magic methods will be going to the database, query for the particular requested field (perhaps cache it for future request) and return it. Doing it with composition would require the client to either create properties for each field or write some version of the magic methods code.

Addendum: There some other ways one allow extending ORM objects. Thus, I do not think inheritance is necessary on this case.

For another example, a video game engine may create a base class that uses native code depending on the target platform to do 3D rendering and event handling. This code is complex and platform specific. It would be expensive and error prone for the developer user of the engine to deal with this code, in fact, that is part of the reason of using the engine.

Furthermore, without the 3D rendering part, this is how many widget frameworks work. This releases you from worrying about handling OS messages… in fact, in many languages you cannot write such code without some form of native biding. Moreover, if you were to do it, it would tight your portability. Instead, with inheritance, provided the developer do not break compatibility (too much); you will be able to easily port your code to any new platforms they support in the future.

In addition of what I said above, consider that many times we only want to override a few methods and leave everything else with the default implementations. Had we been using composition we would have to create all those methods, even if only to delegate to the wrapped object.

By this argument, there is a point where composition can be worst for maintainability than inheritance (when the base class is too complex). Yet, remember that maintainability of inheritance can be worse than that of composition (when the inheritance tree is too complex), which is what I refer to in the yo-yo problem.

In the presented examples, the developers rarely intend to reuse the code generated via inheritance in other projects. That mitigates the diminished reusability of using inheritance instead of composition. In addition, by using inheritance the framework developers can provide a lot of easy to use and easy to discover code.


Final thoughts

As you can see, composition has some advantage over inheritance in some situations, not always. It is important to consider the context and the different factors involved (such as reusability, maintainability, testability, etc…) to make the decision. Back to the first point: "Prefer composition over inheritance" is a just good heuristic.

You may also have notices that many of the situation I describe can be resolved with Traits or Mixins to some degree. Sadly, these are not common features in the great list of languages, and they usually come with some performance cost. Thankfully, their popular cousin Extension Methods and Default Implementations mitigate some situations.

I have a recent post where I talk about some of the advantages of interfaces in why do we require interfaces between UI,Business and Data access in C#. It helps decoupling and eases reusability and testability, you might be interested.