Inheritance or Composition

February 5, 2018    Development OOP Theory

Inheritance or Composition

Here’s a fun video from Fun Fun Function with a silly example, but a good explanation of “composition over inheritance” train of thought. It also has a JS example using composition. Let’s use sections of this video to think about inheritance and composition in Object Oriented Programming.

The first 3 minutes and 15 seconds show inheritance and how it can run into problems. The inheritance approach is what I was taught in college in the early 2000’s. I might have forgotten, but I don’t recall discussions about composition.

At the end of that section he defines inheritance as “what they are” and composition as “what they do”.

Have you ran into problems when adding inheritance to avoid code reuse?

Have you considered an alternative to creating that hierarchy?

I’ve created XBase classes all too often and wonder what the ramifications will be later on. Lately, I’ve been using composition to add functionality and to simplify existing objects.

Using composition makes unit testing a lot easier, since you can focus in on the current unit. Dzone lists composition in their 10 tips to writing good unit tests. I’m also striving for smaller classes that have a single responsibility. The S.O.L.I.D. principles encourage small classes, dependency injection and open for extension/closed for modification. I think that composition is helpful in working towards these ideals.

Composition also lends itself to JavaScript imports (which is similar in many ways to DI) and small pieces put together are easier to maintain, read, test and share. I can much easily share a class then moving it out of an inherited object’s method.

There is also newer discussion about composable UIs (aka micro-frontends) that fits into composition patterns and something to keep an eye on. Here are a few links: Composite UIs for Microservices - a primer, Cory House on Twitter, a bunch of recorded talks and Micro-frontends.org.

Inheritance Example

