The Flaws of Inheritance

CodeAesthetic
22 Dec 202210:00

Summary

TLDRThe video script discusses the concept of 'prefer composition over inheritance' in programming. It explains that inheritance, while allowing code reuse and abstraction, can lead to tightly coupled code and difficulties in refactoring. Composition, on the other hand, promotes flexibility and easier maintenance by allowing classes to be assembled from other classes without inheritance. The script illustrates this with an example of image classes, showing how composition can reduce complexity and improve adaptability to change. It also touches on the use of interfaces and dependency injection as alternatives to inheritance for achieving abstraction and modularity.

Takeaways

  • πŸ” Composition and inheritance both aim to reuse code, but they approach it differently.
  • πŸ“œ Inheritance involves creating a subclass that extends the functionality of a parent class.
  • πŸ“Œ Inheritance can lead to coupling with the parent class, making changes more difficult.
  • πŸ”„ Composition allows for a more flexible code structure by using objects together without inheritance.
  • πŸ“š Abstract classes and methods are used in inheritance to define common functionality across subclasses.
  • 🚫 Inheritance can be problematic when you need to add new functionality that doesn't fit the parent class structure.
  • πŸ”§ To address inheritance issues, you might need to refactor the codebase, which can be costly.
  • πŸ”„ Composition promotes a more modular design, making it easier to adapt to changes.
  • πŸ”— Interfaces can be used in composition to define a contract for behavior without inheritance.
  • πŸ’‰ Dependency injection is a technique where dependencies are passed in, allowing for more flexible and testable code.
  • πŸ“ Boilerplate code and wrapper methods can be drawbacks of composition, but they reduce coupling between objects.

Q & A

  • What is the main advice given in the script regarding code reuse?

    -The main advice is to prefer composition over inheritance when trying to reuse code.

  • What are the two approaches to code reuse discussed in the script?

    -The two approaches discussed are inheritance and composition.

  • How does inheritance work in the context of the script?

    -Inheritance works by creating a subclass that extends the functionality of an existing class, allowing for code reuse through the subclass.

  • What is the downside of using inheritance as mentioned in the script?

    -The downside of inheritance is that it tightly couples the child class to the parent class, making it difficult to change or refactor the code without affecting other parts of the system.

  • What is composition and how does it differ from inheritance?

    -Composition is a pattern where classes reuse code by using instances of other classes without inheriting from them. It differs from inheritance in that it allows for more flexibility and less coupling between classes.

  • The script illustrates composition by removing the abstract methods from the Image class and having the JPEG, PNG, and bitmap classes use instances of the Image class directly, without inheritance.

    -null

  • What is an interface and how is it used in the script?

    -An interface is a contract that defines the methods an object can perform without specifying their implementation. In the script, an interface called ImageFile is used to define the load and save operations, allowing for abstraction without inheritance.

  • What is dependency injection and how is it demonstrated in the script?

    -Dependency injection is a technique where an object receives its dependencies from external sources rather than creating them itself. In the script, it is demonstrated by passing an interface for the image file to the class, allowing the class to focus on UI commands and leaving the file handling to another part of the system.

  • Why might inheritance still be useful in certain situations?

    -Inheritance might be useful when working within an existing system with highly repetitive code, where creating a new class hierarchy would be too costly or time-consuming.

  • What are the potential drawbacks of using composition?

    -The potential drawbacks of using composition include increased boilerplate code, repeated code in many implementations, and the need to create wrapper methods to expose information from reused code.

  • What advice is given for designing classes that will be inherited?

    -The advice given is to avoid protected variables with direct access, create an explicit protected API for overriding, and keep everything else private, final, or sealed to prevent bugs when changing parent classes.

Outlines

00:00

πŸ” Understanding Composition vs Inheritance

This paragraph discusses the concept of preferring composition over inheritance in coding. It explains that both are ways to reuse code, with inheritance involving a class extending another to reuse functionality. The script uses an example of an Image class and its subclasses for different image formats. It highlights the limitations of inheritance, such as the need to refactor when new requirements arise, and the coupling of the child class to the parent's structure. The paragraph concludes by introducing composition as an alternative, where classes reuse code without inheriting, allowing for more flexibility and easier adaptation to changes.

05:03

πŸ“š Benefits and Use Cases of Composition

The second paragraph delves into the benefits of composition, illustrating how it allows for the combination of classes for specific use cases without the constraints of inheritance. It explains the role of abstraction in inheritance and how it allows a consumer to interact with a class without knowing its specific implementation. The paragraph introduces the concept of interfaces as a lightweight alternative to parent classes, which define a contract for method availability without the overhead of shared variables. It also touches on dependency injection as a way to decouple classes and focuses on the advantages of composition, such as reduced coupling and easier maintenance, despite the potential for increased boilerplate code. The paragraph ends with advice on best practices when using inheritance, emphasizing the importance of a protected API for overriding and the use of private, final, or sealed elements to prevent unintended changes.

