I like Jonathan Blow's take on design paradigms: " The benefits are obvious because the thing got proposed because of its benefits and you'll hear about the benefits. The cost tend to be subtle and/or swept under the rug by the people that want to sell you on the benefits." - 'Jonathan Blow on unit testing and TDD' (7:25)
I love how CodeAesethic just came from nowhere, proceeded to release some bangers and now is easily one of my top 3 fav programming youtube channels lol
Nemean was even crazier, just dropped the Quake q_rsqrt video and got like a million views and tens of thousands of subscribers with only one video on his channel
@@animowany111 Not programming, but Bobby Fingers' view to upload ratio is insane. Guy comes out of nowhere and drops a 20 minute video that is currently sitting at half a million views 10 months later.
I dislike quite a few of his videos. They feel like they were made by person that writes 100 lines of code and names it a project, and therefor it's not relatable to actual production code.
@@pixelpox11 I agree with you. Even the actual name of channel "code aesthetics" already sounds very weird, since code is not a book or poem, it's a machine instructions :)
Inheritance only works on paper because it forces you to go backwards. Programming tends to happen bottom-up, you start with some core functionalities, and write more and more stuff on top. OOP requires you to work top-down, which necessitates knowledge of the full problem upfront. That’s why it works on paper (small exercises) but fails on the true stuff. I’m not sure if you can build inheritance to work the other way round, but it’s clear Rusts traits are much closer to this, that’s why they feel nicer.
Underrated comment. I would expect if one were to poll pro-OOP vs anti-OOP programmers there would be a strong correlation between top down vs bottom up design mentality.
Depends on what your true stuff is. In embedded stuff, especially safety critical stuff you get a much tighter contract than any "big tech" company has ever written. Not uncommen to get a close to full coverage unit test alongside a project. But most of the time we use inheritance to fit some code into memory constraints. Something most desktop or server programmer don't need to bother with. At least not to the extend of, if we really need the bigger chip we lose millions of dollars.
I love how Prime calls out CodeAesthetics for the next line curly braces and then rhetorically asks whether it's C# because C# standards infamously use next line curly braces. Then I double check the code and realize that it is actually C# code.
I definitely agree with that whole "when you're shown this it makes perfect sense". Like the idea of inheritance is smart, and practical, and obviously a decent way to do it. Then you see a codebase that actually uses it and it's such a complete cluster fuck and it's so hard to keep track of what anything is supposed to be.
I think the issue is that inheritance is often taught with examples that describe existing properties or functionality. People will often use the example of having a square class that inherits from a polygon class. The issue is that this works because you’re classifying existing functionality. When creating something new, the analogy falls apart. Companies don’t have a Vehicle blueprint that they then extend to either get a Car blueprint or a Motorcycle blueprint to the build. They build the Car and Motorcycle completely separately.
Yeah I think people take it too far, I worked for a company which only employed "Senior Developers" and had 2 juniors to do really menial stuff. Some of the software those "Senior Devs" wrote had so much inheritance if you wanted to go down the rabbit hole it would take you hours to figure it out. Thats really bad code architecture and I don't think you can call yourself a "Senior" anything if you don't think about how your code reads to a person who's not familiar with the structure.. At the same time though, these devs would be very critical of how you named your classes and methods, so you don't care about how your code reads but you really care about what a class is called? That doesnt make any sense to me.
doesn't it just depend on how it's implemented? For example, in my game, every item inherits from the base world object class. This class handles temperature and other things like that, so anything that affects temperature can easily do that. Then an example of an item is a tool. The tool has base things like, how it can hold items, how you can remove items from it, and whatnot. And then there's specific tools that have their own specific behaviors for items in the tool. I just want to know, how in god's name would you approach any of that without inheritance? I genuinely want to know. And I think if done well it's just common sense what things are supposed to be
@@moonashaDoes your game have weapons? If so, would a weapon be a subclass of item? What would happen if I wanted a hammer to act as both a tool and a weapon, or a baguette as a weapon and food etc. This flexibility might not be necessary for whatever you're building but it's something to consider
Yep, and honestly there’s some codebases that spoil us rotten and make it seem so good because the focus is so constrained that object orientation is good. Perfect example is game engines and really popular large web frameworks. Even then, there’s limits and people end up running into issues. Want to see a HORRIBLE OOP library? Read LangChain, and try to figure out how Conversational Agents pass through chat_history, this nearly drove me mad. If you’re wondering why it’s that way, they release a new version of the library roughly every 1-2 days (absolute chaos of a commit history) and they prioritize maximum flexibility above LITERALLY ALL ELSE.
@@JohnZakaria I really don't think that calling codeAesthetic the 3blue1browne of CS is very accurate, programming is significantly more accurate. Like the topics he covers is: - how to make code easier to read - how to make code more extendible - how to design code effectively (yes this is a mix of the above). What he isn't covering is: - how the computer works fundamentally - complexity analysis - math What he's covering is significantly more accurate to call it programming than CS. And this isn't a bad thing btw. I find it funny when people think monkeybrain "CS good, programming bad". But saying that, they aren't the same topic, computer is understanding why we design code, how we can analyse code, and even lower how a computer works. It's even to find the limitations of code. Programming is a part of CS, and this is what CodeAesthetic is explaining (really well).
I know it's an old video but I'm so glad I found this channel. RUclips addiction is my greatest struggle as a junior dev that just has been coasting in his work. I know this channel is primarily entertainment but replacing some of the political trash that I normally binge with your content is starting to improve my mindset and problem solving skills and I'm noticing improvement and growing competence at work as a full stack dev. Coming from a game dev degree that I'm sometimes not proud of you're really helping me fill gaps in my knowledge base that I missed in my formal education. That said I'm happy I went to college for game dev, got me into coding and even if I was disillusioned by the industry during salary negotiation it got me started down this path and it's something I'll never take back.
Inheritance has its place. It can definitely get out of hand. Composition has its issues, but he made the point that some of those issues might be due to putting too many responsibilities on the composes objects. Some of the issues with inheritance can surely be traced to the same.
Never heard of the triple method but thinking back, its seems to be categorically true in my experience. I've seen lots of new products fail after months of effort because we didn't know all the things we learned in the first attempt but the 2nd pass usually ends up shipping and doing well. Whenever I've worked on proprietary framework level things it does seem like it takes at least two but often three passes for it to be fully baked and desirable to the org.
The file savers should really just be functions that take some sort of writable object, an image, and options. There really doesn't need to be a class, since that just creates an unnecessary alias to the image and options.
2:40 that is because they don't tell/show you how painful it is to change the structure of it after it has been coded . They show it from the perspective of a world where everything is easy, and is correct on the first try. (I have not gone to college for programming, so I don't know if that is the case, but it sounds like that is why)
I have studied OOP in college a long time ago and this is exacyly how it is shown, OOP has its place but we have gone too far in making everything abstract and that in turn made things worse.
Schools teach you how, not why. So when they introduce a new hammer, people want to try and hit everything that looks even remotely like a nail with it.
"Avoid protected member variables with direct access" - when I was learning Android Development in Java, this KILLED my motivation entirely when trying to reason with the code, some view operations intensively use protected members from the parent class and I kept asking myself where those members were coming from...
Just add correct code convention. m_ - protected member variables. p_ - private variables. Well in any case you can just look into a codebase and find what this member variable is actually doing. I didn't really understand your concern.
The biggest problem with the term NoSQL is that it's like "We run NotLinux on our servers"... well, what DO you run? Windows NT? FreeBSD? QNX? iOS?! FreeDOS?! "Everything except ..." is a worthless way to group things.
I quite like these 2nd channel videos. The volume of clips and more of a focus on Actual Programming concepts, rather than the 'hot-takes' to just drive engagement like the main channel. I like seeing the M who just likes to help people get better.
I love my next line squirly braces... especially in a language where things can occur between functions and their scope, such as declaring a function const, or member initializers, or metaprogramming(?) such as std::enable_if, or any other number of things. Lines come free, readability doesn't. MyClass::MyClass(int value_1, int value_2) : member1(value_1), member2(value_2) { //stuff }; ^^ isn't too bad until you have longer type names, or more parameters, or more going on with the class/function/etc. MyClass::MyClass(int value_1, int value_2) : member1(value_1), member2(value_2) { //stuff }; ^^ is so much easier to read and is much easier to be consistent with in different situations.
24:06 This describes my hatred of DI *frameworks*. I love dependency injection and I use it a lot in Go, but the dependencies are created manually and passed around in code in such a way that's easy to trace and debug. I hated DI in other languages (C#, TS [Angular], PHP) because it almost always involves some sort of DI Framework that over complicates everything and turns DI into a magic global black box that's a pain in the ass to debug and manage configurations.
9:15, there should be quotes around the word perfect in "change is the enemy of perfect design". A truly perfect design enables and embraces change and provides seamless extendability without needing breaking changes.
dependency injection was our bread and butter in high school robotics. We split the robot into subsystems and everything would be injected into relavent methods and classes. Was a massive pain
Data duplication is good when it leads to a more decoupled codebase. You don't always want a giant users table with 100 columns because every part of your system needs parts of the table. Adding an extra column suddenly breaks everything because other parts of the project are now trying to insert null into the new column.
I like your point at 10:00. First build is discovering what you don't know. Second build is learning the things you didn't know. Third build is starting from the beginning with full knowledge.
the rust default impls of traits is available in C# as well (also C# allows Data Members in interface but not entirely since Properties are methods and not fields so it feels like it does when it actually doesn't)
I really connect with Prime's idea of 3 stages of writing an application. Coming from embedded development, I am very used to separating data and methods on data, and even now that I work on higher plane of abstraction I often always write my first version as a combination of data containers and methods on them. What I find is that often times on subsequent re-factors that pattern alone is sufficient without even going towards OOP way of structuring your code. I am not generally opposed to it, but in my experience it's far easier to first do the simplest thing first (data + methods on data) and if you spot patterns - refactor them however it makes sense to refactor them. Using classes to structure your code from the beginning IMO is hard to get right even for experienced devs, inheritance or not, you are still effectively dealing with lots of local namespaces and are dealing with "this object owns this and that object owns that", if you then need to share some data, it's more work to make these changes compared to if they were simply passed in as a parameter to some method I also feel that having a very clear way of identifying data within your program is often very beneficial and illuminating. I suppose this is more towards data oriented programming approach
My experience is that if you're having a clump of data, and then some operations on that data, it's pretty easy to get it done using OOP. Will you have to refactor a lot? Maybe. But it's pretty easy to think in terms of. Then again, I model the database FIRST these days, as nailing that is pretty reasonable, and then the OOP layer on top handles messaging between the data model and the front end. So it's like the controller in the way MVC was originally conceived. It works really well for enterprisey solutions.
That description of developing software in a 3ply-fashion is brilliant. Totally tracks sometimes you build it to understand what it does, then build it to make it ready for others, then build it to make for you and others in the future.
I did java for several years and i suffered at the hands of forced inheritance. Now i use literally anything other than java and i hate inheritance because it overcomplicates everything and forces you to fit the problem to the code rather than the other way around
i use inheritance only for derivatives, the child classes only add new functionality, they may or may not alter some functionality (i usually prefer not to), and maybe implement some virtual methods to fit an API (or use interfaces) aside from that, i avoid it like the plague, if it's not needed, dont use it
C++, JavaScript, Python, C#, TypeScript and fucking COBOL has inheritance, and are fine, but not Java? Do people just not know how to write Java or something?
@@HrHaakon I almost didn't reply because your comment severely missed the point, but I have a minute so I'll explain again: Java forces you to use classes and heavily encourages you to think about the problem in an object oriented way. (You don't have to of course if your classes are just function containers or bags of data but that is terrible Java. and as someone who knows how to write Java I'm sure you wouldn't approve.) This is not good because most problems are not cleanly represented in the pure OO inheritance world of Java. So while other languages may have inheritance, they also have more sensible ways of modeling code so that you never have to touch inheritance. Caveat with TypeScript: you should know about prototypical inheritance but you rarely have to actually use it. You can write a NodeJS application in TS that doesn't touch prototypes or explicit classes ever.
@@HrHaakon I didn't sneak it in. I made it pretty clear that you don't have to use inheritance in Java but if you don't then you're writing what Java people would call "bad Java." Maybe next time read the comment.
i put squiggly braces on the next line in protest; if a language is forcing me to define blocks with braces, then I am going to make them as prominent and annoying as possible
~8:00 OOP inheritance can easily go out of hand when doing games, classic orc merchant npc issue. It's not nearly as bad when you have hard defined scope, although thats probably the crux that you tend to expand that scope over time and now suddenly your child classes bring along garbage thats only relevant to some children and you need to override stuff
The solution to the image problem the video presents could be far more easily solved in practice with a single Image struct and some associated methods for editing + separate load/write functions to different image formats (maybe a ImgConverter interface if needed). The video is good but usually these OOP examples are overly complicated for no reason other than trying to fit a mental model of classes
That's the point, though. You have some class that works just fine, but then one day you want to extend it. Doesn't matter how cleverly your inheritance structure solves the current requirements. The moment a new requirement is added, the resulting refactor is huge. Sure, you can solve the problem given here, with a drawable / non-save-and-load image just fine if you know that that's a requirement from the get-go. But then something new comes along.
Actually the method I proposed is way more flexible, as the image manipulation logic is separate from the conversion logic. Want more formats? Simply write a new save/load function pair and pass an Image struct to it.
@@MasterHigure The problem with inheritance when I used it (maybe bad at OOP) is mutation/global state is order dependent, and if you inherit something the child classes may sometimes want to call the super method first before the body sometimes last and it could lead to subtle bugs because of state, you sometimes have to have the super method be a higher order function so it can be called around your code. Also sometimes you have to have the parent call the parent version of a method other times you want the parent to call the child method...
So true. When learning programming, inheritance seems so logical and endlessly useful. Yet much of the more advanced programming literature/videos points out that composition is often better.
Is it though? In a dynamically typed language, if I implement a common set of operations on some classes, then I have implicitly implemented an interface. That Java requires implementors to be explicit doesn't suddenly make it inheritance. There's also the question of what is being inherited: besides default methods, there isn't any behavior being shared by an interface to its implementors. I don't think there's much inheritance at all.
25:03 Yeah, I've thought about this a bit and have been curious if it's not a bad idea to have an `embodies`, `compose`, or a similar keyword. The idea would be that within your class you would say that it embodies another object, which you can then access it's methods as if it's your own, but they would be private. Mostly a convenience feature, it would benefit from the IDE informing you which component you are fetching the method from, but I haven't thought of a scenario where it would be dangerous (yet.)
I believe pretty strongly inheritance is only good for composing orthogonal behaviors. Inheritance for specialization opens the door to overriding, calls to super and having to understand multiple versions of the same artifacts but jumping back and forth between modules to understand what each does. My brain doesn't compute that.
This is the first reaction video I liked - ever. You complement each other so well. It is like you inherit his evil genius and implement an emotional amplifier of profanity into it.
I agree with you on smooth scrolling. There's a reason why scrolling was originally in steps and it's because it causes less motion sickness. I am not particularly prone to motion sickness, but I find it much harder to read stuff when they're smooth scrolling, even on a 144hz monitor. The greatest demonstration of this are youtube live comments vs twitch, where the former is smooth scroll and the latter is bulked into steps. Twitch chat is legible when it's being -SPAMMED-, I'm talking 100 messages a second spammed. RUclips chat is barely legible with 5 messages per second, because it's constnatly scrolling up, there's no rest for your eye to actually read the words.
This video so purely speaks to an idea I've been thinking about for a long time. I keep seeing these videos floating around about why OOP is a lie, and why it doesn't do what it was made to do. Which was solve the lack of modularity in imperative programming. To be fair, it doesn't solve that problem, we have modules now. However, this is exactly why OOP has evolved over time. It's not about trying to make infinitely reusable code snakes, it's about breaking down large frameworks into easily definable objects that can be instanced to serve a higher purpose.
I clang-format on save with whatever defaults it considers the best. So because IDGAF enough to create a .clang-format file it does my braces on new lines. At first it would cause me geniune aggrievement but honestly you learn to accept it and move on. I work with C++, my life is shitty enough.
Now I understand why Rust traits work in such a way. It has the advantages suggested in this video, without the disadvantages that comes from inheritance.
@@betterinbooks I agree. Once I was coding a server backend. I created the struct for the internal state that is going to be accessed by the endpoints. I then created a trait with the methods for accessing the state. For each endpoint, I created a unique trait for it with the behavior of that endpoint. The trait applies to any struct that implement the original trait type. Thanks to that, each endpoint is its own trait. We just have to implement these in the state type. Instead of one file with all the business logic, I was able to divide each into its own trait. Extremely easy to scale. Loved it so much. Using that structure, we could have several people working and pushing to the same repository at the same time. Each developer needs to simply work on their trait.
as a silver haired coder from the 1980s OOP & inheritance was the bees knees. on paper. awesome. in reality - i watched programmers inherit the crap out of stuff, till it was just crap. So deep that even a Matryoshka doll would have gasped. also kudos for your music tastes there with miss monique.
6:25, just use the virtual keyword instead of abstract. This will allow you to define and write code in the function inside the abstract class. This allows you the ability to override it in inherited classes while not being forced to.
10:05 A typical conversation at my job: I: Should we really change this thing to work totally different? It will be difficult to restore it. I'll remove all the stuff I don't need anymore. > Sure, just change it. We decided for the perfect design, it won't change anymore. A few months later: > I got an idea, for a new design, we should do it like this. I just think, that's almost exactly how we already had it.
Commenting on the vid up to 6:58 - The problem here is not that inheritance is bad (which it may or may not be), but that load and save have nothing to do with an image. They are actions you want on a file, not on an image. Jpeg, png and tiff are also not "kinds of images", they are storage formats for images. One part of the concept of inheritance is to use it as an abstraction of reality, that makes it easier to handle the aspects of reality that are relevant for your program. If you choose the wrong abstraction, of course things are not getting easier. But complaining about it is as if you would say woodworking sucks because you ordered a bed and the carpenter made a chair.
Straw-man argument. He uses a contrived example, a bad example, and then blames inheritance. He couples persistence with the image type when he should have created a separate tree of persistence types that takes a stream of bytes and then used them as separate but cooperative objects. I could claim that composition has problems as well by creating a badly composed object and then showing the coupling issues. This kind of video is useless. He should be showing how to properly design and use the classes. Anyone can make a video that shows that a screwdriver is a terrible hammer.
Code Aesthetics messed up with the inheritance diagram right before moving on to composition. The object on the right started with only blue and red square but ended up as a child of the green and blue object.
6:32 How about having a File class and an Image class. If a particular subclass of Image needs File I/O, then let that subclass extend the File class. If not, then the subclass of Image doesn’t have to extend the File class and hence doesn’t have to implement the File I/O methods
This feels like almost an exact replica of a Raymond hettinger talk, but there was obviously a lot of care and love put into visualization and video development.
It's indeed the best way to produce quality software. Developers who think they can do it right the first time just haven't learned that lesson yet. RIP Harambe
How to execute: 1. Dicks out for Harambe 2. Shit code 3. Full rewrite no matter what "statistics" tells you Took me over 10 years to understand how true this is
I got to work on an old project where all the important bits happened in business objects that were pure spaghetti of inheritance. EVERYTHING happened through protected variables and methods. You would see a 5-line function and think that that should be easy enough to understand... but then it ends up triggering 500 lines of code across 5 levels of inheritance. And it would go up and down, up and down... Line 1 out 5 executes something in its parent, but that is passed right through to the grand parent. So the grand parent does some things, then passes it to the GRAND-GRAND-parent, which executes like 10 lines before it goes back to the grand-parent. The grand parent does a few more lines before calling something that passes through to the GRAND-GRAND-GRAND parent... which calls some completely other module... and from there it falls back to the original child object I was working on. One line done, get ready for 4 more...
50+ years of programming here. Who does curly braces this way? I grew up doing brackets the more typical way. On my own I figured out why this way is for me enormously better. The reason you do curly braces this way is that you can visually look down the left side of your code and quickly find out if you have brace error. If you put the opening bracket on the same line as if, while, do and switch, your eye has to look right to see where the end of the statement is, and whether it has an opening bracket. I can look at the code at 1:43 and within a second see that all the brackets are where they should be. This is so much easier on my eyes that the first thing I do when I inherit code is fix it up this way. If you don't like it, do it any way you like, but don't criticize what you don't understand.
How about people think long and hard about what their classes look like before writing them. Why the hell would an Image have any concept of save and load when the sole purpose of an image should be to represent an image as data? Having anything extra is over abstracting, class should be the bare minimum and nothing more. I'd keep image the way it was (without save and load, and maybe even the transformations) and have the abstract class for saving and loading. Interfaces to me are for functionality which isn't restricted to any concept. For example if you wanted to have a collection of things which are all unrelated but all have save and load then I'd use an interface, that way I can just create a collection of what ever interface, ex: ISerializable, IEnumerable. If I want to save and load strictly for images, I'd have an abstract class inherit from Image and put abstract save and load methods in there. Now I can have a collection of images which can contain all image types as well as a collection of all "FileImage" types if I wanted only images that have save and load. This also doesn't break implicit conversions from Image to any FileImage. And for those saying null-conditional operator is hiding a problem, I'd argue that it exposes a problem that was previously hiding. And null in itself is not an issue, its like saying instead 0 as a guid is an issue, its just a state. It is your responsibility to handle what state its in. Asking to get rid of null entirely is like asking to get rid of 0 entirely.
4:15 Why is he putting save and stuff on the image? Shouldn’t it be something like an image serializer that takes in an image and then saves it? So that the memory representation of an image can be abstracted away from the persisted form
You could even invert that relationship so that the image takes in a serializer, allowing you to preserve the load and save methods that are directly on the Image
Those curly braces on the same line are standard practice in C#. Any linting tool would complain if you did anything different to that. They are called automatic properties and they are awesome.
The one thing I've never understood is why full on complex class objects is considered better code reuse than separate functions for common tasks. Like classes make sense if the class covers every single use you have, but that's rarely the case in any moderately complex system. I guess this is what they mean by composition over inheritance, but inheritance seems more like a convenience and organization advantage in certain cases, not a code reuse advantage.
An example of where inheritance makes sense is Pokémon: you have the definition of a pokemon(name, evolution, moves it can learn, abilities it can have, base stats). Then you have a class to express a pokemon in your team (has ivs, evs, only four moves out of whatever it can learn, a personality, only one ability) and from the pokemon class it helps to validate if it can learn the move, calculate actual stats based ob ivs and evs and so on. But then I wouldn't do a battle class that inherits from pokemon, items and so on
Imo the only good way to have this conversation is by looking at what the compiler does. For example each time the compiler sees a dereference (dot operand) it performs an extra instruction for null checking, while local variables are accessed directly. And if we get even more exquisite... Local assignment(dereferencing a field to the method stack is supposedly even faster... But the assignment tradeoff needs to be matched against the number of times the reference will be accessed.
The performance hit is probably insanely negligible since many instructions are pipelined and executed partially in parallel. By this logic, all OOP languages would be slow, but they arent Inheritance is not bad. The code reuse alone is much better than any type of convoluted dispatching argument. The best, most bug free code is code that is never written or re-written. Without inheritance, you are guaranteed to write code over and over for no reason. People just really dont understand inheritance in my opinion. And if you are so concerned about performance, write everything in assembly.
@@jshowao I like OOP, but I won't deny it's drawbacks, specially in such limited scope tests like a simple loop. JIT is better at optimizing code than a well written C, maybe even a very good C. But the time it takes for the optimization to kick in... Takes some time. Another thing about VM languages is that their compilers are really not that smart... Maybe because of exceptions and extensibility but devirtualization should happen a lot more... yet is something that rarely happens. Each virtual call adds a whole instruction... which eventually adds up... Each extra instruction call is ONE LESS op code of instruction sequence, which is one less op code for the speculation sequence. The worst offender is that each virtual function call forces a memory order boundary, which means that the compiler is not allowed to interleave beyond the virtual function call... this is the worst offender.
@@jshowao I love OOP, I just wish compilers were more confident when devirtualizing and parallelizing up front, maybe even add keywords to hint devirtualization. Also I'd love for compilers to not implement 'freezing' so strictly, instead make a flow graph and ignore the constructor memory barrier if the logic allows it... do it up front instead of JIT. This would allow swaths of lengthy op code sequences... Making massive and faster speculations uninterrupted. I keep being told that if full de-virtualization would be applied, some pattern would break when dynamic dispatch is not obeyed, yet I keep getting some weird/nonsensical and incomprehensible examples of this...
What do you think of query builders? I honestly can't stand query builders for sql. I'd rather look at a basic string with some replacement keywords or embedded variables.
@@ThePrimeTimeagen just... Why should I be learning a new structure/language with your query builder? If you just keep it as sql I can read it without having to figure out your custom representation of a query.
String replacement can take a long walk of a short pier, but SQL is better than 90% of everything else the SECOND you're no longer doing basic CRUD that's 1-1 with a single table with no joins.
11:34 I disagree on this one because of two reasons: 1st If you put all common things into base class and then it turns out that something is not common then you basicly put non common things into base class. Your design is bad and not the inheritance. 2nd Inheritance doesnt force anything on you. Private, protected and public scopes already solved that. Again, it is your design that is bad if you boundle everything together mindlessly, not the inheritance. Composition and Inheritance in many cases is actually same thing if you dig deep enough to realize it, but for some reason Ppl are glorifying one over another.
Hey! I'm not sure that it's actually `The flow of Inheritanece` Inheritance meant to encapsulation data with behaviors that it can manipulate. And subclasses will inherit those and may argument a superclass. He started with good example with User/Admin (we can have more subclasses). But then he change to `Image` class example that already has problem that not really relate to inheritance itself. `Image` class encapsulates data about image and how to manipulate it. But then he adds methods save and load that represents totally different domain. So now we have two different domain in one class Image knowledge and how to convert it into another format. This now violates single responsibility principle. Those two thing should not be in single class from the beginning, it should have been `Image` and `ImageDrawer`. That why OOP goes with SOLID to guide what should be in Object (class) and how to identify domains. This not a really flow of inheritance, it's more like particular case of misuse of inheritance. Another popular one is for "code reuse" that also in most cases solved by composition. In my experience those are two most frequent examples when inheritance causes a trouble. But it's not because inheritance is bad but rather it was applied incorrectly.
The thing is, inheritance asks you to come up with these taxonomies of objects you're dealing with (and maintain them as your code evolves). It makes sense when you're dealing with toy examples (cats, dogs, etc.) and also for certain data structures, but in general your types can't always be grouped into a neat, reasonable hierarchy like that. Inheritance is very often used is when you're dealing with values that need to have the same type (work in the same places) but have different internal structure and behavior. Even though this may seem like the entire reason for inheritance, it isn't exactly the same as just having a taxonomy of objects. This 'same type; different structure|behavior' has two sub-categories: when you want the number of sub-types to be fixed or when it should be extensible. Some languages call the first kind 'sealed classes' and often you can achieve it by using certain access modifiers. Inheritance can be used for both of these categories, but the first one will usually be kinda awkward. Contrast this with proper tagged unions, where you can inspect the value to see which sub-type you got and dispatch the behavior based on that. This way the code that uses the value contains the conditional logic, instead of spreading this logic over class definitions for each of all the variants. The second category is a much better match for inheritance, but frankly doesn't really need all that hierarchical coupling that inheritance requires you to come up with. The alternative is lightweight interfaces. Not achieved through sub-typing but just by constraining your generics with statements like 'requires Foo to be implemented for T'. Finally there's subtyping itself. It really is tied to inheritance, but I'm yet to see any code that actually needs it. If the already mentioned mechanisms aren't enough you can always use explicit cast functions - they are fully equivalent to subtyping. (given x : S and S ⊑ P then x : P, so x can be used in every place where values of the parent type P can be used) (given x : S and c : S->P then c(x) : P, so it can be used in every place where values of the 'parent' type P can be used) Give me inheritance example and I'll give you the same thing with just interfaces and tagged unions.
@@mskiptr Hey! That make a lot of sense. If we would look more about class vs interface. There both can be used interchangeably in most cases. (some languages require interface to implement classes and some don't even have interface at all and it defined usually as a class with methods that have exceptions in the body (ruby, python, php before typing)) They are both reference types and contain methods and variables. Most difference is: - constructors can be inherited in class not interface - method can be inherited in class (and can call superclass method) but not interface - classes can be public, private, or protected but interface always public - interface can combined from multiple interface but classes usually only one (some langs has multiple inheritance) How I understand it: class let you inherit share and behavior when interface just define shape class gives you control over who can do what with it when interface free to implement by anyone. Example for interface are shown on the video. It make a lot of sense to just define `ImageDrawerInterface` and ask for something that implement it from `Image`. In other words `Image->save(ImageDrawerInterface)` just say "Give me anything with that shape, I'm just going to call a method `export` (or better name) on it with certain format of data" In this case `Image` does not really care what it is. It not going to pass it around internally, just shove data in it in some format it does care if it even works. Other example when dev actually what a control. For 3rd party library like this you can create instance `Image` but there can be other things like `Viewport`, `Rectangle`, etc that will accept instance of `Image` as an argument. so they will use it to set `Rectangle` background as an image for example. In this case if someone inherit from `Image` and pass it as an argument there is no guaranty that `Rectangle` won't brake because of modifications made to original `Image` class. So he make it private and that guaranty only valid set of classes can be passed. In continuation of that we may have different assets like this: `Image`, `VectorImage`, `Clip`, etc. And the all can be argument to `Rectangle` (or else) for example. But we do not want to define list of things every time that would be to much (and every time we add update all places) So we can create a superclass for them like `Asset` and then would just need to check if argument is `Asset` or it's subclass. Another typical and often use case is Single Table Inheritance (and more design or DDD patterns, etc). If we go closer to real world there will be multi dimensional spectrum of what works best for each individual project/case. There will be spectrums from: typed systems to not typed, functional - oop - declarative, relational - data driven... A lot of languages implement different ways to implement things to developers can choose what works best for them in exact situation. It also influenced by the history of community and frameworks. So I believe there is not "truly proper way" of solving a problem. It's alway about exact problem and exact language, and exact level of developers involved in it, and ... Not to mention every language implement classes differently and some don't event have nor classes interphases at all :sweat_smile: (hello ruby love you) Thank you to reply. That really helped me to think more on that and finally make this idea to the paper. On my original comment it was more about that in `The Flaws of Inheritance` video everything started with a not proper use of inheritance at all and then we have a great solution using composition. I just rather name it `Misapplication of Inheritance` or smth on that sort. Current name make inheritance like 'evil'. Or maybe I'm just don't get clickbait titles. Any way. Thank for reply
@@fanantoxa Well, I wrote about 'interfaces' because that's the most mainstream word for what I meant, but I really had in mind the kind that is known as 'typeclasses' in Haskell or 'traits' in Rust. They are more expressive and more general then what you can find in e.g. Java. Yes, they very much can define the shape of your data (at least in a certain sense) but ultimately it's the only thing they do (in that very sense). The most straightforward examples include mathematical properties like Equality (there's a function that when given two values of type T will tell you if they are equal), Comparable (there's a function that given two values of type T will tell you which one is greater) or Monoid (there is an associative operation that when given two values of type T will produce another value of type T and there's a value of type T that is a neutral element of that operation). Still, you can use such interfaces for much more down to earth tasks - like converting various data types to regular Strings. Such interface requires implementers to provide a tostring function of type T -> String, with that T being whatever type a particular implementation is concerned with. And there are also some in-between cases. Interfaces like Mappable or Foldable can describe shape of your data structures. They require the implementations to provide functions like map (of type C -> C) or fold|reduce (of type (((B, A) -> B), B, C) -> B). (This is where my pseudocode for generics and other type parameters really broke down. It's much neater when you can actually describe types of your types.) And why did I say it's more expressive? a) Because in principle you can have interfaces that take a different number of type parameters - ones that relate two or three types together. E.g. Castable which requires a function of type A -> B. b) Because you can implement generic functions that ask the input or output types to implement some interfaces - possibly several at once (certain argument needs to be both Comparable and possible to convert to String).
@@mskiptr Cmon mate. There is not "right" way to do things. Haskel and functional programing is not "silver bullet" and never will be nor the OOP. And if we check most languages are not pure but support both paradigms. You can't build one without the other. Question more how much of each you use? When we talking about pure data manipulation, parsing, calculations, etc. Functional programing usually is the best choice. It easy to build "pipes" to transform data and combine them together. Data just get thought the system. And they very efficient. But when you need to describe an entities and relation between them. And build whole business layer on top of them that where OOP shines. It allows to build system that works for users and business processes that often are not logical. And also you kinda have control over your entities. My take is not going to change: There is not best language or paradigm, or best way of doing things. There is plenty of languages, typing (or not) systems, paradigms, etc. And all of them cool! But at all of those are just tools to solve problems. And depend on problem and resources there will be different "more appropriate" solutions. You won't use Haskel to build classical rest API, same as won't use Ruby on Rails to build high performance computation model, or Elixir to build a game. (I mean you can, but it won't be the most appropriate solution to build, extend and maintain) All we can do at best is: "what is the best fit solution I know I can use here?" and response will be changing with time and developer experience. Nothing is perfect. It's all about needs and trade-offs 🙂 Tnx for discussion
Code Aesthetics used an awful, contrived example. I could have easily provided an equally awfully examples for other OO constructs. You typically don't use inheritance for this purpose...I definitely would not have implemented the image save functionality like this... you're breaking Single Responsibility - an image should not save itself. Inheritance works in scenarios where you need to allow specific call outs to override base functionality - the class MUST adhere to Single Responsibility. For example, a class generates some JSON based on a supplied data structure. In certain scenarios you need to override the JSON generation based on specific condition(s) in the data structure. You have five options... 1. If Statements for the conditional logic - Arrgghhh - Let me out of here. 2. Just copy the code and implement your new logic - bad for obvious reasons. 3. Use some sort of weird composition where you pass in an object which performs the specific implementation - bad approach as it means a build up of extremely fined grained objects which can be difficult to see how they relate to the base object. 4. Function Pointers/Events - You pass function pointers/delegates which are then called by the base object. This is a better approach but again you need to inject something into the core object to make it function the way you want. Events tend to work better, but you end up with the same result - You have code in the calling method/object/function containing the callout. 4. Overridable Methods - Inherit the class and override the method to handle the specific generation. Object creation is easy (you don't have any daft objects or function pointers) and the code's intent is clear. You musty however adhere to single responsibility. And in total agreeance with the 2 or 3 times - especially with inheritance - you need to know where your 'extension' points are going to be.
CodeAesthetic is like DI: it works till it does not. His take on premature optimization was invalid simply because he did not understand the original quote (probably because he provided only the part most people know, without the broader context. Many such cases) and decided to defend the frankly awful practice of treating the performance like some kind of a trade-off that you can't have if you want your product to be scalable and maintainable and other big programming buzzwords. He also subscribes to this asinine idea that you should write comments only in very specific cases because something something a good-written self-documented code, which is another of those ideas that are great on paper but falls apart when you come down to the trenches and have to spend days, maybe even weeks, to understand the code.
companies should be interviewing people by having them configure the linter for the language they’ll have to write. nevermind if they can cook up a linked list problem in 40min, find out of they want all code to be wrapped to exactly 80 columns and convert tabs to 5 paces
my current strategy is, "Can I do it with an interface?" If the answer is yes, I don't use inheritance. There are some cases though where inheritance is super useful, but I live mostly in the game coding world so it probably doesn't apply elsewhere. As an example, a game would have a base object, that keeps track of temperature, who owns it, etc. Then more specific objects with certain functionalities can inherit from it, without having to duplicate a bunch of code. Basically for me it comes down to how much code you're duplicating
You create classes for each of your properties, like temperature, and add them to the class of the object you are creating. Then, you make a factory with methods for instantiating your object with valid combinations of your property classes for creating different types of objects.
Java Swing goes full in on inheritance, and it's the only one case that I found actually useful and easy to hook into. Building UI, quickly changing a draw method here, or making a table behave like a tree. Once I got it, it was really easy. But designing that stuff myself to be flexible like that, no thanks.
I great thing I heard to use was "Is" vs "Has". If something "Is" something else, then you use inheritance. If something "Has" something else, you use composition. For example, "Is" a Png an image? No. Png "Is" a format for saving images. As are the other formats. So we can separate these from image and have them inherit from some ImageFormat class which contains methods to take in an image and save it and return a saved image. "Is" a DrawableImage an Image? No. But a DrawableImage "Has" an image that it can draw to. So we can separate DrawableImage to its own class, and give it an image to perform drawing operations on.
Did XNA in college senior year, I loved it. Glad to hear I wasn't the only person who used it. I made a pacman and tetris clone in it and was so excited to deploy it to the first generation of windows 7 phone. RIP windows 7 phone, never got flash support and was too late to the market
I also made my first game with xna, a tower defense, and I also had like 4 levels of inheritance for only the attack effects lmao Oop obsesión was so bad when I was young
Inheritance is more about modeling the domain and that subclasses ARE an instance of the superclass. Composition is about HAVING something. Those that argue they are solving the same problem are mistaken. Interfaces/traits are for a shared set of behavior with different implementation. They are all different and have their place. The problem is when a language forces a developer to only use one.
7 месяцев назад
I do inheritance, but it's almost always just one level, very rarely two. I often need very slight variations on different objects, and majority of the functionality comes from the parent. If some of those variations are needed in multiple classes (but not in *every* of them), then they belong into interfaces and become composition.
When implementing a compiler inheritance is elegantly straight forward. Functionality is presumed from the way objects are structured in memory. Where as composition there has to be an active mechanism in determining if such particular object contains a particular functionality. Maybe inheritance is a house of cards that may apart due to design issues. But when it works it should be more efficient then composition
It just depends how many "branches" of your core class there end up being. If say there are 30 variations.... then you'll probably have a hierarchy of inheritance with 3 or 4 "generations" to stop objects from inheriting too much. That can require a lot of pre-planning and can easily end up a mess. I'd usually prefer to use the modular approach of composition to form each sub type, but it really just depends on how many variations we plan on using and how worried we are about memory.
10:15 This is why agile was implemented because waterfall developing might produce completely unnecessary products. Its funny how you seemingly never make the connection, either in this video, neither in the agile video.
So...I don't see why the proposed solution isn't functionally equivalent to multiple inheritance. The entire idea of interfaces being "lightweight" but (in some languages) allowing you to give default implementations that reference other methods of the interface... isn't that just a smaller parent class? I don't see how inheritance precludes any of the solutions to the given problems. "ImageFile" could also be an abstract parent class, as could "Image." Then your JPEG, PNG, and Bitmap all inherit from both parents, while drawable only inherits from "Image." Am I missing something?
I like Jonathan Blow's take on design paradigms: " The benefits are obvious because the thing got proposed because of its benefits and you'll hear about the benefits. The cost tend to be subtle and/or swept under the rug by the people that want to sell you on the benefits." - 'Jonathan Blow on unit testing and TDD' (7:25)
Amazing commentary
TBH unit testing certain stuff is a godsend, but TDD is absolute nonsense
I love how CodeAesethic just came from nowhere, proceeded to release some bangers and now is easily one of my top 3 fav programming youtube channels lol
Nemean was even crazier, just dropped the Quake q_rsqrt video and got like a million views and tens of thousands of subscribers with only one video on his channel
@@animowany111 Not programming, but Bobby Fingers' view to upload ratio is insane. Guy comes out of nowhere and drops a 20 minute video that is currently sitting at half a million views 10 months later.
And mia for a while
I dislike quite a few of his videos. They feel like they were made by person that writes 100 lines of code and names it a project, and therefor it's not relatable to actual production code.
@@pixelpox11 I agree with you. Even the actual name of channel "code aesthetics" already sounds very weird, since code is not a book or poem, it's a machine instructions :)
Inheritance only works on paper because it forces you to go backwards.
Programming tends to happen bottom-up, you start with some core functionalities, and write more and more stuff on top. OOP requires you to work top-down, which necessitates knowledge of the full problem upfront. That’s why it works on paper (small exercises) but fails on the true stuff.
I’m not sure if you can build inheritance to work the other way round, but it’s clear Rusts traits are much closer to this, that’s why they feel nicer.
agreed
That's a really good way of describing it. Tbh, I prefer inheritance over composition, but it requires much more work to do it right.
I learned to program from a top down approach and I definitely find it very useful for working out problems. I barely ever use inheritance though
Underrated comment. I would expect if one were to poll pro-OOP vs anti-OOP programmers there would be a strong correlation between top down vs bottom up design mentality.
Depends on what your true stuff is. In embedded stuff, especially safety critical stuff you get a much tighter contract than any "big tech" company has ever written.
Not uncommen to get a close to full coverage unit test alongside a project.
But most of the time we use inheritance to fit some code into memory constraints. Something most desktop or server programmer don't need to bother with. At least not to the extend of, if we really need the bigger chip we lose millions of dollars.
I love how Prime calls out CodeAesthetics for the next line curly braces and then rhetorically asks whether it's C# because C# standards infamously use next line curly braces. Then I double check the code and realize that it is actually C# code.
I definitely agree with that whole "when you're shown this it makes perfect sense". Like the idea of inheritance is smart, and practical, and obviously a decent way to do it.
Then you see a codebase that actually uses it and it's such a complete cluster fuck and it's so hard to keep track of what anything is supposed to be.
I think the issue is that inheritance is often taught with examples that describe existing properties or functionality. People will often use the example of having a square class that inherits from a polygon class.
The issue is that this works because you’re classifying existing functionality. When creating something new, the analogy falls apart.
Companies don’t have a Vehicle blueprint that they then extend to either get a Car blueprint or a Motorcycle blueprint to the build. They build the Car and Motorcycle completely separately.
Yeah I think people take it too far, I worked for a company which only employed "Senior Developers" and had 2 juniors to do really menial stuff. Some of the software those "Senior Devs" wrote had so much inheritance if you wanted to go down the rabbit hole it would take you hours to figure it out.
Thats really bad code architecture and I don't think you can call yourself a "Senior" anything if you don't think about how your code reads to a person who's not familiar with the structure..
At the same time though, these devs would be very critical of how you named your classes and methods, so you don't care about how your code reads but you really care about what a class is called? That doesnt make any sense to me.
doesn't it just depend on how it's implemented? For example, in my game, every item inherits from the base world object class. This class handles temperature and other things like that, so anything that affects temperature can easily do that. Then an example of an item is a tool. The tool has base things like, how it can hold items, how you can remove items from it, and whatnot. And then there's specific tools that have their own specific behaviors for items in the tool. I just want to know, how in god's name would you approach any of that without inheritance? I genuinely want to know. And I think if done well it's just common sense what things are supposed to be
@@moonashaDoes your game have weapons? If so, would a weapon be a subclass of item? What would happen if I wanted a hammer to act as both a tool and a weapon, or a baguette as a weapon and food etc. This flexibility might not be necessary for whatever you're building but it's something to consider
Yep, and honestly there’s some codebases that spoil us rotten and make it seem so good because the focus is so constrained that object orientation is good. Perfect example is game engines and really popular large web frameworks. Even then, there’s limits and people end up running into issues.
Want to see a HORRIBLE OOP library? Read LangChain, and try to figure out how Conversational Agents pass through chat_history, this nearly drove me mad. If you’re wondering why it’s that way, they release a new version of the library roughly every 1-2 days (absolute chaos of a commit history) and they prioritize maximum flexibility above LITERALLY ALL ELSE.
i'm a fan of that channel, the way he explains is quite like he's the 3blue1brown of programming.
And that should be emulated everywhere!
I would say the 3blue1brown of CS is reducible, he goes deep into CS topics
yeah code aesthetics is more like the 3blue1brown of medium articles
I'd say he has some of the same traits of 3blue1brown, don't want your comment to get outdated if 3blue1brown changes how he makes vids.
@@JohnZakaria I really don't think that calling codeAesthetic the 3blue1browne of CS is very accurate, programming is significantly more accurate. Like the topics he covers is:
- how to make code easier to read
- how to make code more extendible
- how to design code effectively (yes this is a mix of the above).
What he isn't covering is:
- how the computer works fundamentally
- complexity analysis
- math
What he's covering is significantly more accurate to call it programming than CS. And this isn't a bad thing btw. I find it funny when people think monkeybrain "CS good, programming bad". But saying that, they aren't the same topic, computer is understanding why we design code, how we can analyse code, and even lower how a computer works. It's even to find the limitations of code. Programming is a part of CS, and this is what CodeAesthetic is explaining (really well).
LMAO the curly brace thing at the beginning, finally someone else who HATES newline braces
it hurts me
and yet so many different style guides say not to >:(
Thats actually a best practice for # and Java
C#
Squirrely braces you mean
I know it's an old video but I'm so glad I found this channel. RUclips addiction is my greatest struggle as a junior dev that just has been coasting in his work. I know this channel is primarily entertainment but replacing some of the political trash that I normally binge with your content is starting to improve my mindset and problem solving skills and I'm noticing improvement and growing competence at work as a full stack dev.
Coming from a game dev degree that I'm sometimes not proud of you're really helping me fill gaps in my knowledge base that I missed in my formal education. That said I'm happy I went to college for game dev, got me into coding and even if I was disillusioned by the industry during salary negotiation it got me started down this path and it's something I'll never take back.
Inheritance has its place. It can definitely get out of hand. Composition has its issues, but he made the point that some of those issues might be due to putting too many responsibilities on the composes objects. Some of the issues with inheritance can surely be traced to the same.
Spot on!
Never heard of the triple method but thinking back, its seems to be categorically true in my experience.
I've seen lots of new products fail after months of effort because we didn't know all the things we learned in the first attempt but the 2nd pass usually ends up shipping and doing well. Whenever I've worked on proprietary framework level things it does seem like it takes at least two but often three passes for it to be fully baked and desirable to the org.
Holy moley I think it's been at least 10 years since I've heard anyone bring up Microsoft XNA.
yeah... i did it!
The file savers should really just be functions that take some sort of writable object, an image, and options. There really doesn't need to be a class, since that just creates an unnecessary alias to the image and options.
the OOP mind cannot comprehend this
Nooo you need to allocate a FileSaver object to the heap so the garbage collector doesn't get bored.
@@thesenamesaretaken ugh no you need a file saver factory to handle the different types of file you’ll encounter
Vertically aligned Squirrely Braces is 1000x more readable. This isn't the 1970s where newlines are expensive.
2:40 that is because they don't tell/show you how painful it is to change the structure of it after it has been coded . They show it from the perspective of a world where everything is easy, and is correct on the first try.
(I have not gone to college for programming, so I don't know if that is the case, but it sounds like that is why)
I have studied OOP in college a long time ago and this is exacyly how it is shown, OOP has its place but we have gone too far in making everything abstract and that in turn made things worse.
Schools teach you how, not why.
So when they introduce a new hammer, people want to try and hit everything that looks even remotely like a nail with it.
"Avoid protected member variables with direct access" - when I was learning Android Development in Java, this KILLED my motivation entirely when trying to reason with the code, some view operations intensively use protected members from the parent class and I kept asking myself where those members were coming from...
Why not use the Python naming conventions to add _ to protected and __ to private (name mangled).
Just add correct code convention.
m_ - protected member variables.
p_ - private variables.
Well in any case you can just look into a codebase and find what this member variable is actually doing. I didn't really understand your concern.
who’s gonna tell prime that java has interfaces in the way he described that he likes
The biggest problem with the term NoSQL is that it's like "We run NotLinux on our servers"... well, what DO you run? Windows NT? FreeBSD? QNX? iOS?! FreeDOS?! "Everything except ..." is a worthless way to group things.
Who needs inheritance when you have enums and traits?
these are facts
you just hold my heart in a lock, i swear
That makes no sense.
I quite like these 2nd channel videos. The volume of clips and more of a focus on Actual Programming concepts, rather than the 'hot-takes' to just drive engagement like the main channel. I like seeing the M who just likes to help people get better.
I love my next line squirly braces... especially in a language where things can occur between functions and their scope, such as declaring a function const, or member initializers, or metaprogramming(?) such as std::enable_if, or any other number of things.
Lines come free, readability doesn't.
MyClass::MyClass(int value_1, int value_2)
: member1(value_1), member2(value_2) {
//stuff
};
^^ isn't too bad until you have longer type names, or more parameters, or more going on with the class/function/etc.
MyClass::MyClass(int value_1,
int value_2)
:
member1(value_1),
member2(value_2)
{
//stuff
};
^^ is so much easier to read and is much easier to be consistent with in different situations.
24:06 This describes my hatred of DI *frameworks*. I love dependency injection and I use it a lot in Go, but the dependencies are created manually and passed around in code in such a way that's easy to trace and debug. I hated DI in other languages (C#, TS [Angular], PHP) because it almost always involves some sort of DI Framework that over complicates everything and turns DI into a magic global black box that's a pain in the ass to debug and manage configurations.
9:15, there should be quotes around the word perfect in "change is the enemy of perfect design". A truly perfect design enables and embraces change and provides seamless extendability without needing breaking changes.
dependency injection was our bread and butter in high school robotics. We split the robot into subsystems and everything would be injected into relavent methods and classes. Was a massive pain
Data duplication is good when it leads to a more decoupled codebase. You don't always want a giant users table with 100 columns because every part of your system needs parts of the table. Adding an extra column suddenly breaks everything because other parts of the project are now trying to insert null into the new column.
I like your point at 10:00.
First build is discovering what you don't know.
Second build is learning the things you didn't know.
Third build is starting from the beginning with full knowledge.
the rust default impls of traits is available in C# as well (also C# allows Data Members in interface but not entirely since Properties are methods and not fields so it feels like it does when it actually doesn't)
Agreed.
Its also existed in c++ since forever.
Not sure why he was gushing so much about it in rust, its not a new concept
I really connect with Prime's idea of 3 stages of writing an application.
Coming from embedded development, I am very used to separating data and methods on data, and even now that I work on higher plane of abstraction I often always write my first version as a combination of data containers and methods on them. What I find is that often times on subsequent re-factors that pattern alone is sufficient without even going towards OOP way of structuring your code. I am not generally opposed to it, but in my experience it's far easier to first do the simplest thing first (data + methods on data) and if you spot patterns - refactor them however it makes sense to refactor them.
Using classes to structure your code from the beginning IMO is hard to get right even for experienced devs, inheritance or not, you are still effectively dealing with lots of local namespaces and are dealing with "this object owns this and that object owns that", if you then need to share some data, it's more work to make these changes compared to if they were simply passed in as a parameter to some method
I also feel that having a very clear way of identifying data within your program is often very beneficial and illuminating. I suppose this is more towards data oriented programming approach
My experience is that if you're having a clump of data, and then some operations on that data, it's pretty easy to get it done using OOP. Will you have to refactor a lot? Maybe. But it's pretty easy to think in terms of.
Then again, I model the database FIRST these days, as nailing that is pretty reasonable, and then the OOP layer on top handles messaging between the data model and the front end. So it's like the controller in the way MVC was originally conceived.
It works really well for enterprisey solutions.
That description of developing software in a 3ply-fashion is brilliant. Totally tracks sometimes you build it to understand what it does, then build it to make it ready for others, then build it to make for you and others in the future.
I did java for several years and i suffered at the hands of forced inheritance. Now i use literally anything other than java and i hate inheritance because it overcomplicates everything and forces you to fit the problem to the code rather than the other way around
i use inheritance only for derivatives, the child classes only add new functionality, they may or may not alter some functionality (i usually prefer not to), and maybe implement some virtual methods to fit an API (or use interfaces)
aside from that, i avoid it like the plague, if it's not needed, dont use it
C++, JavaScript, Python, C#, TypeScript and fucking COBOL has inheritance, and are fine, but not Java?
Do people just not know how to write Java or something?
@@HrHaakon I almost didn't reply because your comment severely missed the point, but I have a minute so I'll explain again: Java forces you to use classes and heavily encourages you to think about the problem in an object oriented way. (You don't have to of course if your classes are just function containers or bags of data but that is terrible Java. and as someone who knows how to write Java I'm sure you wouldn't approve.) This is not good because most problems are not cleanly represented in the pure OO inheritance world of Java. So while other languages may have inheritance, they also have more sensible ways of modeling code so that you never have to touch inheritance. Caveat with TypeScript: you should know about prototypical inheritance but you rarely have to actually use it. You can write a NodeJS application in TS that doesn't touch prototypes or explicit classes ever.
@@harleyspeedthrust4013
You don't need to use a lot of inheritance in Java. Stop trying to sneak in that assumption.
@@HrHaakon I didn't sneak it in. I made it pretty clear that you don't have to use inheritance in Java but if you don't then you're writing what Java people would call "bad Java." Maybe next time read the comment.
i put squiggly braces on the next line in protest; if a language is forcing me to define blocks with braces, then I am going to make them as prominent and annoying as possible
~8:00 OOP inheritance can easily go out of hand when doing games, classic orc merchant npc issue. It's not nearly as bad when you have hard defined scope, although thats probably the crux that you tend to expand that scope over time and now suddenly your child classes bring along garbage thats only relevant to some children and you need to override stuff
The solution to the image problem the video presents could be far more easily solved in practice with a single Image struct and some associated methods for editing + separate load/write functions to different image formats (maybe a ImgConverter interface if needed). The video is good but usually these OOP examples are overly complicated for no reason other than trying to fit a mental model of classes
That's the point, though. You have some class that works just fine, but then one day you want to extend it. Doesn't matter how cleverly your inheritance structure solves the current requirements. The moment a new requirement is added, the resulting refactor is huge.
Sure, you can solve the problem given here, with a drawable / non-save-and-load image just fine if you know that that's a requirement from the get-go. But then something new comes along.
Actually the method I proposed is way more flexible, as the image manipulation logic is separate from the conversion logic. Want more formats? Simply write a new save/load function pair and pass an Image struct to it.
@@marcs9451 How is writing more functions different from having them as methods in the classes?
@@MasterHigure The problem with inheritance when I used it (maybe bad at OOP) is mutation/global state is order dependent, and if you inherit something the child classes may sometimes want to call the super method first before the body sometimes last and it could lead to subtle bugs because of state, you sometimes have to have the super method be a higher order function so it can be called around your code. Also sometimes you have to have the parent call the parent version of a method other times you want the parent to call the child method...
@@MasterHigure extension methods 😎
So true. When learning programming, inheritance seems so logical and endlessly useful. Yet much of the more advanced programming literature/videos points out that composition is often better.
People will say "inheritance sucks" and "interfacing is great" in the same breath as if interfacing isn't just a form of inheritance.
Is it though? In a dynamically typed language, if I implement a common set of operations on some classes, then I have implicitly implemented an interface. That Java requires implementors to be explicit doesn't suddenly make it inheritance. There's also the question of what is being inherited: besides default methods, there isn't any behavior being shared by an interface to its implementors. I don't think there's much inheritance at all.
25:03 Yeah, I've thought about this a bit and have been curious if it's not a bad idea to have an `embodies`, `compose`, or a similar keyword. The idea would be that within your class you would say that it embodies another object, which you can then access it's methods as if it's your own, but they would be private. Mostly a convenience feature, it would benefit from the IDE informing you which component you are fetching the method from, but I haven't thought of a scenario where it would be dangerous (yet.)
I believe pretty strongly inheritance is only good for composing orthogonal behaviors. Inheritance for specialization opens the door to overriding, calls to super and having to understand multiple versions of the same artifacts but jumping back and forth between modules to understand what each does. My brain doesn't compute that.
"What is this, C#?" he says, while staring at a snippet of C# code. Yes, in fact, it is C# :p
3:09 C#, when the method is lower case?
This is the first reaction video I liked - ever. You complement each other so well. It is like you inherit his evil genius and implement an emotional amplifier of profanity into it.
Composition and Inheritance aren't opposites. They work together nicely, and someone with a good idea of both can write the best of both worlds
I agree with you on smooth scrolling. There's a reason why scrolling was originally in steps and it's because it causes less motion sickness. I am not particularly prone to motion sickness, but I find it much harder to read stuff when they're smooth scrolling, even on a 144hz monitor.
The greatest demonstration of this are youtube live comments vs twitch, where the former is smooth scroll and the latter is bulked into steps. Twitch chat is legible when it's being -SPAMMED-, I'm talking 100 messages a second spammed. RUclips chat is barely legible with 5 messages per second, because it's constnatly scrolling up, there's no rest for your eye to actually read the words.
This video so purely speaks to an idea I've been thinking about for a long time. I keep seeing these videos floating around about why OOP is a lie, and why it doesn't do what it was made to do. Which was solve the lack of modularity in imperative programming.
To be fair, it doesn't solve that problem, we have modules now. However, this is exactly why OOP has evolved over time. It's not about trying to make infinitely reusable code snakes, it's about breaking down large frameworks into easily definable objects that can be instanced to serve a higher purpose.
I clang-format on save with whatever defaults it considers the best. So because IDGAF enough to create a .clang-format file it does my braces on new lines. At first it would cause me geniune aggrievement but honestly you learn to accept it and move on. I work with C++, my life is shitty enough.
1:36 actually yes it *is* C#
Now I understand why Rust traits work in such a way. It has the advantages suggested in this video, without the disadvantages that comes from inheritance.
yep
trait system is so good I wish it existed everywhere else.
@@betterinbooks I agree. Once I was coding a server backend. I created the struct for the internal state that is going to be accessed by the endpoints. I then created a trait with the methods for accessing the state.
For each endpoint, I created a unique trait for it with the behavior of that endpoint. The trait applies to any struct that implement the original trait type.
Thanks to that, each endpoint is its own trait. We just have to implement these in the state type. Instead of one file with all the business logic, I was able to divide each into its own trait. Extremely easy to scale. Loved it so much.
Using that structure, we could have several people working and pushing to the same repository at the same time. Each developer needs to simply work on their trait.
as a silver haired coder from the 1980s OOP & inheritance was the bees knees. on paper. awesome. in reality - i watched programmers inherit the crap out of stuff, till it was just crap. So deep that even a Matryoshka doll would have gasped. also kudos for your music tastes there with miss monique.
your advice on capital D Design and your 2-level way of writing production code is really REALLY helpful. Thank you.
6:25, just use the virtual keyword instead of abstract. This will allow you to define and write code in the function inside the abstract class.
This allows you the ability to override it in inherited classes while not being forced to.
10:05 A typical conversation at my job:
I: Should we really change this thing to work totally different? It will be difficult to restore it. I'll remove all the stuff I don't need anymore.
> Sure, just change it. We decided for the perfect design, it won't change anymore.
A few months later:
> I got an idea, for a new design, we should do it like this.
I just think, that's almost exactly how we already had it.
hey dude- plz make a video explain dependency injection and inversion of control for the masses
Commenting on the vid up to 6:58 - The problem here is not that inheritance is bad (which it may or may not be), but that load and save have nothing to do with an image. They are actions you want on a file, not on an image. Jpeg, png and tiff are also not "kinds of images", they are storage formats for images.
One part of the concept of inheritance is to use it as an abstraction of reality, that makes it easier to handle the aspects of reality that are relevant for your program. If you choose the wrong abstraction, of course things are not getting easier. But complaining about it is as if you would say woodworking sucks because you ordered a bed and the carpenter made a chair.
Straw-man argument. He uses a contrived example, a bad example, and then blames inheritance. He couples persistence with the image type when he should have created a separate tree of persistence types that takes a stream of bytes and then used them as separate but cooperative objects. I could claim that composition has problems as well by creating a badly composed object and then showing the coupling issues. This kind of video is useless. He should be showing how to properly design and use the classes. Anyone can make a video that shows that a screwdriver is a terrible hammer.
Code Aesthetics messed up with the inheritance diagram right before moving on to composition. The object on the right started with only blue and red square but ended up as a child of the green and blue object.
That's kinda the point. If your system is too messed up with ideas that are more complex than they need to be, then you'll make mistakes
6:32 How about having a File class and an Image class. If a particular subclass of Image needs File I/O, then let that subclass extend the File class. If not, then the subclass of Image doesn’t have to extend the File class and hence doesn’t have to implement the File I/O methods
This feels like almost an exact replica of a Raymond hettinger talk, but there was obviously a lot of care and love put into visualization and video development.
The triple method - harambe moment at 10:05 is pure gold
Oh baby a triple!
It's indeed the best way to produce quality software. Developers who think they can do it right the first time just haven't learned that lesson yet. RIP Harambe
@2:14 -- yes, it makes perfect sense on paper ...just like the waterfall model for development made perfect sense on paper.
How to execute:
1. Dicks out for Harambe
2. Shit code
3. Full rewrite no matter what "statistics" tells you
Took me over 10 years to understand how true this is
There is inheritance. A person inherits. Composition. Replace the person heart with a pig heart.
I got to work on an old project where all the important bits happened in business objects that were pure spaghetti of inheritance. EVERYTHING happened through protected variables and methods.
You would see a 5-line function and think that that should be easy enough to understand... but then it ends up triggering 500 lines of code across 5 levels of inheritance. And it would go up and down, up and down...
Line 1 out 5 executes something in its parent, but that is passed right through to the grand parent. So the grand parent does some things, then passes it to the GRAND-GRAND-parent, which executes like 10 lines before it goes back to the grand-parent. The grand parent does a few more lines before calling something that passes through to the GRAND-GRAND-GRAND parent... which calls some completely other module... and from there it falls back to the original child object I was working on.
One line done, get ready for 4 more...
50+ years of programming here. Who does curly braces this way?
I grew up doing brackets the more typical way. On my own I figured out why this way is for me enormously better. The reason you do curly braces this way is that you can visually look down the left side of your code and quickly find out if you have brace error.
If you put the opening bracket on the same line as if, while, do and switch, your eye has to look right to see where the end of the statement is, and whether it has an opening bracket. I can look at the code at 1:43 and within a second see that all the brackets are where they should be. This is so much easier on my eyes that the first thing I do when I inherit code is fix it up this way.
If you don't like it, do it any way you like, but don't criticize what you don't understand.
How about people think long and hard about what their classes look like before writing them. Why the hell would an Image have any concept of save and load when the sole purpose of an image should be to represent an image as data? Having anything extra is over abstracting, class should be the bare minimum and nothing more. I'd keep image the way it was (without save and load, and maybe even the transformations) and have the abstract class for saving and loading. Interfaces to me are for functionality which isn't restricted to any concept. For example if you wanted to have a collection of things which are all unrelated but all have save and load then I'd use an interface, that way I can just create a collection of what ever interface, ex: ISerializable, IEnumerable. If I want to save and load strictly for images, I'd have an abstract class inherit from Image and put abstract save and load methods in there. Now I can have a collection of images which can contain all image types as well as a collection of all "FileImage" types if I wanted only images that have save and load. This also doesn't break implicit conversions from Image to any FileImage.
And for those saying null-conditional operator is hiding a problem, I'd argue that it exposes a problem that was previously hiding. And null in itself is not an issue, its like saying instead 0 as a guid is an issue, its just a state. It is your responsibility to handle what state its in. Asking to get rid of null entirely is like asking to get rid of 0 entirely.
4:15 Why is he putting save and stuff on the image? Shouldn’t it be something like an image serializer that takes in an image and then saves it? So that the memory representation of an image can be abstracted away from the persisted form
You could even invert that relationship so that the image takes in a serializer, allowing you to preserve the load and save methods that are directly on the Image
Those curly braces on the same line are standard practice in C#. Any linting tool would complain if you did anything different to that. They are called automatic properties and they are awesome.
Meme, not standard practice. Entirely due to historical VS defaults.
@@Ormaaj You are wrong.
🤣No I have superior taste.
@@Ormaaj Wrong about that, as well. That's the thing with you. Always wrong.
Whatever newline-curlybracer. At least I'm not wasting all the world's supply of vertical whitespace
19:30: C# 8.0 was released 2019 and introduced default interface methods....
The one thing I've never understood is why full on complex class objects is considered better code reuse than separate functions for common tasks.
Like classes make sense if the class covers every single use you have, but that's rarely the case in any moderately complex system.
I guess this is what they mean by composition over inheritance, but inheritance seems more like a convenience and organization advantage in certain cases, not a code reuse advantage.
at 14:35 I don't really get what the abstract class he got rid of is or how that's relevant but I suppose maybe one day I will
His curly braces are symmetrical. That's real esthetics - real beauty. 💯
An example of where inheritance makes sense is Pokémon: you have the definition of a pokemon(name, evolution, moves it can learn, abilities it can have, base stats).
Then you have a class to express a pokemon in your team (has ivs, evs, only four moves out of whatever it can learn, a personality, only one ability) and from the pokemon class it helps to validate if it can learn the move, calculate actual stats based ob ivs and evs and so on.
But then I wouldn't do a battle class that inherits from pokemon, items and so on
Imo the only good way to have this conversation is by looking at what the compiler does.
For example each time the compiler sees a dereference (dot operand) it performs an extra instruction for null checking, while local variables are accessed directly.
And if we get even more exquisite... Local assignment(dereferencing a field to the method stack is supposedly even faster... But the assignment tradeoff needs to be matched against the number of times the reference will be accessed.
The performance hit is probably insanely negligible since many instructions are pipelined and executed partially in parallel. By this logic, all OOP languages would be slow, but they arent
Inheritance is not bad. The code reuse alone is much better than any type of convoluted dispatching argument. The best, most bug free code is code that is never written or re-written. Without inheritance, you are guaranteed to write code over and over for no reason.
People just really dont understand inheritance in my opinion.
And if you are so concerned about performance, write everything in assembly.
@@jshowao I like OOP, but I won't deny it's drawbacks, specially in such limited scope tests like a simple loop.
JIT is better at optimizing code than a well written C, maybe even a very good C.
But the time it takes for the optimization to kick in... Takes some time.
Another thing about VM languages is that their compilers are really not that smart... Maybe because of exceptions and extensibility but devirtualization should happen a lot more... yet is something that rarely happens. Each virtual call adds a whole instruction... which eventually adds up...
Each extra instruction call is ONE LESS op code of instruction sequence, which is one less op code for the speculation sequence.
The worst offender is that each virtual function call forces a memory order boundary, which means that the compiler is not allowed to interleave beyond the virtual function call... this is the worst offender.
@@jshowao I love OOP, I just wish compilers were more confident when devirtualizing and parallelizing up front, maybe even add keywords to hint devirtualization.
Also I'd love for compilers to not implement 'freezing' so strictly, instead make a flow graph and ignore the constructor memory barrier if the logic allows it... do it up front instead of JIT.
This would allow swaths of lengthy op code sequences... Making massive and faster speculations uninterrupted.
I keep being told that if full de-virtualization would be applied, some pattern would break when dynamic dispatch is not obeyed, yet I keep getting some weird/nonsensical and incomprehensible examples of this...
What do you think of query builders? I honestly can't stand query builders for sql. I'd rather look at a basic string with some replacement keywords or embedded variables.
they always seem so neet
then... they become less neet
@@ThePrimeTimeagen just... Why should I be learning a new structure/language with your query builder? If you just keep it as sql I can read it without having to figure out your custom representation of a query.
String replacement can take a long walk of a short pier, but SQL is better than 90% of everything else the SECOND you're no longer doing basic CRUD that's 1-1 with a single table with no joins.
I love new line curly braces looks cleaner and more symmetrical.
I love this channel and the long video format, for me that can't manage to watch the streams, this is gold
11:34 I disagree on this one because of two reasons:
1st If you put all common things into base class and then it turns out that something is not common then you basicly put non common things into base class. Your design is bad and not the inheritance.
2nd Inheritance doesnt force anything on you. Private, protected and public scopes already solved that. Again, it is your design that is bad if you boundle everything together mindlessly, not the inheritance.
Composition and Inheritance in many cases is actually same thing if you dig deep enough to realize it, but for some reason Ppl are glorifying one over another.
Hey! I'm not sure that it's actually `The flow of Inheritanece`
Inheritance meant to encapsulation data with behaviors that it can manipulate. And subclasses will inherit those and may argument a superclass.
He started with good example with User/Admin (we can have more subclasses).
But then he change to `Image` class example that already has problem that not really relate to inheritance itself.
`Image` class encapsulates data about image and how to manipulate it. But then he adds methods save and load that represents totally different domain. So now we have two different domain in one class Image knowledge and how to convert it into another format. This now violates single responsibility principle.
Those two thing should not be in single class from the beginning, it should have been `Image` and `ImageDrawer`.
That why OOP goes with SOLID to guide what should be in Object (class) and how to identify domains.
This not a really flow of inheritance, it's more like particular case of misuse of inheritance.
Another popular one is for "code reuse" that also in most cases solved by composition.
In my experience those are two most frequent examples when inheritance causes a trouble.
But it's not because inheritance is bad but rather it was applied incorrectly.
The thing is, inheritance asks you to come up with these taxonomies of objects you're dealing with (and maintain them as your code evolves). It makes sense when you're dealing with toy examples (cats, dogs, etc.) and also for certain data structures, but in general your types can't always be grouped into a neat, reasonable hierarchy like that.
Inheritance is very often used is when you're dealing with values that need to have the same type (work in the same places) but have different internal structure and behavior. Even though this may seem like the entire reason for inheritance, it isn't exactly the same as just having a taxonomy of objects.
This 'same type; different structure|behavior' has two sub-categories: when you want the number of sub-types to be fixed or when it should be extensible. Some languages call the first kind 'sealed classes' and often you can achieve it by using certain access modifiers. Inheritance can be used for both of these categories, but the first one will usually be kinda awkward. Contrast this with proper tagged unions, where you can inspect the value to see which sub-type you got and dispatch the behavior based on that. This way the code that uses the value contains the conditional logic, instead of spreading this logic over class definitions for each of all the variants.
The second category is a much better match for inheritance, but frankly doesn't really need all that hierarchical coupling that inheritance requires you to come up with. The alternative is lightweight interfaces. Not achieved through sub-typing but just by constraining your generics with statements like 'requires Foo to be implemented for T'.
Finally there's subtyping itself. It really is tied to inheritance, but I'm yet to see any code that actually needs it. If the already mentioned mechanisms aren't enough you can always use explicit cast functions - they are fully equivalent to subtyping.
(given x : S and S ⊑ P then x : P, so x can be used in every place where values of the parent type P can be used)
(given x : S and c : S->P then c(x) : P, so it can be used in every place where values of the 'parent' type P can be used)
Give me inheritance example and I'll give you the same thing with just interfaces and tagged unions.
@@mskiptr Hey! That make a lot of sense.
If we would look more about class vs interface.
There both can be used interchangeably in most cases.
(some languages require interface to implement classes and some don't even have interface at all and it defined usually as a class with methods that have exceptions in the body (ruby, python, php before typing))
They are both reference types and contain methods and variables.
Most difference is:
- constructors can be inherited in class not interface
- method can be inherited in class (and can call superclass method) but not interface
- classes can be public, private, or protected but interface always public
- interface can combined from multiple interface but classes usually only one (some langs has multiple inheritance)
How I understand it:
class let you inherit share and behavior when interface just define shape
class gives you control over who can do what with it when interface free to implement by anyone.
Example for interface are shown on the video.
It make a lot of sense to just define `ImageDrawerInterface` and ask for something that implement it from `Image`.
In other words `Image->save(ImageDrawerInterface)` just say "Give me anything with that shape, I'm just going to call a method `export` (or better name) on it with certain format of data"
In this case `Image` does not really care what it is. It not going to pass it around internally, just shove data in it in some format it does care if it even works.
Other example when dev actually what a control.
For 3rd party library like this you can create instance `Image` but there can be other things like `Viewport`, `Rectangle`, etc that will accept instance of `Image` as an argument. so they will use it to set `Rectangle` background as an image for example.
In this case if someone inherit from `Image` and pass it as an argument there is no guaranty that `Rectangle` won't brake because of modifications made to original `Image` class. So he make it private and that guaranty only valid set of classes can be passed.
In continuation of that we may have different assets like this: `Image`, `VectorImage`, `Clip`, etc. And the all can be argument to `Rectangle` (or else) for example.
But we do not want to define list of things every time that would be to much (and every time we add update all places)
So we can create a superclass for them like `Asset` and then would just need to check if argument is `Asset` or it's subclass.
Another typical and often use case is Single Table Inheritance (and more design or DDD patterns, etc).
If we go closer to real world there will be multi dimensional spectrum of what works best for each individual project/case.
There will be spectrums from: typed systems to not typed, functional - oop - declarative, relational - data driven...
A lot of languages implement different ways to implement things to developers can choose what works best for them in exact situation.
It also influenced by the history of community and frameworks.
So I believe there is not "truly proper way" of solving a problem. It's alway about exact problem and exact language, and exact level of developers involved in it, and ...
Not to mention every language implement classes differently and some don't event have nor classes interphases at all :sweat_smile: (hello ruby love you)
Thank you to reply. That really helped me to think more on that and finally make this idea to the paper.
On my original comment it was more about that in `The Flaws of Inheritance` video everything started with a not proper use of inheritance at all and then we have a great solution using composition.
I just rather name it `Misapplication of Inheritance` or smth on that sort. Current name make inheritance like 'evil'. Or maybe I'm just don't get clickbait titles.
Any way. Thank for reply
@@fanantoxa Well, I wrote about 'interfaces' because that's the most mainstream word for what I meant, but I really had in mind the kind that is known as 'typeclasses' in Haskell or 'traits' in Rust. They are more expressive and more general then what you can find in e.g. Java.
Yes, they very much can define the shape of your data (at least in a certain sense) but ultimately it's the only thing they do (in that very sense).
The most straightforward examples include mathematical properties like Equality (there's a function that when given two values of type T will tell you if they are equal), Comparable (there's a function that given two values of type T will tell you which one is greater) or Monoid (there is an associative operation that when given two values of type T will produce another value of type T and there's a value of type T that is a neutral element of that operation).
Still, you can use such interfaces for much more down to earth tasks - like converting various data types to regular Strings. Such interface requires implementers to provide a tostring function of type T -> String, with that T being whatever type a particular implementation is concerned with.
And there are also some in-between cases. Interfaces like Mappable or Foldable can describe shape of your data structures. They require the implementations to provide functions like map (of type C -> C) or fold|reduce (of type (((B, A) -> B), B, C) -> B).
(This is where my pseudocode for generics and other type parameters really broke down. It's much neater when you can actually describe types of your types.)
And why did I say it's more expressive?
a) Because in principle you can have interfaces that take a different number of type parameters - ones that relate two or three types together. E.g. Castable which requires a function of type A -> B.
b) Because you can implement generic functions that ask the input or output types to implement some interfaces - possibly several at once (certain argument needs to be both Comparable and possible to convert to String).
@@mskiptr Cmon mate. There is not "right" way to do things.
Haskel and functional programing is not "silver bullet" and never will be nor the OOP.
And if we check most languages are not pure but support both paradigms. You can't build one without the other.
Question more how much of each you use?
When we talking about pure data manipulation, parsing, calculations, etc. Functional programing usually is the best choice. It easy to build "pipes" to transform data and combine them together. Data just get thought the system. And they very efficient.
But when you need to describe an entities and relation between them. And build whole business layer on top of them that where OOP shines. It allows to build system that works for users and business processes that often are not logical. And also you kinda have control over your entities.
My take is not going to change:
There is not best language or paradigm, or best way of doing things.
There is plenty of languages, typing (or not) systems, paradigms, etc. And all of them cool!
But at all of those are just tools to solve problems.
And depend on problem and resources there will be different "more appropriate" solutions.
You won't use Haskel to build classical rest API, same as won't use Ruby on Rails to build high performance computation model, or Elixir to build a game. (I mean you can, but it won't be the most appropriate solution to build, extend and maintain)
All we can do at best is: "what is the best fit solution I know I can use here?" and response will be changing with time and developer experience.
Nothing is perfect. It's all about needs and trade-offs 🙂
Tnx for discussion
1 for agree
2 for disagree?
Only 12 zeroes
Code Aesthetics used an awful, contrived example. I could have easily provided an equally awfully examples for other OO constructs. You typically don't use inheritance for this purpose...I definitely would not have implemented the image save functionality like this... you're breaking Single Responsibility - an image should not save itself.
Inheritance works in scenarios where you need to allow specific call outs to override base functionality - the class MUST adhere to Single Responsibility. For example, a class generates some JSON based on a supplied data structure. In certain scenarios you need to override the JSON generation based on specific condition(s) in the data structure.
You have five options...
1. If Statements for the conditional logic - Arrgghhh - Let me out of here.
2. Just copy the code and implement your new logic - bad for obvious reasons.
3. Use some sort of weird composition where you pass in an object which performs the specific implementation - bad approach as it means a build up of extremely fined grained objects which can be difficult to see how they relate to the base object.
4. Function Pointers/Events - You pass function pointers/delegates which are then called by the base object. This is a better approach but again you need to inject something into the core object to make it function the way you want. Events tend to work better, but you end up with the same result - You have code in the calling method/object/function containing the callout.
4. Overridable Methods - Inherit the class and override the method to handle the specific generation. Object creation is easy (you don't have any daft objects or function pointers) and the code's intent is clear. You musty however adhere to single responsibility.
And in total agreeance with the 2 or 3 times - especially with inheritance - you need to know where your 'extension' points are going to be.
I need "Change is the enemy of perfect design" on a poster above my monitor
> A [pee enn gee] is called a ping
I'll update my pronunciation in a giffy.
CodeAesthetic is like DI: it works till it does not. His take on premature optimization was invalid simply because he did not understand the original quote (probably because he provided only the part most people know, without the broader context. Many such cases) and decided to defend the frankly awful practice of treating the performance like some kind of a trade-off that you can't have if you want your product to be scalable and maintainable and other big programming buzzwords. He also subscribes to this asinine idea that you should write comments only in very specific cases because something something a good-written self-documented code, which is another of those ideas that are great on paper but falls apart when you come down to the trenches and have to spend days, maybe even weeks, to understand the code.
companies should be interviewing people by having them configure the linter for the language they’ll have to write. nevermind if they can cook up a linked list problem in 40min, find out of they want all code to be wrapped to exactly 80 columns and convert tabs to 5 paces
my current strategy is, "Can I do it with an interface?" If the answer is yes, I don't use inheritance. There are some cases though where inheritance is super useful, but I live mostly in the game coding world so it probably doesn't apply elsewhere. As an example, a game would have a base object, that keeps track of temperature, who owns it, etc. Then more specific objects with certain functionalities can inherit from it, without having to duplicate a bunch of code. Basically for me it comes down to how much code you're duplicating
You create classes for each of your properties, like temperature, and add them to the class of the object you are creating. Then, you make a factory with methods for instantiating your object with valid combinations of your property classes for creating different types of objects.
Java Swing goes full in on inheritance, and it's the only one case that I found actually useful and easy to hook into. Building UI, quickly changing a draw method here, or making a table behave like a tree. Once I got it, it was really easy. But designing that stuff myself to be flexible like that, no thanks.
14:44 how to _enforce_ the common function of save/load be called that across all different composed classes like formats classes in this example?
15:28 leading upto 16:48 i think what i wanted to ask is interface in composition.
16:46
The emotion behind "and then all the sudden shit goes so wrong" got me to subscribe.
I great thing I heard to use was "Is" vs "Has". If something "Is" something else, then you use inheritance. If something "Has" something else, you use composition.
For example, "Is" a Png an image? No. Png "Is" a format for saving images. As are the other formats. So we can separate these from image and have them inherit from some ImageFormat class which contains methods to take in an image and save it and return a saved image.
"Is" a DrawableImage an Image? No. But a DrawableImage "Has" an image that it can draw to. So we can separate DrawableImage to its own class, and give it an image to perform drawing operations on.
Did XNA in college senior year, I loved it. Glad to hear I wasn't the only person who used it. I made a pacman and tetris clone in it and was so excited to deploy it to the first generation of windows 7 phone.
RIP windows 7 phone, never got flash support and was too late to the market
I already watched every single video, super cool insights
This is precisely why typeclasses/traits work so well
I also made my first game with xna, a tower defense, and I also had like 4 levels of inheritance for only the attack effects lmao
Oop obsesión was so bad when I was young
You have no idea how much I can relate to this
I'll put my squirly braces where I damn well please.
Inheritance is more about modeling the domain and that subclasses ARE an instance of the superclass. Composition is about HAVING something. Those that argue they are solving the same problem are mistaken. Interfaces/traits are for a shared set of behavior with different implementation. They are all different and have their place. The problem is when a language forces a developer to only use one.
I do inheritance, but it's almost always just one level, very rarely two. I often need very slight variations on different objects, and majority of the functionality comes from the parent. If some of those variations are needed in multiple classes (but not in *every* of them), then they belong into interfaces and become composition.
When implementing a compiler inheritance is elegantly straight forward. Functionality is presumed from the way objects are structured in memory. Where as composition there has to be an active mechanism in determining if such particular object contains a particular functionality. Maybe inheritance is a house of cards that may apart due to design issues. But when it works it should be more efficient then composition
Inheritance is an O(0) solution where as composition is O(1) at best.
It just depends how many "branches" of your core class there end up being. If say there are 30 variations.... then you'll probably have a hierarchy of inheritance with 3 or 4 "generations" to stop objects from inheriting too much. That can require a lot of pre-planning and can easily end up a mess. I'd usually prefer to use the modular approach of composition to form each sub type, but it really just depends on how many variations we plan on using and how worried we are about memory.
10:15 This is why agile was implemented because waterfall developing might produce completely unnecessary products. Its funny how you seemingly never make the connection, either in this video, neither in the agile video.
.dmg -> a damage
.rpm -> a rampage
.AppImage -> Appalachian sized Imagination
.exe -> use Linux please
100%
When your parents are divorced and they both die, you get to inherit from one and the other...
What!?! Curly braces next line is the only way.
So...I don't see why the proposed solution isn't functionally equivalent to multiple inheritance. The entire idea of interfaces being "lightweight" but (in some languages) allowing you to give default implementations that reference other methods of the interface... isn't that just a smaller parent class?
I don't see how inheritance precludes any of the solutions to the given problems. "ImageFile" could also be an abstract parent class, as could "Image." Then your JPEG, PNG, and Bitmap all inherit from both parents, while drawable only inherits from "Image." Am I missing something?