Here’s another example in Typescript (easily converted to C#) that I made up (maybe it’s not the best). This approach doesn’t seem bad now, but as he points out later “humans can’t predict the future”. This could get complex and a large file very quickly.

  class Spaceship {
      public jumpToHyperSpace(coords: HyperSpaceCords) {
          // implementation code
      }

      public accelerate(newSpeed: number) {
          // implementation code
      }

      public turn(newDirection: number) {
          // implementation code
      }
  }

  class Cruiser extends Spaceship {

  }

  class Fighter extends Spaceship {

  }

Composition Examples

Watch 3:15 to 5:46 to see his composition example in JavaScript. JavaScript is different then C# and can be dynamically attached to, so C# would need injected components. I’ve followed this train of thought in my examples.

Now let’s look at my example using composition and imports to simplify Spaceship.

Composition with inheritance mix example

  import HyperDrive from './hyperDrive';
  import PropulsionSystem from './propulsionSystem';
  import NavigationSystem from './navigationSystem';

  // in this case, the Cruiser doesn't have a weapons system
  // adding a weapons system needs to only go into the fighter (and other spaceships in the feature that have weapons).
  import WeaponsSystem from './weaponsSystem';

  class Spaceship {
      protected currentSpeed = 0;
      protected accelerationAmount = 0;
      constructor(private hyperDrive: HyperDrive,
        private propulsionSystem: PropulsionSystem,
        private navigationSystem: NavigationSystem) {

      }

      public jumpToHyperSpace(coords: HyperSpaceCords) {
          // all that implementation code moves into the hyperDrive module!
          this.hyperDrive.jumpToHyperSpace(coords);
      }

      public accelerate(newSpeed: number) {
          this.currentSpeed = newSpeed;
          this.propulsionSystem.accelerate(newSpeed);
      }

      public turn(newDirection: number) {
          this.navigationSystem.turn(newDirection);
      }
  }

  class Cruiser extends Spaceship {

  }

  // things are starting to get layered and less readable the deeper the inheritance chain goes.
  // this example still isn't too bad.
  class BaseFighter extends Spaceship {
       constructor(private hyperDrive: HyperDrive,
        private propulsionSystem: PropulsionSystem,
        private navigationSystem: NavigationSystem,
        private weaponsSystem: WeaponsSystem) {
          super(hyperDrive, propulsionSystem, navigationSystem);
          this.accelerationAmount = 5;
      }

      public accelerate(ignore:number){
          // problematic!!

          this.accelerate(this.currentSpeed + )
      }

      public fire() {
          this.weaponsSystem.fire();
      }
  }

  class ClassAFighter extends BaseFighter {
      constructor(private hyperDrive: HyperDrive,
        private propulsionSystem: PropulsionSystem,
        private navigationSystem: NavigationSystem,
        private weaponsSystem: WeaponsSystem) {
          super(hyperDrive, propulsionSystem, navigationSystem, weaponsSystem);
          this.accelerationAmount = 10
      }
  }

  class ClassBFighter extends BaseFighter {
    constructor(private hyperDrive: HyperDrive,
        private propulsionSystem: PropulsionSystem,
        private navigationSystem: NavigationSystem,
        private weaponsSystem: WeaponsSystem) {
          super(hyperDrive, propulsionSystem, navigationSystem, weaponsSystem);
      }
  }

We still have inheritance, but at least the code is put into the smaller modules. At this point, I’m ok with leaving in the inheritance. However, when someone comes along and adds something to Spaceship that both don’t have, or adds a new type that doesn’t have a hyper drive, then we’re in trouble.

No Inheritance

  class Cruiser {
      private currentSpeed = 0;
      constructor(private hyperDrive: HyperDrive,
        private propulsionSystem: PropulsionSystem,
        private navigationSystem: NavigationSystem) {

      }

      public jumpToHyperSpace(coords: HyperSpaceCords) {
          // all that implementation code moves into the hyperDrive module
          this.hyperDrive.jumpToHyperSpace(coords);
      }

      public accelerate() {
          this.currentSpeed += 100;
          this.propulsionSystem.accelerate(this.currentSpeed);
      }

      public turn(newDirection: number) {
          this.navigationSystem.turn(newDirection);
      }
  }

  class SpaceShuttle {
      private currentSpeed = 0;
       constructor(private propulsionSystem: PropulsionSystem,
        private navigationSystem: NavigationSystem) {
      }

      public accelerate() {
          this.currentSpeed += 10;
          this.propulsionSystem.accelerate(this.currentSpeed);
      }

      public turn(newDirection: number) {
          this.navigationSystem.turn(newDirection);
      }
  }

If we remove the base class, we now have duplication of the code (but not of the logic) that passes on the values to the module. This requires duplicate tests that make sure the correct method was called with the correct value to get the coverage we need. I’m not completely sure if this duplication should be avoided or if the simplification and the easy tests are worth it.

Factory Methods help

One downside that comes up with composition is having to know which objects to pass in and to create them. A factory method can compose the objects for you, reducing this difficulty.

 class CruiserFactory {
     public static Create() : Cruiser {
         // we can new up objects here, "new is glue" where I can't fake out these objects now
         // so we could inject them from the Inversion of Control system or use a different factory method for tests.
         return new Cruiser(new HyperDrive(), new PropulsionSystem(), new NavigationSystem());
     }
 }

 const cruiser = CruiserFactory.Create();

When to use inheritance vs composition?

At 5 minutes and 47 seconds he talks about the question “When to use inheritance vs composition?” He says you should always use composition over inheritance. “because humans can’t predict the future” and “more flexible, more powerful, and really easy to do”.

However, I think the quote from ThoughtWorks is important to consider as well. “As a heuristic, ‘favor composition over inheritance’ is okay, however, I am not a fan of mantras. While they often contain a kernel of truth, it is far too easy for people to hear the slogan without understanding its source or context, and thus avoid thinking for themselves - and that never turns out well.” [1]

There were some good rules of thumb, including “If you find that your subclass is removing things provided by the superclass, question inheriting from that superclass.” from ThoughtWorks. I highly recommend reading the ThoughtWorks article for a more in-depth discussion. [1]

Here are some more comments from StackOverflow [2].

“The “preference” for composition is not a matter of “better”, it’s a question of “most appropriate” for your needs, in a specific context.” [1]

“Prefer composition over inheritance as it is more malleable / easy to modify later, but do not use a compose-always approach. With composition, it’s easy to change behavior on the fly with Dependency Injection / Setters. Inheritance is more rigid as most languages do not allow you to derive from more than one type. So the goose is more or less cooked once you derive from TypeA.” ~Gishu[2]

“Think of containment as a has a relationship. A car “has an” engine, a person “has a” name, etc.[2] Think of inheritance as an is a relationship. A car “is a” vehicle, a person “is a” mammal, etc. I take no credit for this approach. I took it straight from the Second Edition of Code Complete by Steve McConnell, Section 6.3.” ~ Nick Zalutskiy [2]

Conclusion

As always, it depends on the situation and context. Both inheritance and composition have their place. However, I’m going to continue in the thought and recommendation to prefer “composition over inheritance”.

Overall, I think my example shows that composition should be favored over inheritance. What do you think? I’d love to hear from you in the comments.

References

[1]ThoughtWorks article

[2]StackOverflow question and answers

found after writing this article: Composing Software: An Introduction



comments powered by Disqus

Please consider using Brave and adding me to your payment ledger. Then you won't have to see ads!

Support me and download Brave!

Use Brave