Mindmap

Keywords

πŸ’‘Composition

Composition in the context of the video refers to a design pattern where classes reuse code by containing instances of other classes rather than inheriting from them. This approach allows for more flexibility and easier maintenance, as changes in one class do not necessarily affect others. For example, the script discusses how the Image class can be composed of other classes to handle different file formats without forcing all classes to implement unnecessary methods.

πŸ’‘Inheritance

Inheritance is a fundamental concept in object-oriented programming where a class (the subclass) extends another class (the superclass), inheriting its properties and methods. The video script illustrates the drawbacks of inheritance, such as the tight coupling between parent and child classes, which can lead to complex refactoring when changes are needed. In the example, the Image class is extended to create JpgImage, PngImage, and BmpImage subclasses, which inherit the load and save methods, even when they may not be applicable.

πŸ’‘Abstract Methods

Abstract methods are methods in a class that have no implementation and must be overridden by subclasses. They define a contract for what the subclasses must provide. In the video, the Image class has abstract methods save() and load(), which are meant to be implemented by subclasses like JpgImage and PngImage. However, the script points out the limitations of this approach, as it forces all subclasses to implement these methods, even if they are not relevant.

πŸ’‘DrawableImage

The DrawableImage class is an example in the video script of a class that would extend the Image class to allow users to draw on the image. This class would inherit from Image, but the script highlights the issue that it would also inherit the load and save methods, which do not make sense for a DrawableImage that does not come from a file. This illustrates the inflexibility of inheritance in certain scenarios.

πŸ’‘FileImage

FileImage is a proposed new parent class in the video script to address the issues with inheritance. It would contain the load and save methods, allowing other image classes like JpgImage and PngImage to inherit from it instead of directly from Image. This change would prevent the need for subclasses like DrawableImage to implement methods that do not apply to them, but it also introduces complexity and potential for breaking changes.

πŸ’‘Interfaces

Interfaces in object-oriented programming define a contract for a set of methods that a class must implement. They are used in the video script as a solution to the problems with inheritance. By creating an interface called ImageFile, the script demonstrates how classes can implement this interface to provide load and save functionality without being tightly coupled to a parent class. This allows for more flexibility and easier maintenance.

πŸ’‘Dependency Injection

Dependency injection is a design pattern that involves passing the dependencies of a class to it, rather than having the class create or find them itself. In the video, dependency injection is used to pass an ImageFile interface into the image app class, allowing the class to focus on UI commands while the file handling is managed elsewhere. This approach promotes modularity and makes the system more adaptable to changes.

πŸ’‘Boilerplate Code

Boilerplate code refers to the repetitive, standardized code that is often found in many places in a program. In the context of the video, the use of composition can lead to boilerplate code, as similar initializations and wrapper methods may be needed for each class that reuses functionality. Despite this, the video argues that composition is preferable due to its flexibility and reduced coupling between classes.

πŸ’‘Refactoring

Refactoring is the process of restructuring existing computer code without changing its external behavior. In the video, refactoring is mentioned as a costly process when using inheritance, especially when changes require editing multiple classes due to the tight coupling. The script suggests that composition can reduce the need for extensive refactoring by allowing for more independent class evolution.

πŸ’‘Cleanness Database Schema

A cleanness database schema refers to a well-structured and organized database design. The video script compares the ideal of a clean database schema to the problems that arise with inheritance when scaling. It suggests that, like with databases, the pursuit of a perfect design in inheritance can lead to difficulties when changes are needed, and it may be more practical to embrace some duplication and flexibility.

Highlights

The advice 'prefer composition over inheritance' is explored in detail.

Composition and inheritance both aim to solve the problem of code reuse.

Inheritance involves creating a subclass to extend functionality.

An example of an Image class is used to illustrate inheritance.

Subclasses like JpgImage, PngImage, and BmpImage inherit from Image to reuse methods.

Inheritance's downside is its coupling to the parent class structure.

Inheritance can lead to expensive refactors when changes are needed.

Composition is the pattern of reusing code without inheritance.

The Image class is refactored to be composed of other classes instead of being abstract.

The new DrawableImage class is created using composition, not inheritance.

Composition allows for more flexibility when adding new features like ImageDraw.

Inheritance provides the ability to build abstractions through a contract.

Interfaces are used to define a contract for objects without the baggage of parent classes.

