The Flaws of Inheritance

The Flaws of Inheritance


The Flaws of Inheritance

Let’s discuss the tradeoffs between Inheritance and Composition

Access to code examples, discord, song names and more at https://www.patreon.com/codeaesthetic

0:00 Introduction
0:25 Inheritance
3:32 Composition
5:22 Abstracting with Inheritance
6:52 Abstracting with Interfaces
8:20 When to use Inheritance


Content

0.5 -> You may have heard the saying prefer composition over inheritance.
4.8 -> The advice is a little vague, so I'm going to break it down.
7.7 -> What is composition, inheritance and why would you prefer one over
11.183 -> the other?
16.816 -> Both composition and inheritance are trying to solve the same problem.
20.216 -> You have a piece of code that you're trying to reuse.
24.15 -> Inheritance is when you have a class that contains functionality you want to reuse.
28.4 -> So you create a subclass extending its functionality.
31.816 -> If you simply extend a class, you've basically created a copy of the class
35.633 -> with a new name, and then you can inject new methods to extend or override parts.
41.45 -> We have a rudimentary image class here.
44.266 -> It represents an RGB image and stores it as a double array of pixels.
48.6 -> The image class hides how the image is stored in memory
51.316 -> and provides a method for looking up pixel values.
54.6 -> We also have some stuff we can do to the image.
57.5 -> We have a resize method which resizes the image by a scale factor,
61.4 -> and we have methods to flip the image horizontally or vertically.
66.45 -> The library
66.983 -> should support JPEG, PNG and bitmap images, but
70.816 -> we also want to reuse all of these methods for the different types of images.
75.95 -> So to support loading and saving these images,
78.466 -> we add two abstract methods: save() and load(),
82.7 -> and then we create the subclass JpgImage,
86 -> PngImage and BmpImage.
93.766 -> These subclasses implement their different versions of load()
96.8 -> and save(),
101.116 -> but also get all of the other methods for free.
104.45 -> So when we load a JPEG image or PNG image
107.183 -> we can call resize on it and then we save it.
110.516 -> The resize method is reused for all of the image types.
114.15 -> But when we call load or save the overriden version is called instead.
118.616 -> This works well,
119.7 -> but now we want to create a version of an image
121.666 -> that doesn't come from a file at all, but instead has some methods
124.95 -> that allow the user to draw on the image.
128.15 -> So we create a DrawableImage class and inherit from our parent Image class.
133.366 -> But this
133.883 -> is where inheritance starts to have issues.
137 -> The downsides of inheritance is that you've couple yourself to the parent
140.266 -> class.
141.35 -> The structure of the parent is thrust upon the child.
144.45 -> We're forced to implement these to load and save methods
147.316 -> in order to reuse our resize and flip code,
150.5 -> even though they don't make sense for our subclass.
153.2 -> All we can do is have these two methods throw an exception.
157.066 -> To prevent this, we need to remove this method from our parent class
160.1 -> and add a new parent class
161.3 -> in between called FileImage that contains these two methods.
165.116 -> But this also breaks
166.066 -> anyone who currently expects the Image class to contain those methods.
170.3 -> When new changes like this come,
172.25 -> we're forced to edit all our classes, a very expensive refactor.
177.75 -> This is the
178.433 -> greatest downfall of inheritance.
181.5 -> I find it similar to how the ideal cleanness database
184.35 -> schema often causes problems when you scale.
188.15 -> We've moved to NoSql databases with tons of dirty duplication.
192.566 -> Inheritance breaks down when you need to change the code.
195.416 -> Change is the enemy of perfect design and you often paint yourself
198.75 -> into a corner early on with your inheritance design.
202.583 -> This is because inheritance naturally asks you
204.9 -> to bundle all common elements into a parent class.
208.1 -> But the soon as you find an exception to the commonality, it requires big changes.
213.316 -> So our alternative is to use composition.
217.216 -> So what is composition?
219.716 -> You've already been doing it.
222.2 -> Composition is the pattern you're doing whenever you reuse code
225.05 -> without inheritance.
226.616 -> If we have two classes and they want to reuse code, they simply use the code.
232.766 -> Let's
233.116 -> change our image classes to be composed instead.
236.4 -> First, we're going to remove our abstract methods from image.
241.666 -> Now, this is no longer an abstract class.
244.166 -> It's simply a class that represents an image in memory.
248.75 -> In our JPEG, PNG and bitmap classes.
252.05 -> We no longer inherit image,
254.933 -> but we'll keep our save and load methods.
257.066 -> They'll just now be stand alone, not overriding anything.
260.483 -> The methods were accessing a bunch of stuff from the parent class.
263.566 -> So what do we do about those?
265.466 -> Well, instead of accessing them through “this” we’ll
268.8 -> simply pass in the image in question instead.
273.266 -> So now image represents an image
275.933 -> and these other classes cleanly represent a specific file
279.6 -> format.
283.583 -> Now, if our new drawing requirement comes in,
286.016 -> we create an image draw class that takes an image to draw to.
289.4 -> And the methods do their thing.
291.05 -> We're no longer bundled to the file related stuff.
295.066 -> Because we didn't force all the common elements into a parent class
298.283 -> we don't need to alter any of the other classes to add our ImageDraw class.
302.933 -> Now the user no longer chooses the one class that suits their needs.
306.416 -> They also combine classes together for their particular use case.
309.9 -> So here we're loading a JPEG image drawing to it and then saving it.
314.633 -> Another app could do something different, like load a bitmap
317.416 -> image, flip it, resize it, and then save it out as a PNG.
322.5 -> Inheritance is interesting because it actually combines
324.95 -> two capabilities: the ability to reuse code,
328.216 -> but also the ability to build an abstraction.
330.733 -> Creating abstractions allow a piece of code to reuse
334.466 -> another piece of code, but also not know which piece of code it's using.
339.15 -> You define a contract that both sides of the abstraction agree to.
342.466 -> This gives the code
343.55 -> the rough shape of the other code, but it doesn't know exactly what it is.
347.7 -> Inheritance does this by allowing a consumer to think it's taking a class,
351.833 -> but it's actually given a subclass instead.
354.65 -> Then the code can operate like it always does.
357.166 -> Even if the system as a whole is doing something very different.
360.2 -> If we go back to when our image code used inheritance,
363.083 -> our application used the natural abstraction capability of inheritance
366.65 -> by storing references to the parent class.
370.316 -> When our app opens a file,
371.9 -> we just figure out
372.65 -> which subclass to create and then store a reference to it through the parent class.
378.2 -> Then when the user clicks the save button,
380.366 -> our save clicked method will get invoked and we'll just call the save method
384.75 -> and we're abstracted from whether it's the JPEG, ping or bitmap.
388.65 -> But with composition you don't have parent classes.
391.7 -> You're just using the types you want.
393.866 -> Inheritance allows you to abstract because the methods of the parent class
397.216 -> forms a contract.
398.633 -> A contract that says that every child class shall have at least these methods.
403.65 -> So for our new classes without inheritance, we still want to be
407.1 -> able to call our save and load methods without caring about which class it is.
412.516 -> This is where interfaces come in.
414.6 -> Instead of a full parent class with all its variables and methods,
418.2 -> an interface simply describes the contract of what an object can do.
422.85 -> In this case, we'll create an interface called Image File,
425.933 -> which represents the operations an image file can do:
429.166 -> load and save.
435.616 -> Now, like
436.016 -> before, we save a reference to one of our implementations.
439.1 -> But now, through the interface
441.983 -> and when the user click save,
443.666 -> we call save on whatever type was created.
446.866 -> Interfaces are a much more lightweight
448.8 -> way to do this because our interfaces are minimal.
452.033 -> Parent classes share everything by default, making them more difficult
455.45 -> to change.
456.9 -> But interfaces define only the critical parts of the contract
460.166 -> and are easily tacked on to existing classes.
463.666 -> Now that we have a nice abstraction for loading and saving files in our app,
467.25 -> we can actually lift the creation of which image file out of our image app class.
472.216 -> We’ll simply ask the user of the class to pass in the interface instead.
477.266 -> That way this class can just focus on dealing
479.366 -> with the UI commands and the file class can be elsewhere.
483.683 -> There's a name for what we just did there.
485.816 -> Dependency injection.
488.25 -> I'll do a whole video on dependency injection,
490.65 -> but if you've heard the term before and wondered what it was, that's it.
494.566 -> Passing in an interface for what you're going to use.
499.8 -> I won't say that inheritance is as evil as some would say,
503.25 -> but I will say that I almost never use it in my code.
507.716 -> Composition isn't perfect.
509.55 -> You do end up with a lot of boilerplate
512.333 -> needing to initialize all of your internal types.
515.033 -> Many implementations will contain the same code repeated,
518.816 -> and when you need expose information from reused code, you often need to create
522.9 -> a lot of wrapper methods where you simply return a call to an inner type.
527.033 -> But ultimately, composition reduces the surface area
530.25 -> between objects, which gives you less friction as changes come in.
535.2 -> Inheritance might be useful if you're working inside of an existing system
538.666 -> that had highly repetitive code where you only needed to modify one thing.
543.566 -> For example, if you had the need for 100 classes
546.266 -> to conform to some specific interface
548.516 -> which half of them need the same boilerplate over and over again.
552.283 -> You might say that that means that each class has too much responsibility,
556.316 -> and you'd be right.
557.183 -> But changing the plugin model would cost the team months of work.
560.483 -> You might not want to put your effort into that just yet.
563.566 -> If you do use inheritance design the class to be inherited, I'd avoid
568.35 -> protected variables with direct access, like we avoid making our variables public.
573.716 -> For overriding: create an explicit protected
575.816 -> API that you're supposed to override an access mark.
579.016 -> Everything else is private, final or sealed.
582.266 -> This prevents bugs when changing your parent classes
584.666 -> because of not understanding what your child classes have done.

Source: https://www.youtube.com/watch?v=hxGOiiR9ZKg