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. UA-cam is aggressively deleting all links. If you wish to submit your repo for review, use this form instead: codinghelmet.com/go/code-review-request
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
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?
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.
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.
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.
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
Very well explained, as always! :) But there are multiple drawbacks in this approach! First: Nothing prevents you from calling one of the factory methods with wrong abilities (Maybe Inheritence can help out here ;)). Second: I now depend on multiple abilities that could either be set or not ("dont depend on things you dont need"), causing possible nullReferenceExceptions and confusion. Third: There IS code duplication!!!, when having multiple abilities, all of the same type (written after each other) -> When adding new Abilities i have to CHANGE (for example) GetAverageSpeed implementation (OpenClosedPrinciple violated). The Problem in the beginning of the implementation was that you tried to bound all the complexity to primitives. Why not just abtsract out that as well? Instead of defining "double topSpeed" define a class/record "Speed" and inherit from this aswell? For example "record Knots : Speed" aswell as "record Kmh : Speed". Also tackle the complexity in these ValueObjects by defining calculation of Average Speed in them? Vehicle can still expose GetAvgSpeed, but only needs to call its variant of "Speed.CalculateAverage". Or am i wrong? :) I know that all of the topics i meantined as a solution you are a master of! But my experience tell me that this example lacks of smarter implementation of inherent Objects :)
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 :)
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?
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.
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?
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.
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!
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.
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!
Here is the video I made on covariance and contravariance: ua-cam.com/video/Wp5iYQqHspg/v-deo.html Regarding volatile keyword, I am not sure that there is much audience for that topic.
Great tutorial! Very helpful. Question: Sometimes I see some design patterns that are taught with inheritance. Should those be reworked to use composition instead?
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.
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?
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.
Wouldn't it become completely unwieldy to use a class with a bunch of optional fields/methods that may or may not have been implemented? This is just a small example but it already has 4 optional methods on it. In this case they are private so they don't muddy the interface but what about public properties/methods?
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?
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?
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.
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
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.
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?
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.
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!
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?
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.
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!
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?
@@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.
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
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.
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.
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)?
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.
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.
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!
I don't totally agree with the end of your solution. Obviously, as you demonstrate, inheritance means a combinatorial design impasse. But from there to offering a superclass managing everything and its opposite. So yes, in this case, you have to compose a class rather than invent endless inheritance. But you have to think about semantics and physics. I haven't really taken the time to think about it much, but it's like causality, which asks who is the parameter of what. I see that the environment is an issue to be modeled before talking about skills.I see that the environmental context is important, and that it even determines the name of skills and units of measurement. A Vehicle admits one or more environments, therefore its abilities are indexed to the environment. I would say that it is a bit like a physics application engine which deals with elementary notions of mechanics... very influenced in its formulas and in its calculations by the environment. After reflection, I fear that only a dynamic class would be suitable in this specific case.
@@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.
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 :)
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.
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.
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.
@@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.
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.
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 UA-cam are indistinguishable from bugs...)
@@vasoelias It appears that not only UA-cam 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
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.
UA-cam is aggressively deleting all links. If you wish to submit your repo for review, use this form instead: codinghelmet.com/go/code-review-request
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
I'am not sure if I like the example, but the explanation is top notch. Thanks
i feel the same way, i wish the example was better but good video nonetheless
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?
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.
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.
That sounds like a valid use of composition.
congrats you invented ecs
@@energy-tunes lol, new skill unlocked
Zoran is favorite UA-cam Man. Keep going, please.
Hi Zoran
Every time I learn something new, whenever you publish a new video.
Great to see. Thanks for sharing it.
Glad to hear it was useful!
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.
Thank you in advance, watching now but I'm sure the content will be awesome as always :)
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
Very explanatory and useful video. Thank you
Excellent content as always Zoran!
Thanks! You can always take part by submitting a question about some of your designs where you need a second opinion or a guideline.
Thanks for the simple explanation!
You are an excellent teacher. Let me not degrade you by calling you a university Professor
Very well explained, as always! :) But there are multiple drawbacks in this approach! First: Nothing prevents you from calling one of the factory methods with wrong abilities (Maybe Inheritence can help out here ;)). Second: I now depend on multiple abilities that could either be set or not ("dont depend on things you dont need"), causing possible nullReferenceExceptions and confusion. Third: There IS code duplication!!!, when having multiple abilities, all of the same type (written after each other) -> When adding new Abilities i have to CHANGE (for example) GetAverageSpeed implementation (OpenClosedPrinciple violated). The Problem in the beginning of the implementation was that you tried to bound all the complexity to primitives. Why not just abtsract out that as well? Instead of defining "double topSpeed" define a class/record "Speed" and inherit from this aswell? For example "record Knots : Speed" aswell as "record Kmh : Speed". Also tackle the complexity in these ValueObjects by defining calculation of Average Speed in them? Vehicle can still expose GetAvgSpeed, but only needs to call its variant of "Speed.CalculateAverage". Or am i wrong? :) I know that all of the topics i meantined as a solution you are a master of! But my experience tell me that this example lacks of smarter implementation of inherent Objects :)
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 :)
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?
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.
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?
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?
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.
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!
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.
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!
Could you also make something on the usage of volatile keywords and also explaining covariance and contra variance exactly
Here is the video I made on covariance and contravariance: ua-cam.com/video/Wp5iYQqHspg/v-deo.html
Regarding volatile keyword, I am not sure that there is much audience for that topic.
@@zoran-horvat thanks a lot, I believe volatile is too vague nobody uses it
Great tutorial! Very helpful. Question: Sometimes I see some design patterns that are taught with inheritance. Should those be reworked to use composition instead?
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.
I can't help quoting my attorney when I ask her a legal question: "it all depends".
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?
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.
Best Video about composition! I'm your new Fan and Sub
Thanks! I'm glad to hear you liked it.
Wouldn't it become completely unwieldy to use a class with a bunch of optional fields/methods that may or may not have been implemented? This is just a small example but it already has 4 optional methods on it. In this case they are private so they don't muddy the interface but what about public properties/methods?
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?
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?
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.
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
Best videos ever!!!
Amazing explanation. New sub here. Keep it up!
I don't understand why Faster is implemented as an extension and not as a public static method of the MovingAbility class...
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.
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?
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.
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!
If I were to guess he was a Serbian-Hungarian from Vojvodina.
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?
@@maou_knayo It is easy to modify it on Earth.
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.
Hi Zoran, love you content, could you make a series on DSA on c#
Possibly yes, I'm not sure.
Zoren you are awesome,
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!
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?
@@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.
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
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.
Really interestering, this is the way Aristotle would prefer, do you have some book references about this design preference?
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.
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)?
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.
I would have used Noop objects to get rid of the null checks everywhere
How could someone implement dependency injection into the composition version of the Vehicle class?
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.
Can we use LSP to solve this class inheritance problem also? If yes, how it solves it?
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!
I don't totally agree with the end of your solution. Obviously, as you demonstrate, inheritance means a combinatorial design impasse. But from there to offering a superclass managing everything and its opposite. So yes, in this case, you have to compose a class rather than invent endless inheritance. But you have to think about semantics and physics.
I haven't really taken the time to think about it much, but it's like causality, which asks who is the parameter of what. I see that the environment is an issue to be modeled before talking about skills.I see that the environmental context is important, and that it even determines the name of skills and units of measurement.
A Vehicle admits one or more environments, therefore its abilities are indexed to the environment.
I would say that it is a bit like a physics application engine which deals with elementary notions of mechanics... very influenced in its formulas and in its calculations by the environment.
After reflection, I fear that only a dynamic class would be suitable in this specific case.
really looks like a bridge pattern
@@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.
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 :)
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.
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.
is that the strategy design pattern???
It shares some similarities, but it does not share the motivation and usage. Therefore, it is not a strategy.
That moving ability maybe would be solved with a nice enum? 😊
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.
Not considered clean code and should be avoided.
Are there any languages that support object composition as a first class feature?
Every language does.
@@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.
@@XKS99 That is the dot operator in OO languages. Every language has it.
@@zoran-horvat you can’t compose Class A with Class B to get Class C with the dot operator
@@XKS99 What does "compose class" mean in your comment? You compose objects, and then you assign them or return for further use.
Haha, this is ethernal problem. Your example of resolve this isgood. but.. there is better solution - non OOP. ECS
Doesnt youtube automatically deletes comments containing links?
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.
@@zoran-horvat I did post a couple of links. Dont see them after a few minuets.
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 UA-cam are indistinguishable from bugs...)
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.. 🤦♂
@@vasoelias It appears that not only UA-cam 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
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?
@@kamertonaudiophileplayer847 Because the domain expert says so.
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.