Dependency injection is introduced as a way to pass in interfaces for flexibility.

The speaker prefers composition due to its reduced friction with changes.

Inheritance might be useful in systems with highly repetitive code.

Best practices for inheritance design include avoiding protected variables with direct access.

Transcripts

00:00

You may have heard the saying prefer composition over inheritance.

00:04

The advice is a little vague, so I'm going to break it down.

00:07

What is composition, inheritance and why would you prefer one over

00:11

the other?

00:16

Both composition and inheritance are trying to solve the same problem.

00:20

You have a piece of code that you're trying to reuse.

00:24

Inheritance is when you have a class that contains functionality you want to reuse.

00:28

So you create a subclass extending its functionality.

00:31

If you simply extend a class, you've basically created a copy of the class

00:35

with a new name, and then you can inject new methods to extend or override parts.

00:41

We have a rudimentary image class here.

00:44

It represents an RGB image and stores it as a double array of pixels.

00:48

The image class hides how the image is stored in memory

00:51

and provides a method for looking up pixel values.

00:54

We also have some stuff we can do to the image.

00:57

We have a resize method which resizes the image by a scale factor,

01:01

and we have methods to flip the image horizontally or vertically.

01:06

The library

01:06

should support JPEG, PNG and bitmap images, but

01:10

we also want to reuse all of these methods for the different types of images.

01:15

So to support loading and saving these images,

01:18

we add two abstract methods: save() and load(),

01:22

and then we create the subclass JpgImage,

01:26

PngImage and BmpImage.

01:33

These subclasses implement their different versions of load()

01:36

and save(),

01:41

but also get all of the other methods for free.

01:44

So when we load a JPEG image or PNG image

01:47

we can call resize on it and then we save it.

01:50

The resize method is reused for all of the image types.

01:54

But when we call load or save the overriden version is called instead.

01:58

This works well,

01:59

but now we want to create a version of an image

02:01

that doesn't come from a file at all, but instead has some methods

02:04

that allow the user to draw on the image.

02:08

So we create a DrawableImage class and inherit from our parent Image class.

02:13

But this

02:13

is where inheritance starts to have issues.

02:17

The downsides of inheritance is that you've couple yourself to the parent

02:20

class.

02:21

The structure of the parent is thrust upon the child.

02:24

We're forced to implement these to load and save methods

02:27

in order to reuse our resize and flip code,

02:30

even though they don't make sense for our subclass.

02:33

All we can do is have these two methods throw an exception.

02:37

To prevent this, we need to remove this method from our parent class

02:40

and add a new parent class

02:41

in between called FileImage that contains these two methods.

02:45

But this also breaks

02:46

anyone who currently expects the Image class to contain those methods.

02:50

When new changes like this come,

02:52

we're forced to edit all our classes, a very expensive refactor.

02:57

This is the

02:58

greatest downfall of inheritance.

03:01

I find it similar to how the ideal cleanness database

03:04

schema often causes problems when you scale.

03:08

We've moved to NoSql databases with tons of dirty duplication.

03:12

Inheritance breaks down when you need to change the code.

03:15

Change is the enemy of perfect design and you often paint yourself

03:18

into a corner early on with your inheritance design.

03:22

This is because inheritance naturally asks you

03:24

to bundle all common elements into a parent class.

03:28

But the soon as you find an exception to the commonality, it requires big changes.

03:33

So our alternative is to use composition.

03:37

So what is composition?

03:39

You've already been doing it.

03:42

Composition is the pattern you're doing whenever you reuse code

03:45

without inheritance.

03:46

If we have two classes and they want to reuse code, they simply use the code.

03:52

Let's

03:53

change our image classes to be composed instead.

03:56

First, we're going to remove our abstract methods from image.

04:01

Now, this is no longer an abstract class.

04:04

It's simply a class that represents an image in memory.

04:08

In our JPEG, PNG and bitmap classes.

04:12

We no longer inherit image,

04:14

but we'll keep our save and load methods.

04:17

They'll just now be stand alone, not overriding anything.

04:20

The methods were accessing a bunch of stuff from the parent class.

04:23

So what do we do about those?

04:25

Well, instead of accessing them through β€œthis” we’ll

04:28

simply pass in the image in question instead.

04:33

So now image represents an image

04:35

and these other classes cleanly represent a specific file

04:39

format.

04:43

Now, if our new drawing requirement comes in,

04:46

we create an image draw class that takes an image to draw to.

04:49

And the methods do their thing.

04:51

We're no longer bundled to the file related stuff.

04:55

Because we didn't force all the common elements into a parent class

04:58

