Why Favor Object Composition Over Class Inheritance? A Deep Dive

Поделиться
HTML-код
  • Опубликовано: 20 сен 2024

Комментарии • 101

  • @zoran-horvat
    @zoran-horvat  Год назад +1

    Become a patron and get access to source code and exclusive live streams: www.patreon.com/posts/favor-object-but-81382143
    Send over the examples of your class designs that you wish me to review and, maybe, include in the future video on code redesign and refactoring.
    RUclips is aggressively deleting all links. If you wish to submit your repo for review, use this form instead: codinghelmet.com/go/code-review-request

  • @kzelmer
    @kzelmer 2 месяца назад +4

    Beautifully explained. I have seen so many explanations where they use the horrible idea of "composition is a "has a" and inheritance is an "is a"".... While this is partially true, it does not completely explain why favour composition. Following that logic you could always find an "is a" relation in any hierarchy
    Love your explanation here: what problem were we trying to solve with inheritance? Movement? Ok, just have a class that represents that behaviour and use DI to use it on the "base" class. Just clear and beautiful
    Many thanks for such a simple and elegant explanation

  • @bogdanzarzycki9885
    @bogdanzarzycki9885 Год назад +11

    I'am not sure if I like the example, but the explanation is top notch. Thanks

    • @UberEverywhereSKRT
      @UberEverywhereSKRT Год назад +1

      i feel the same way, i wish the example was better but good video nonetheless

    • @blo0m1985
      @blo0m1985 Год назад

      totalHours with sum operations. If one of MovingAbility is null, adding null to any value, will return null and not a value. Thus all ground vehicles, having flying ability as null, will always have null totalHours, correct? is it supposed to be so?

    • @DevMeloy
      @DevMeloy 4 месяца назад

      I agree, I'd stick with the classes. I'd have made extension methods to derive the speed from kph to anything else needed. This implementation would be fine in a small project but it appears messy and lacks focus.

  • @akhilbandari629
    @akhilbandari629 Год назад +2

    Hi Zoran
    Every time I learn something new, whenever you publish a new video.
    Great to see. Thanks for sharing it.

  • @bjk837
    @bjk837 Год назад +6

    Excellent content as always Zoran!

    • @zoran-horvat
      @zoran-horvat  Год назад

      Thanks! You can always take part by submitting a question about some of your designs where you need a second opinion or a guideline.

  • @e-k4110
    @e-k4110 2 месяца назад +1

    Very explanatory and useful video. Thank you

  • @piotrw3366
    @piotrw3366 Год назад +2

    Hey Zoran!
    I'm so grateful that you have recorded this video. I still can't quite out my finger on converting inheritance to composition.
    Could you recommend any reading on it (or one of your courses)? I might just be not fully understanding some concept of OOP that causes me to constantly try inheritance (I read a lot of books / tried creating bigger projects but still I am missing something).
    I would really appreciate your help :)

  • @hauntedcrowdev
    @hauntedcrowdev 8 месяцев назад +1

    This is a very interesting video! Very useful information. I've been working with Unity for a while now, and since most of the objects, like items and characters, have repeated behaviors, instead of using inheritance, I've been using separated scripts for a defined behaviors, which makes it so much easier for design. Instead of finding a way to make inheritance work and be flexible, I can just make a behavior script and attach it to the object. I thought I was being a little lazy there, but it may have not been a bad idea after all.

    • @zoran-horvat
      @zoran-horvat  8 месяцев назад +1

      That sounds like a valid use of composition.

    • @energy-tunes
      @energy-tunes Месяц назад

      congrats you invented ecs

    • @hauntedcrowdev
      @hauntedcrowdev Месяц назад

      ​@@energy-tunes lol, new skill unlocked

  • @piotrw3366
    @piotrw3366 Год назад +3

    Around 13:35 you keep adding different abilities to the Vehicle class (composition). Is this how we extend the functionality (Open-closed principle) of the vehicle class? I mean is that the procedure when we want to add more functionality to the vehicle class later on?
    What I mean is I always thought composition means that we create separate class for ex "input", "engine" or "movement type" and next we create a "boat" class that as a movement type uses "water Movement" which ex detects if we are on water and can move, passes to it the value from the "input" and it performs the movement. For the car we use "Road movement" or whatever. That we create new classes that reuses the existing ones and basically are composed out of those.
    Thanks for this video!

    • @zoran-horvat
      @zoran-horvat  Год назад +4

      That is the good question. Depending on your priorities, you can orient the design in direction on supporting addition of more components without changing the class and disrupting its dependees. A design with a collection of abilities might be the way.
      However, such design would open the whole lot of other issues that stem from its increased abstractness. Like, how do we select abilities that apply to the terrain given in the query. That is simple now, at the expense of making a significant change at one point, maybe breaking some previous tests, etc.
      Bottom line is that any strategic decision in design is also a liability - a trade off. In the end, we select the design that satisfies what we consider important.

  • @LinuxUser822
    @LinuxUser822 Год назад +4

    Thank you in advance, watching now but I'm sure the content will be awesome as always :)

  • @anonymous.youtuber
    @anonymous.youtuber Год назад +1

    Beautiful video ! ❤ how you provide insight. By the way, in the context of flying vehicles there’s air speed and ground speed, making things even more complex.

  • @blo0m1985
    @blo0m1985 Год назад +1

    Zoran is favorite RUclips Man. Keep going, please.

  • @colebq
    @colebq Год назад +9

    Hey Zoran, thanks for the great content. Using your last example, how would you model vehicle specific properties without having to bloat the Vehicle class with functionality related to a single vehicle type, while also publicly exposing functionality only appropriate for the specific type. For example an airplane can have different wings (biplane, tandem wing, low wing, mid wing...), while a flying drone might have 4, 8,.. propellers instead. A boat might have sea anchors of different types, while that does not apply to car... At the end we do not want a method named "ApplyLandingConfiguration()" on a Car object. Of course we could have `car.LandingConfiguration?.Apply()` but the Vehicle class might become very large and confusing, if using composition only. Thinking out loud, but the ideal solution seems more like a combination of inheritance and composition, and constant refactoring while we add more types and functionality?

    • @zoran-horvat
      @zoran-horvat  Год назад +6

      The rule of thumb is to keep entanglement under control. It is easy to imagine classes gradually becoming too diverse, precisely the way you described it. At some point, trying to find what they share in common, so to make certain downstream operations universal, becomes impossible, given the complexity of everything else around and inside the classes.
      ISP is often helping strike a good balance. Separate the classes, let them share nothing except private components they can use for their own purposes. Then let them implement an interface or two that will make them partially overlap, and hence applicable to some operations that should work on them.
      And when everything fails, we are back at square one - we might be forced to duplicate code of external operations if there is really no way to apply it to two or more classes that used to relate in the distant past.

    • @blo0m1985
      @blo0m1985 Год назад

      totalHours with sum operations. If one of MovingAbility is null, adding null to any value, will return null and not a value. Thus all ground vehicles, having flying ability as null, will always have null totalHours, correct? is it supposed to be so?

  • @sebbefalt
    @sebbefalt 10 месяцев назад +1

    Thanks for the simple explanation!

  • @TwinechoesEntertainment
    @TwinechoesEntertainment 2 месяца назад

    Unfortunately, in C++, you can have multiple inheritance, which means the Amphibian vehicle can inherit twice, once from Boat and once from GroundVehicle, then inside GetAverageSpeed it can call into the base classes.
    The problems still arise later because, unlike members inside an object, base classes can not be swapped at runtime, you can not change the base class of an object to be something else, some other object instance.
    Great video overall though!

  • @brandonjoaocastillo7490
    @brandonjoaocastillo7490 Год назад +1

    Best Video about composition! I'm your new Fan and Sub

    • @zoran-horvat
      @zoran-horvat  Год назад

      Thanks! I'm glad to hear you liked it.

  • @user-nw8oi9vn9y
    @user-nw8oi9vn9y 10 месяцев назад +1

    Great tutorial! Very helpful. Question: Sometimes I see some design patterns that are taught with inheritance. Should those be reworked to use composition instead?

    • @zoran-horvat
      @zoran-horvat  10 месяцев назад +1

      On many occasions I prefer interfaces to base classes. When you transform it to composition, it would usually be a different pattern. But that comes with a twist: since composition can also be viewed as a manual kind of inheritance, then it turns out that all patterns of OOD boil down to two or three cases! Now that will be bothering you in the upcoming time.

  • @vincecas3557
    @vincecas3557 8 месяцев назад +1

    One thing that bothers me with videos that shows that says 'composition over inheritance' is they always show an example where they trip on the smallest problem and act likes there's no solution, or that it will become a mess.
    It always the same thing. Here's a apple, its a fruit! At this level, its simple! But what if the apple has wings?
    ??? Then its NOT a fruit?
    What I'm trying to say is that maybe there's an issue with your abstraction?

    • @zoran-horvat
      @zoran-horvat  8 месяцев назад

      Any solution for the problem described in this video based on inheritance would exhibit combinatorial growth in the number of classes and code repetition caused by it. Which part of that sentence you don't understand?

    • @pharoah327
      @pharoah327 4 месяца назад

      I agree. He uses a contrived example to prove a point. The fact is that inheritance has plenty of use cases where it works fine and composition has plenty of use cases where it works as well. Choose the one that's right for your use case. I've created elegant solutions with inheritance in professional applications. Sure it has drawbacks but so does composition! There is no silver bullet. Every design decision has trade offs. A skilled developer considers all tools when making a decision and goes with the one that makes the most sense for their current problem.

  • @danstoian7721
    @danstoian7721 Год назад +1

    Zoran, one side question, are you Hungarian? I feel like the name resembles! 😅🙂
    Greetings from Romania, Dan! 👋
    And thank you once again for your videos, I find them of great value!

    • @XKS99
      @XKS99 11 месяцев назад +1

      If I were to guess he was a Serbian-Hungarian from Vojvodina.

  • @user-tk2jy8xr8b
    @user-tk2jy8xr8b Год назад +1

    Another case when inheritance brings lot of pain is injected deps, especially when you have 10+ descendants and add a new dep into the ctor of the base class

  • @jean-pierreboies9749
    @jean-pierreboies9749 Год назад +2

    I'm sorry if you already answered that question before but what's the reason(s) you switched from Visual Studio to Visual Studio Code?

    • @zoran-horvat
      @zoran-horvat  Год назад +3

      It is quicker compared to VS and I find great joy in tools that respond to commands without delay. I moved gradually to VS Code and now I think there is no project type in which I prefer VS anymore.
      It is worth noting, though, that two of my early attempts failed miserably.

  • @dzllz
    @dzllz Год назад +1

    Amazing explanation. New sub here. Keep it up!

  • @anm3037
    @anm3037 Год назад +10

    You are an excellent teacher. Let me not degrade you by calling you a university Professor

  • @anywheredoor4699
    @anywheredoor4699 Год назад +1

    Could you also make something on the usage of volatile keywords and also explaining covariance and contra variance exactly

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      Here is the video I made on covariance and contravariance: ruclips.net/video/Wp5iYQqHspg/видео.html
      Regarding volatile keyword, I am not sure that there is much audience for that topic.

    • @anywheredoor4699
      @anywheredoor4699 Год назад

      @@zoran-horvat thanks a lot, I believe volatile is too vague nobody uses it

  • @lostinsarajevo
    @lostinsarajevo Год назад

    Hi Zoran, I have a question.
    So, let's say we create a Car.
    var car = Vehicles.CreateGroundVehicle(new MovingAbility(120));
    Console.WriteLine($"Average speed is: {car.GetAverageSpeed(120, 22, 3330)}");
    What would you expect this to print out?
    If it's a car, then it can not travel on air nor water, we can conclude the average speed would be 0 because the time needed would approach infinity to cover any of the two distances.
    However, what if we want to look for car.GetAverageSpeed(120, 0, 0);
    In this case, it should take into account that the car does not need to travel over water nor air and then print out the average speed. However, due to the nullability nature of the moving abilites, the totalHours will be null, hence rendering the GetAverageSpeed to be 0.
    Is this a mistake in my reasoning or a potential issue?

    • @zoran-horvat
      @zoran-horvat  Год назад

      I would expect any vehicle to return average speed zero if the track includes portions that it cannot travel, such as a flying distance applied to a car.

  • @Manas-co8wl
    @Manas-co8wl Год назад +4

    One of those hardfast rules that just.. is. Unquestionable. Sometimes I wish all programming conumdrums were clear-cut as this.
    Also why do I like your voice so much

  • @maou_knayo
    @maou_knayo Месяц назад

    you said the vehicle class is easily extendable without modifing the class directly but if i want to add a new type of Moving ability for example space i need to add a new property and a new factory for that. how is that possible without modifying the vehicle class?

    • @zoran-horvat
      @zoran-horvat  Месяц назад

      @@maou_knayo It is easy to modify it on Earth.

  • @antonshaganov9663
    @antonshaganov9663 Год назад +1

    Best videos ever!!!

  • @Steve3dot1416
    @Steve3dot1416 Год назад +1

    I don't understand why Faster is implemented as an extension and not as a public static method of the MovingAbility class...

    • @zoran-horvat
      @zoran-horvat  Год назад

      Because of the shorter call syntax and because that operation does not belong to the MovingAbility class. Though I am not convinced that such a short name is communicating its purpose well.

  • @nikolazagorac8634
    @nikolazagorac8634 8 месяцев назад +1

    What if you instead of having just the MovingAbility object composed, you also have a LightsAbility which controls the different kind of vehicle signalization, and a FuelAbility which controls fuel consumption, type and amount. In object composition you would have CreateDieselGroundVehicle(), CreateGasolineGroundVehicle(), CreateElectricGroundVehicle(), etc... and you would need to pass in the arguments all the different information: for eg. lights, engine, transmission etc. while in inheritance you would have a Vehicle abstract class which abstracts all those arguments as properties instead so a GroundVehicle would pass the FuelAbility into further abstraction so that the Mercedes_S_Class class would override that property and return a DieselFuelAbility which is created in the constructor and determined by a GroundVehicleData object passed in as an argument. I usually go a step further and have for each new type a separate data type and bind it with a generic argument from the base class, but then you also need an interface for the base class since referencing types with generic arguments requires you to know the exact argument type which means you cannot group them together. The inheritance would go something like this Vehicle : IVehicle where T : VehicleData, GroundVehicleData : VehicleData, GroundVehicle : Vehicle where T : GroundVehicleData, Mercedes_S_Class : GroundVehicle. Complexity cannot be avoided, you can only make your project simpler, which is often not what you require. I rule in favor of using both inheritance and composition, the Vehicle class should not know how the engine starts, instead it should have an Engine component which it abstracts so that a sub class can determine if it's a V engine, a rotary engine, a linear engine, etc. Anyways, this is a great video and I subscribed :)

  • @sunnysky935
    @sunnysky935 9 месяцев назад

    What if we need to introduce an engine "Ability"? There are gas, diesel, LNG, and electric. Since we have flying vehicles here, we might have jet engines. So at least 5 types. 5 more properties? Then we have a gas tank, or batteries for the electric motor. Another 2 properties?

    • @zoran-horvat
      @zoran-horvat  9 месяцев назад

      A vehicle doesn't take fuel. Its engine or engines do. Therefore, I believe those would be the properties of objects contained in the vehicle, not the vehicle itself.
      Anyway, if you really plan to model a vehicle as we know it in the streets, where real cars have anything from 1,000 parts and above, it would make little sense to try to limit the number of components of such a complex model to a single-digit count.

  • @brucerosner3547
    @brucerosner3547 Год назад +1

    I can't help quoting my attorney when I ask her a legal question: "it all depends".

  • @pharoah327
    @pharoah327 4 месяца назад

    Won't this cause us to always check each moving ability to see which is null and which isn't? Every time I want to do something with a vehicle object, I'll have spaghetti code if statements checking to see which ability it has that's not null. Don't get me wrong, this solution clearly has benefits over inheritance but let's be clear about the drawbacks too.

  • @divyanshujoshi1795
    @divyanshujoshi1795 Год назад

    Hi Zoran, love you content, could you make a series on DSA on c#

  • @andersmalmgren6528
    @andersmalmgren6528 18 дней назад

    I would have used Noop objects to get rid of the null checks everywhere

  • @aguilaaudax1362
    @aguilaaudax1362 Год назад

    Really interestering, this is the way Aristotle would prefer, do you have some book references about this design preference?

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      I can't think of any book on top of my head (other than GoF, which begins with this principle and applies it consistently). But you can find a lot of materials online.
      It could also be an interesting exercise to try writing code in some language that has no native support for inheritance.

  • @dmtree8570
    @dmtree8570 Год назад

    Thanks for the great video. I have a question about the GetAverageSpeed method: if we use it on any object that does not have all abilities that it will always return 0 right? so it is not quite useful. Was that just for demostration purposes or am I missing something? Thanks again

    • @zoran-horvat
      @zoran-horvat  Год назад

      Actually, zero result is perfectly valid and useful in that case - it says you won't move. There was a much simpler design which calculates time instead of speed, but **that** design would fail in case that none of the transportation methods is available - it would have to return infinite time, which is inconvenient for TimeSpan.

  • @aguilaaudax1362
    @aguilaaudax1362 Год назад

    If this is composition... Why you are injecting dependencies objects through factory constructor parameters (like aggregation) and not instantiating its composed objects inside the constructor (like composition)?

    • @zoran-horvat
      @zoran-horvat  Год назад

      Composition vs. Aggregation is really not about who instantiates components and when. It is about the parts - in aggregation, parts make sense outside the whole; in composition, they don't.
      In the example from the video, a MovingAbility has no meaning outside a Vehicle. Therefore, Vehicle is a composition of MovingAbilities.
      Speaking of construction, it looks like anything makes sense except an aggregation that creates parts. Since each part is self-sufficient, it makes little sense to keep them locked inside the aggregation.
      Contrary to that, by injecting parts into a composition, especially if parts are polymorphic, we make the composition more flexible. Try to imagine a composition which knows all concrete part types and knows which one to use when - well, that would certainly be the most hardcoded and rigid design you have ever seen.

  • @AndrewErwin73
    @AndrewErwin73 Год назад

    the start of this example was really better suited for an interface rather than an abstract class (or even object composition) - I agree that many times composite function/objects are more suitable than inheritance, but I don't think this was a great example. If you are going to override all of the base class methods, there is literally no need to inherit. It is code duplication. Use an interface!

    • @zoran-horvat
      @zoran-horvat  Год назад

      Isn't it off topic to ask for an interface in a demo which compares class inheritance with object composition? Like asking for an abstract class in a video on ISP.
      But, out of curiosity - what do you mean by "start of this example"? Start of the example has no features, so what timestamp would that start be?

    • @AndrewErwin73
      @AndrewErwin73 Год назад

      @@zoran-horvat I suppose you have a point. I just don't like the example. You were trying to show why composition is preferred over inheritance, but in your example, you should not have been using inheritance in the first place. I guess I get a little edgy about the whole argument because most people I encounter that argue against inheritance (and indeed, OOP in general) don't seem to know how to use it in the first place! Any tool can be misused.
      That said, I appreciate the video. And as I said, I do fundamentally agree that composition is very often the better way to go, especially in larger projects.

  • @rakkarajput
    @rakkarajput Год назад

    Zoren you are awesome,

  • @pontusschönhult
    @pontusschönhult 5 месяцев назад

    is that the strategy design pattern???

    • @zoran-horvat
      @zoran-horvat  5 месяцев назад

      It shares some similarities, but it does not share the motivation and usage. Therefore, it is not a strategy.

  • @user-nw8oi9vn9y
    @user-nw8oi9vn9y 10 месяцев назад

    How could someone implement dependency injection into the composition version of the Vehicle class?

    • @zoran-horvat
      @zoran-horvat  10 месяцев назад

      Depending on what the dependency object does, it would either be referenced by the containing object, or by the contained one. Object composition does not change much in the way we do dependency injection.

  • @SteveNgaiCheeWeng
    @SteveNgaiCheeWeng Год назад

    Can we use LSP to solve this class inheritance problem also? If yes, how it solves it?

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      Object composition is closely related to LSP in the sense that the object's components themselves are usually polymorphic. However, there is a difference between designing a class hierarchy and designing polymorphic components - the latter normally have only one level of derivation.
      That is where LSP comes to play because all variants of a single component type must fully satisfy the base component's promise. If they cannot, that is probably an indication of a need to separate that concept into two components!

  • @NGC-rr6vo
    @NGC-rr6vo Месяц назад

    really looks like a bridge pattern

    • @zoran-horvat
      @zoran-horvat  Месяц назад

      @@NGC-rr6vo I think it works the other way around. You can observe about a dozen coding patterns, most of them not more than one or two lines long, which comprises all of the programming.
      The design patterns are raising those coding patterns to a higher level, giving a scenario in which a few of those coding patterns combine into a meaningful whole.
      That is why so many design patterns look the same. What is the difference between a factory and a strategy that returns an object? How about an adapter, strategy, bridge? They often produce identical code, line by line, and the difference is only in how we feel about it.

  • @coderider3022
    @coderider3022 Год назад

    I don’t like the vehicle containing these methods and think it violates open/closed principle as we need to change this to add more behaviours. I would look at a builder pattern for the moveability and inject it into vehicle.

    • @zoran-horvat
      @zoran-horvat  Год назад

      You don't need such a heavyweight solution as a builder. The composition principle is showing a simpler solution - inject a polymorphic object that manages a certain ability and it is done, from point of view of the Vehicle class.

  • @mdev3987
    @mdev3987 Год назад

    That moving ability maybe would be solved with a nice enum? 😊

    • @zoran-horvat
      @zoran-horvat  Год назад +4

      Not really - each possibility would need to manage a different set of attributes. That asks for proper types.
      The true solution would be a union, similar to how C defined them but strongly typed. It is interesting that Rust is defining enums precisely that way: As a strongly typed union, each option with its own set of attributes, and with compiler guarantees of type safety.

    • @coderider3022
      @coderider3022 Год назад

      Not considered clean code and should be avoided.

  • @XKS99
    @XKS99 11 месяцев назад

    Are there any languages that support object composition as a first class feature?

    • @zoran-horvat
      @zoran-horvat  11 месяцев назад +1

      Every language does.

    • @XKS99
      @XKS99 11 месяцев назад

      @@zoran-horvat I mean like function composition in f# is with the >> operator. I guess multiple inheritance or interfaces with default implementations serve a kind of composition as a language feature function.

    • @zoran-horvat
      @zoran-horvat  11 месяцев назад

      @@XKS99 That is the dot operator in OO languages. Every language has it.

    • @XKS99
      @XKS99 11 месяцев назад

      @@zoran-horvat you can’t compose Class A with Class B to get Class C with the dot operator

    • @zoran-horvat
      @zoran-horvat  11 месяцев назад

      @@XKS99 What does "compose class" mean in your comment? You compose objects, and then you assign them or return for further use.

  • @EugeneS88-RU
    @EugeneS88-RU 7 месяцев назад

    Haha, this is ethernal problem. Your example of resolve this isgood. but.. there is better solution - non OOP. ECS

  • @antonsimkin
    @antonsimkin Год назад

    Doesnt youtube automatically deletes comments containing links?

    • @zoran-horvat
      @zoran-horvat  Год назад

      That is a good question - I never tried!
      It is possible that I must approve a comment with link, which I would do
      Could you try to post a link to any GitHub repo here? We can test it.

    • @antonsimkin
      @antonsimkin Год назад

      @@zoran-horvat I did post a couple of links. Dont see them after a few minuets.

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      I have changed comments policy for this video to most liberal. Maybe that will help. Thank you for making a note on the problem.
      Unfortunately, I don't see any replies held for review yet. (I also suspect that some features regarding comments on RUclips are indistinguishable from bugs...)

    • @vasoelias
      @vasoelias Год назад

      Anytime I write something or reference to my github, youtube never posts my github links in comments. Not sure if this comment will go through.. 🤦‍♂

    • @zoran-horvat
      @zoran-horvat  Год назад +3

      @@vasoelias It appears that not only RUclips deletes the comments, but it doesn't even send them to me for review. That's pretty aggressive stance towards external links.
      I have created a form where you can submit the links: codinghelmet.com/go/code-review-request

  • @kamertonaudiophileplayer847
    @kamertonaudiophileplayer847 Месяц назад

    What about space? Or land of other planets? Your design will fail. So why do not create a map of max speeds instead of a singe value?

    • @zoran-horvat
      @zoran-horvat  Месяц назад +1

      @@kamertonaudiophileplayer847 Because the domain expert says so.

  • @blo0m1985
    @blo0m1985 Месяц назад

    Code is not working if land, water or air is null, it will always return 0. You can't add null - it will return null.