we don't need to alter any of the other classes to add our ImageDraw class.

05:02

Now the user no longer chooses the one class that suits their needs.

05:06

They also combine classes together for their particular use case.

05:09

So here we're loading a JPEG image drawing to it and then saving it.

05:14

Another app could do something different, like load a bitmap

05:17

image, flip it, resize it, and then save it out as a PNG.

05:22

Inheritance is interesting because it actually combines

05:24

two capabilities: the ability to reuse code,

05:28

but also the ability to build an abstraction.

05:30

Creating abstractions allow a piece of code to reuse

05:34

another piece of code, but also not know which piece of code it's using.

05:39

You define a contract that both sides of the abstraction agree to.

05:42

This gives the code

05:43

the rough shape of the other code, but it doesn't know exactly what it is.

05:47

Inheritance does this by allowing a consumer to think it's taking a class,

05:51

but it's actually given a subclass instead.

05:54

Then the code can operate like it always does.

05:57

Even if the system as a whole is doing something very different.

06:00

If we go back to when our image code used inheritance,

06:03

our application used the natural abstraction capability of inheritance

06:06

by storing references to the parent class.

06:10

When our app opens a file,

06:11

we just figure out

06:12

which subclass to create and then store a reference to it through the parent class.

06:18

Then when the user clicks the save button,

06:20

our save clicked method will get invoked and we'll just call the save method

06:24

and we're abstracted from whether it's the JPEG, ping or bitmap.

06:28

But with composition you don't have parent classes.

06:31

You're just using the types you want.

06:33

Inheritance allows you to abstract because the methods of the parent class

06:37

forms a contract.

06:38

A contract that says that every child class shall have at least these methods.

06:43

So for our new classes without inheritance, we still want to be

06:47

able to call our save and load methods without caring about which class it is.

06:52

This is where interfaces come in.

06:54

Instead of a full parent class with all its variables and methods,

06:58

an interface simply describes the contract of what an object can do.

07:02

In this case, we'll create an interface called Image File,

07:05

which represents the operations an image file can do:

07:09

load and save.

07:15

Now, like

07:16

before, we save a reference to one of our implementations.

07:19

But now, through the interface

07:21

and when the user click save,

07:23

we call save on whatever type was created.

07:26

Interfaces are a much more lightweight

07:28

way to do this because our interfaces are minimal.

07:32

Parent classes share everything by default, making them more difficult

07:35

to change.

07:36

But interfaces define only the critical parts of the contract

07:40

and are easily tacked on to existing classes.

07:43

Now that we have a nice abstraction for loading and saving files in our app,

07:47

we can actually lift the creation of which image file out of our image app class.

07:52

We’ll simply ask the user of the class to pass in the interface instead.

07:57

That way this class can just focus on dealing

07:59

with the UI commands and the file class can be elsewhere.

08:03

There's a name for what we just did there.

08:05

Dependency injection.

08:08

I'll do a whole video on dependency injection,

08:10

but if you've heard the term before and wondered what it was, that's it.

08:14

Passing in an interface for what you're going to use.

08:19

I won't say that inheritance is as evil as some would say,

08:23

but I will say that I almost never use it in my code.

08:27

Composition isn't perfect.

08:29

You do end up with a lot of boilerplate

08:32

needing to initialize all of your internal types.

08:35

Many implementations will contain the same code repeated,

08:38

and when you need expose information from reused code, you often need to create

08:42

a lot of wrapper methods where you simply return a call to an inner type.

08:47

But ultimately, composition reduces the surface area

08:50

between objects, which gives you less friction as changes come in.

08:55

Inheritance might be useful if you're working inside of an existing system

08:58

that had highly repetitive code where you only needed to modify one thing.

09:03

For example, if you had the need for 100 classes

09:06

to conform to some specific interface

09:08

which half of them need the same boilerplate over and over again.

09:12

You might say that that means that each class has too much responsibility,

09:16

and you'd be right.

09:17

But changing the plugin model would cost the team months of work.

09:20

You might not want to put your effort into that just yet.

09:23

If you do use inheritance design the class to be inherited, I'd avoid

09:28

protected variables with direct access, like we avoid making our variables public.

09:33

For overriding: create an explicit protected

09:35

API that you're supposed to override an access mark.

09:39

Everything else is private, final or sealed.

09:42

This prevents bugs when changing your parent classes

09:44

because of not understanding what your child classes have done.

Rate This
β˜…
β˜…
β˜…
β˜…
β˜…

5.0 / 5 (0 votes)

Related Tags
InheritanceCompositionCode ReuseOOPSoftware DesignAbstractionFlexibilityRefactoringDependency InjectionInterface