OOP and FP are both obsolete. There's a new programming paradigm called Dysfunctional Programming. It provides a bipolar flexible path forward. Instead of "this" property, we have "dys" and "dat".
I finished your "The Last Algorithms Course You'll Want", and .. it was awesome. You motivation message is now deep in my heart. Thank you for doing this!
@@helgenlane I don't know if that was deliberate, but you just summarized why inheritance hierarchies always end up sucking ass in real projects. In most projects, nobody can predict what the appropriate OOP-correct data structure will be because it changes over the course of the project, and adjusting those inheritance hierarchies to fit after thousands of lines of code have been written using them can be awful. That's less of an issue with a model that relies more on composition and traits/interfaces, keeps things more flexible even when stuff changes.
Here's my take on getters/setters: they are useful for math vectors. Just do "vec.length += 2" to make vector longer by 2. Or "vec.length = 1/vec.length". In fact, they are useful for many objects, that can have multiple parametrizations, but internally we just store one parametrization, as to not have multiple types doing same thing.
Just FYI Prime, Liskov's applies to Traits too, and any function pointer(ish thing) edit: as a practical example: a function calls another function via a pointer, and expects that function to return the same value with the same parameters with every call, but if some implementation returns a new value with each call it violates the principle and may lead to incorrect behavior
Exactly! It is a design principle that is valid where ever functional variance can be implemented. It also applies to non-OO languages like c when they provide dynamical behavioral variance.
@@etodemerzel2627 No. Pure functions do qualify, but Liskov's does not prohibit side effects, only that those side effects adhere to the interface. The example was of idempotence (which does not require purity), but another example could be an implementation that terminates the application; unless the interface permits that, such an implementation could exhibit undefined behavior (e.g. files left behind, or perhaps the termination function itself has been overwritten in memory, so instead of termination, some other behavior is triggered [not likely in modern languages, but Liskov's applies to assembly too, where self modification isn't that uncommon historically]). The principle is about the expectations / specifications of the interface, and reaches further than just the type information we can declare in modern languages.
Liskov still applies to interface/trait based programming. It means if something implements the interface it must do so without changing the intention of the interface. You can’t just pick and choose what methods of the interface you implement. (It may meant you need to split the interface or have the design wrong if something can only implement some of the interface)
The point of getters and setters is that the class can always take control of private data without changing the interface. You can trigger onchange listeners or reject the new value. But no, it almost never gets used.
Not only does it almost never get used, the "taking control" almost always requires adaptation in the client code anyway, at least long term, or you will just slowly create spaghetti. (taking control almost always means increasing the amount of responsibilities the class has)
I've used setters sparringly, but I do use getters a lot, especially for things that Prime showed.. If my class has lists, and I want to get the total from those lists I consider that those lists are properties themselves, length of those lists are in extension also properties of those properties, so a "public int TotalRecords {get { return list1.Count + list2.Count;}}" makes perfect sense. This is still a "method" with additional contextual information making it clear as day to anyone what this Property contains and that it's read only. There's literally no purpose for something like this be it's own function. I believe that functions/methods should contain some kind of operation on the data that isn't exclusively reading, while getters are perfect for those situations. Another use case is when writing object models for parsing json. Client maybe has cardinal directions as words ("up","down","left","right"), but for coding purposes it'd be more helpful to have this as an enum, so public Directions CardinalDirection {get { switch...case..."up": return Directions.Up;}}. With this approach my object importing is a lot simpler and I have immediate access to properties I need in the format I need them.
i dont entirely agree with that. sometimes, and for simple things, getters and setters are an east interface to connect two components when one has a very simple value that is only used in order to update anohter component, and also needed for other states. not everywhere, but useful. for example, a Unit's HP
This is why objects which are likely to require this type of control should use getters/setters but most should not. If your base class requires this type of control, use them. If it does not, don't. Then if needed, create special cases in the derived. Which will likely only be used in the special case derived, likely implying it should be private anyways. Which means the special case may well be hidden within the specialized derived implementation. Which brings us full circle that for the vast majority of cases, getter/setters should be avoided unless it's a requirement of the base. Contrary to one of the of gallery comments, composition does not avoid some of these complexity or issues in any way. It simply changes the surface area where it does. I'm honestly shocked so many people lack such a fundamental background on OOP yet are willing to rally against it.
I really like getters in C#. In other languages, usually I find necessary to write methods like len() to access read-only fields which is a little annoying.
Prime talking about traits as if they're fundamentally different from inheritance. A first level trait is an interface (and possibly a base implementation class), combination of traits can be seen as multiple inheritence. Fundamentally, the choice of your 'abstractions' is what defines how well either options do. If you create a bird abstraction with the underlying assumption that all birds can fly, then your abstraction is wrong.
@@airkami on top of birds what can't fly, how about an abstraction that accounts for birds that only fly three feet, like chickens? I mean they don't really fly-fly, but they can get off the ground, right? It gets absurd after a while because to create the abstraction "correctly" means you have foresight to account for everything in existence, or everything that can happen in advance. I dare say that even in biology that can't happen as we make discoveries. We need to account for the unknown somehow? Dunno, just asking.
God made birds to fly. Birds that cannot fly are literally fallen creatures and not deserving of further categorization. This includes flapping gliders like chickens.
Traits are kinda the parts of inheritance that make sense, and removing the part that does not. You don't get "parent class" type hierarchy, where you need some sorta flying-baseclass. This class might be nonsensical without context of concrete child classes. Also, the implementation of interface in class-system can only be done by one creating the class, inheritance is bound to class definition. Trait definition can be also just as well used along with interface definition. Further, inheritance muddies the location of where the data is defined. With traits, data is all in the struct, there is no inherited data coming from somewhere else. With inheritance, the parent class(es) might contain data that the child would inherit. Traits are sorta like inheritance, if you removed the most significant problems in inheritance.
Classic "it's not the system that's the problem, it's everyone who uses it" defense of inheritance. If everyone constantly fucks it up, that's a big red flag that the system sucks.
Liskov substitution means that when a function wants a "bird" as an argument, you should be able to pass a "sparrow" or a "chicken" and have the program stay correct. In other words, all subclasses must contain all data from their superclass.
it does not say anything about data. It only states, that a base thing defines an assumption and that every child thing of that base thing must follow that assumption. Collections are a great example. Every collection is iterable, but pretty much all of them have different internal implementations. But, again, they all hold an assumption: a collection is a thing, that represents sets of things, might be empty, and you can access thins in the set one by one. It might be a continuous block of memory, a linked list of block of memory, a hash map, a file, a network socket, even a generative corotine, that spits stuff when asked.
Liskov was high on drugs There is no such thing as a bird. There are things with feathers, things that can fly, and things that tweet Traits/interfaces trump strict object definitions And duck typing with compile time checks trumps traits Java < Go < Rust < Zig
@@steveoc64I disagree with that last part. Explicitly mentioned interfaces are better. Then again, I'm also against the "typeclass" mechanism, where the compiler finds the right implementation. Even *that* is too implicit for me.
I'm not finished the video yet, but I felt the inheritance in oop is like directory and the trait in rust is like tag in note taking management. And I love use tag more then directory.
Duck typing is not checked only in runtime. In C#, a foreach loop uses duck typing and verify at compile time if the object has an "GetEnumerator" method (either a classic method, an implementation of IEnumerable, or an extension method)
There is a great definition of the OOP in the MIT 6.001 SiCP: "One powerful design strategy, which is particularly appropriate to the construction of programs for modeling physical systems, is to base the structure of our programs on the structure of the system being modeled. For each object in the system, we construct a corresponding computational object. For each system action, we define a symbolic operation in our computational model. Our hope in using this strategy is that extending the model to accommodate new objects or new actions will require no strategic changes to the program, only the addition of the new symbolic analogs of those objects or actions. If we have been successful in our system organization, then to add a new feature or debug an old one we will have to work on only a localized part of the system." (Part 3, Modularity, Objects, and State) Without any classes, SOLID, GoF, GRASP, inheritance, and other stuff.
If the system you are modeling contains hierarchical relationships (most do), then you are not being faithful to this definition by avoiding inheritance for the sake of avoiding inheritance.
@@Prim-v1d Actually, nothing prevents you from building different objects with their own methods and fields, without inheritance. Taxonomy does not exist in the real world, and eagle, tit, and thrush are not inherited from birds, but unique things that have their own feathering, beak, and wings and they fly and feed in their own way. This is if we are extra-meticulous to the words in the definition. If not - we can rely on common sense and abstract out some stuff (aka methods) so far this does not interfere with us building the system.
@@Prim-v1d But in general, I cited this definition (from the MIT 6.001 SiCP) as an example of a fairly complete, but at the same time absolutely understandable definition of OOP, which is not overloaded with unnecessary details and restrictions. Like those that “if there are no classes, then it’s not OOP”, or “if there’s no encapsulation, then it’s not OOP” and the like. The OOP can be implemented in pure C, and it will quite be "Object Oriented" Programming, it simply will not fall under some definitions that are widely used as dogmas or axioms - although for some reason no one ever says when and why they became dogmas and axioms.
45:40 I'd like to point out that getters and setters ARE methods. Having them appear as fields with assignments is just weird syntactic sugar and functionally unimportant. Especially in a language like JavaScript.
As someone who's programmed in multiple paradigms, my favorite paradigm is whatever paradigm solves my problem for me. I have no qualms mixing functional with OOP. It all depends on what the problem is.
@@thederpykrafterikr. But where's the fun in that? Even with strict design patterns, knowing when to break the rules is critical. In clojure, there's side effects. Even though it's functional. Globals and Singletons are bad, but sometimes they are the best solution. Why? Because in the real world we deal with these problems. Purity is impossible. The science is knowing the correct thing to do. The art is knowing when to do the incorrect thing.
I will create an object with no real world significance with a load of methods with no persistent state, while totally deriding functional programmers. I will also crate an object with all the data in it, which I will call either O or wOrLD and pass he bloody thing everywhere.
@@thederpykrafterYES! This! These are tools to help you complete different tasks. Don't be too wedded to one paradigm or another. Learn as much as you can about all of them, and use the correct "tool" for the job(s).
I think you might be interested in Odins solution to this. While it generally behaves a lot like C, there is support for subtyping and generics. As a bonus there is even an (experimental) feature on the lsp called "fake methods" to make it easier to find the procedures that are associated with a struct.
What do you name a function that takes two things? Car_road_drive? Road_car_drive? Both? The power of typing "car." and getting a list of functions that are available is specifically what he referring to.
People love to tout that getters and setters for private members are "good" because they compile as functions and can replace that implementation with a calculation, or redirect to a different property, in the future. In practice, this rarely happens. The more commonly needed point for getters and setters, at least in some languages, is to define asymmetric access levels to state. Unless I am mistaken, in C# it is still impossible to grant public access to reading a member value, without either granting write access, or encapsulating via property.
Strictly speaking you are right but C# simplify and hides getter and setter and reduce it to 1 liner so you need only change {get;set;} to {get;}. Actually encapsulation only makes sense when others work on your code or at least works as bridge like a public API or sever -client communication.
I think that getters and setters are useful when we need to add some checks and boundaries to the attributes. Like, if we want to verify the parameters before modifying it, we create a get and set, where the set verifies the inputs. If there is no need to verify the data that we are setting, just make the attribute public.
Majority of Prime's viewers are college students and non professional devs, college students are taught Java in school which is OOP so the poll makes sense.
meh. In college, I never wrote any code to model AST nodes. After college, I found that a class hierarchy was the most natural way to do it I could find. Is it the best? I don't know. But it seems to work better for me than anything else I've seen. Skill issue? Maybe.
I was taught C, C++, Python, web tech and Java personally. Prolly should be learning Rust on my own. It really depends, usually good schools teach you C and FP first before implementing OOP in C (through custom vtables and stuff) and then tell you how C++ abstracts all of that painful stuff, then it's off to general OOP with java or smthg else
@@recursivThe problem with strictly OOP languages w ASTs is that once u build your tree, it's a pain in the ass to perform general operations on your tree. Having currying, match patterns, and higher order functions makes AST stuff way more interesting imo. ZIGs source code is insanely clean if you're looking for more procedural/functional examples
The problem I'm having is I'm encountering people that do not understand procedural programming! Some people I work with think that my code is really weird because I rarely write classes. Their default position is to encapsulate and information hide. They are defending themselves against some imaginary future battle that is raging in their minds where the code has to be modified or re-factored, and "putting everything in classes makes it easier" - because they read it in a book in college. However, I've been writing code for money since 1988, and I can tell you that code re-factoring never happens. The code just gets junked and re-written from scratch. It really does. If you understand the problem domain, it's just easier to start from scratch unless the change is very trivial. For me, if I don't need multiple instances of something (I'm talking about C++ here) then it does not go into a class. It's that simple. Sometimes you are forced into them - for example in C# you'll often be given an object instance as a return type. Fair enough. I do like how the dot notation can make code self describing. In C++ I can get a similar effect from namespaces without requiring classes. OOP is fine, just don't over use it.
@@arch126 To me, re-factoring non-trivial code requires getting into the mind of the person that wrote their code and understanding their style. I have been successful over the years in doing that - sometimes you know which colleague wrote a piece of code. However, it's often just too damn difficult, or too damn time-consuming. So it gets junked and re-written. I'm not saying it's the correct thing to do - I'm just saying that it happens.
Their real benefit shines when using reflection features. That's the main reason of their existence instead of a mere method. They have no reason to exist in languages devoided of reflection capabilities.
@@jacksonlevine9236 ironically, they are less vampiric when not having those features. As the main purpose of those libraries is to vampirize the code at runtime.
Loved hearing even a tiny bit of your thought on lua and metatables, I begun my coding journey in lua (im sure you can figure out where..) and the learning of metatables and metamethods was for me a godsend when I learned when, where and how to use it properly. Would love to see you go through some lua some day
in c# getters and setters let you set breakpoints on prop change and also work a little better with intellisense. also if a class is defined in a dll, you can add code later to hook the getter or setter without altering the interface. so I kinda like them.
@@colemanroberts1102 Kotlin, but Scala is fun too. (Actually a java dev though professionally. Java+Lombok+Manifold can get quite close to Kotlin though)
Crazy how long the discourse between paradigms has been going on when it's so obvious that 1. it's almost always domain/implementation dependent and 2. a combination of everything is almost always the best. Procedural by default with OOP concepts for managing data/custom data types and FP concepts for managing logic and control flow, and 1-2(max) layers of declarative functions wherever is practical with the rest prioritizing an imperative style. Extension traits and composition over inheritance every time for the OOP side and you're golden. Just need a good, *un-bloated* language with a best-in-class type-system (never used but I only hear great things about Hindley-Milner) that isn't trying to be a one-trick for once and devpression would plummet.
I think people forget that a paradigm is not 100% bound to language. You can write OO code in c ,as well you can go procedural in java. OOP doesnt means infinte heritances with mixins and abstract classes with a bilion interfaces, its just a style of programming.
Yeah - it can be abused. We've all seen code that abuses OOP principles more as a cock-measuring contest "ooh... Look how leet my OOP skills are" that result in a confusing and over-complex mess.
Kotlin does well by eliminating all boilerplate to properties (fields with getter and setter) by basically just automatically wrapping declared fields in getters and setters. Still begs the question of why do it at all tho. Only advantage is that you can overload the get/set, which I like for pretty readability, but part of me thinks that code that does something expensive under the hood but just looks like a property access is code that’s lying to you. It’s just some part of me really want to obey data = noun, function = verb
i actually like Kotlin's getter and setter for all object property assignment and referencing but I think there has to be a better syntax to whatever Kotlin is delivering
The problem with inheritance (and LSP) is that it is strictly hierarchical. It breaks when real life introduces cross-cutting concerns across multiple classes with different inheritance trees. We get all the weird object names because the "fix" for cross-cutting concerns is to add new classes to make the hierarchy self-consistent. Then as the code runs into more and more reality, more and more classes are added to the hierarchy, because hierarchies are only occasionally applicable to some real life problems, but not most.
You should not use inheritance to model everything because not everything in real life is hierarchical. Some things are compositional, and you should use composition to model them. But many things in real life are hierarchical, and it is awkward to avoid using inheritance in those scenarios.
@@Prim-v1d Exactly. Some domains inherently lean into inheritance. Some into composition. Some into neither. The failure isn't OOP or composition or functional or whatever. It's the designer. The right tool is always the right tool. The trick is knowing when to use the right tool.
This reality which a lot of software engineers like you @uumlau paint is a compelling one. However, it's due to the abuse of inheritance and not inheritance itself. I recently believe that there's nothing wrong with inheritance as a tool for code reuse. Yet, there's everything wrong with how inheritance is used and implemented in most codebases around the world. Inheritance hierarchies should be built bottom-to-top and not top-to-bottom. All of the problems and issues people experience with inheritance has to do with software engineers building the hierarchies from top-to-bottom. Issues like tight coupling, deep levels of inherited classes. Most examples of inheritance innocently suggest you should create the parent class(es) first. This is wrong! When using inheritance create your sub class(es) first and build the hierarchy from the bottom (subclass) to the top (parent class). For instance, why do i need to create a Shape class before i create a Circle class OR Why do i need to create a Bird class before i create an Ostrich class ? Why ? There's no reason. Even the Java Standard Library got this wrong! And now for years and decades, we have carried on like this. Doing the wrong thing and expecting things to be fine and when they're not fine, we blame to tool. We blame inheritance for all the bad things that we encounter. When we should blame our ways and methods of using inheritance. Most of the nagging issues around Inheritance disappear when we build from bottom-to-top. We even discover that most of our parent classes become Abstract classes as opposed to concrete classes when we use inheritance properly. I know this because i have applied it to my work and seen better results. Read more here (in my article): isocroft.medium.com/the-growing-spate-of-ill-subjectivity-and-presentism-about-software-engineering-practices-c74c6bf34cad
@@Prim-v1d- "some things are compositional". Soooo... not OOP, then? Because composition existed long before OOP. If you're advocating NOT using an OOP-specific features like inheritance, and you're instead saying a non-OOP-specific Ike composition feature be used, you're essentially confirming what OP was saying - use less OOP.
people who say that they love OBJECT oriented programming usually either have no idea about FP, or actually love CLASS oriented programming. majority of practices, patters and principles are about how to use CLASSES. the favorite feature of OOP lovers is inheritance, and it's purely the feature of classes. instead of calling it OOP, we should call it COP.
How about erlang processes as objects? They fit those OOP definitions even better than classes. They hold state shared between a set of functions, fully encapsulated, effectively subtyped since eny message can be received by any process that accepts it. Sounds very OOP to me, you write them with FP though 🤘
29:48 - Caching is a big one imo. It's nice to have a property which caches the reults of some slow calculation. It'd debatable that you would want this as a property though instead of a method.
The use-case for getters and setters over functions/direct access isn't "it's better to hide the function" or "it's better to have a trivial getter/setter function over direct access" The real use-case is when you have code that exposes properties that are already being used directly (sometimes outside your control, like when you ship a library) and you want to change the underlying implementation without breaking clients Obviously there isn't a reason to expose a .length getter in new code, just expose .length() and it's clearer
inheritance has really specific use cases that don't come up that often. while technically useful, I don't think it should be considered a "pillar" of oop.
Tbf, Composition should replace it as a Pillar. Then again, the four pillars most people consider OOP, well not all are…exclusive to OOP. Namely Encapsulation or Polymorphism.
I agree. But to give context: in the early days of OO like the 1980s and 90s, interfaces were mostly classes and not defined as an abstraction of its own. So, there were different methodology types of inheritance: type inheritance, interface inheritance and actually interface implementation. Bc the latter was only deriving from an interface and implement the behavior. Also, in the beginning type inheritance was much more praised. With experience "good" OO principles were defined. This is where LSP and Gang of Four come from. Nevertheless, inheritance is a pillar of oop. It is only not to mistakenly used where a has-a relationship is the better option. And that is often the case. A guiding principle is to think of if the relationship is static or dynamic.
I agree too. I already did another reply like this, but my point was, when you leave inheritance away, OOP no longer conflicts with functional programming too. Somebody mentioned that OOP is actually more opposite too procedural than to imperative. This is at least correct for the concept of methods. Languages like Rust still use methods, which add behaviour, logic to data types, effectively turning those into objects. Polymorphism in functional programming languages is achieved via type variants, discrimated unions, like the enums in Rust. Generics are also used. And extension methods like C# has also count here, because these are often applied to interfaces, and are even used as a system of higher order functions known as LINQ.
@@twenty-fifth420There are cases where inheritance is the superior option. I just don't trust myself or others to know when that is the case, because if it isn't then you're going to hate life.
I use subtyping extensively in my c++ UI/rendering/game library. It is very useful for describing eg Window -> Control -> (Listbox / EditBox / Scrollbar) or other similar items that all have a position on screen, rendering and input similarities. I don't understand why people have such disdain for inheritance.
Two levels deep is manageable. Ten levels though... I guess people are just burned out on way too big inheritance chains so they decided to just throw the entire concept out the window.
one does not need inheritance for that. in low-level languages like C, just cast a pointer of several types to a common type, that has same field offsets and sizes as the common part. it's also possible to do composition or exclusive unions.
20:26 you can get that same inheritance vs composition complexity when considering only interfaces (or rust traits). Like if using interfaces/traits to assign behaviour, at some point you need a way to require a thing that implements multiples of these interfaces/traits, at which point you’d probably introduce a new interface that implement these interfaces. So classes and subclasses aren’t where the complexity exists or where it’s defined. The classes are just implementing the complexity.
You wouldn’t make a new trait that requires other traits. You would write functions that expect some type that implements exactly what you need to use in that function
@@andybrice2711 100 mg of caffeine, i.e., around just under 250 mL (1 cup) is all ya need in a single day. The hard part is keeping it limited to a single cup.
One of my favourite feature in c++ 23 is how good the concepts have gotten, its like attaching behaviour checks like requirements of methods and variables etc. I haven't touched C++ inheritance in a new project
C++ is kind of trying to phase out inheritance. Its still there, but programmers have been coding complex problems for the last 20 years and we know inheritance is bad (for non-gui stuff).
@@complexity5545 yeah the langauge proposal lead by bjarne stroustrup if I remember correctly, he wanted it to land in C++0x/11 but it didn't happen because of radical change in language. I am glad its here though, I absolutely hated the previous errors lead by templated code (its still here but concepts. are better)
19:15 Well, isn't programming about getting things right? Isn't good design about getting things right? Good thing about these principles is that: 1) you don't really need to adhere to them (except for maybe S and O) 2) Your extension methods or mixins or addable traits or what have you don't really impact the adherance, except you have to implement them for every object you have (if you have lots)
The thing with communism is that no one is able to do it correctly. With Liskov substitution principle, a fucking word calculator is able to come up with idea that works.
Yeah I mean look at all these modern communist inventions like electricity, digital computers, combustion engines, HVAC. Just imagine if the world was capitalist, we'd all be starving in a Holodomor @@NXTangl
@@freesoftwareextremist8119 I have and knowing that closures are a poor man's objects & objects are a poor man's closures, SBCL is one of my best tools in my toolbox alongside NeoVim :)
29:39 Have to do Java at my job and our use case requires eclipse. Using getters and setters works much more reliably when you want to see where a member variable will be accessed from. However I think a lot of other IDEs handle that better. And I really don't wanna use eclipse anymore
5:40 what prime is talking about is dot notation, and it's not exclusive to OOP. You can do that with well made modules, OCaml has the best handling of it that I've seen. Skill issue
Oh, yes. This is the answer for my ponders of how you wrap pure functions together in usefull maner. And it was so obivious, too close and maybe because people has been trashed idea of good file names too much... and whole OOP dominace in this space. Bonus is, if you have design rule of not hiding or mutate module names in import, then you will think bad or long module names.... Please no more Helper_util files. helper_util.smooth() helper_util.parse_line() or rate.smooth() style.parse_line()
When lightning strikes a tree, who's behavior is that? Idk, what are you talking about? What the - why did I read it out loud? one of the best things about this video🤣
I think his point was that since the core idea of OOP is that data and behavior should live together, it leaves a big gap for interactions, where it isn't clear which one of two objects own the behavior, often leading to artificial "helpers", "managers", etc. that are basically just trying to OOP something that's inherently imperative It's easy to marvel at "look how nice it is where only the object can access it's properties", until you hit these and the abstraction breaks
@@TruthAndLoyalty "You wanted a banana but what you got was a gorilla holding the banana and the entire jungle" OOP encourages not only this type of nonsense but also weird metaphisical questions regarding behavior is your tree stricken by the lightning or is your lightning the striker of the tree. Fucking nonsense
@@Oi-mj6dv it really doesn't cause those issues. IMO that's a product of teaching oop as if its a matter of taxonomy. It doesn't actually matter "who" the behavior "belongs" to. Oop is a tool for managing complexity. It's not classifying species, designing convoluted inheritance trees, and answering philosophical questions about the is/has duality. The pitfall is that people tend to have difficulty learning how to do that, focusing on the idea of real world objects and "classes". No tool in programming is an automatic win. Shooting yourself in the foot with oop is largely a skill issue. A lot of people hit that roadblock a couple times and decide OOP is the problem and never get good at it. It's apparently hard to teach. I have yet to see a good class or book on the matter. There are some specific disadvantages, but surprisingly those are rarely mentioned in these conversations.
There are edge cases where setters and getters are useful. 1. you only want a getter to make a variable readonly externally 2. you want a setter to perform actions on the value every time you try to change it I'm sure there are a few other use-cases out there, but those are the 2 that I come across most myself. If I don't have a specific use for setter/getter, then I just use a property because like you said it's exactly the same thing at that point but more work, as well as harder to maintain / follow.
18:48 I have one gripe with the way Rust does it (don't know about Go) is that unrelated modules can attach behaviours too. Like if two different modules attach implementations of the same trait (like from a shared dependency) for the same struct (like from a different shared dependency), shouldn't that clash? Especially when you have a trait pointer to that struct?
techinically. you can either attach internal trait to external struct or a internal struct can implement an external trait. importantly this done at the crate level not module level the closest you can get to external to external is to create a new type that wraps over the old type.
@@jadonbelezos2583what I mean in specific is: crate a: defines struct X crate b: defines trait L crate c: implements L for X internally, has a function which returns a Box which is an X create d: implements L for X internally, has a function which wants a Box
@@ayoubelhioui2205Because it violates encapsulation. Whenever my users want me to show more than an empty screen, I tell them that it is not possible because I cannot expose the internal state of my objects to them. There are many bad getters and setters and there are many good getters and setters and there are many bad developers and only a few good developers.
You could think of c as object oriented, just at a module level. If the identifier isn't extern, theres nothing pointing into it from the outside. So to access it, you have to access it through the outward facing public interface(methods/accessors) that were compiled into the module. Otherwise you would have to like, parse the memory of another module to in order to get at the data. Each module can matain the storage of a different collections of data types, and everyone else just points into these types via handles/pointers.
The biggest problem I've found with oop(I'm a junior dev) is that it forces me to make my code into a specific structure that very easily loses its structure by implementing a virtual method in the superclass that doesn't belong there but ends up there anyways because it's easier and then if you want to change a class, you have to change every class that has that class, I've found it more useful when making something that needs to or benifits from hiding details behind abstractions.
To be blunt, that isn't an OOP issue but a skill issue. You are using inheritance wrong if you run into such a situation. It is a sign that you need to rethink how you are doing things.
The biggest problem with OOP is that it is usually taught wrong. If they teach you inheritance as one of the major things to know, they are doing you a disservice. I agree with most things in this video. If you need to hold data and pass data around: use records (structs). Think about making the fields readonly. If you need to connect more distant parts of your code: use Interfaces that define behavior. If you have a thing that has internal state and some exposed behavior: use an object with public methods and private fields. Objects can implement one or more interfaces. Interfaces can extend other interfaces. Base classes and virtual methods are almost never used. Only in some cases and only within a single module. Never make public Base Classes. Use Interfaces. -- The way C++ "invented" OOP in the beginning was a big hack to make message passing work relatively fast with the technology of the time. That way of programming stuck around for to long. Now C++ is much better, but habits die hard.
@@evancombs5159No. sometimes it’s best for the „code to do what the code needs to do“. OOP often feels like translating simple things into a completely different and very static way.
The most OOP I go is Object. Composition is far superior to classic o o classes and their instances and avoids the spiral down the rabbit hole that oo inheritance takes you.
This is a ailure of your education. Do they not teach "is a" vs "has a"? "Is a", means inheritance. "Has a" means composition. You can use both at the same time. The complaints here about OOP is really a skill failure in understanding proper OOP design and engineering. For example, people often create a Car and then derive a Truck and then mandate a Car or a Truck also has wheels and transmission and an engine. Then try to create interfaces off of these abstractions. Never mind the abstraction is completely wrong from the beginning. Of course it all breaks. That's not OOP's failure. Composition and Inheritance are not mutually exclusive. They are extremely complimentary. The issue is, apparently, schools are no longer properly teaching the paradigms. A car "is a" vehicle. A truck "is a" vehicle. A vehicle "has a" motor. A vehicle "has a" transmission. A vehicle "has a" wheels. This well highlights the lines of abstraction and each associated interface to compliment each of those abstractions. Suddenly you're using both and the abstractions well identify the interfaces.
@@justanothercomment416 or you could just use vectors and build the car out of different vectors(one for each component) and just use an index to link everything together.
Does structural typing still let you explicitly implement some of the interfaces? I like purposefully implementing an interface and being forced to implement the necessary methods by the compiler
OMG. Why so complex? How do you come up with OOP concept? By thinking about inheritance, modeling? Really? NOOOO! It started with a simple problem, you have some state, and you have function or procedure to manipulate this state. Everything works fine, because you wrote it. Now another dev wants to use your function but tries to manipulate the state by themself directly instead of your functions and in non-supported manner. Everything crashes, world ends. So, this is the core problem that OOP tries to solve. That's it. That's it. Naturally, you say let's combine state and methods so nobody except you can access the state. Ok, so what should I do if I want to extend original functionality, should I create my own state? Can I extend yours? Naturally, inheritance appears. That is, it. And then all other features appeared from people who deviate from original problem. Original concept of OOP is great and useful, and inheritance is useful. How these concepts are implemented - it's another question. Simple inheritance ok. Classes ok. Everything else is useful only for wide distribution of large managed libraries developed by large companies. It does not mean all features are for you. And agree. The interfaces are great.
The thing with abstraction is that everyone see the "limiting factor" as a downside. In my job, we had this internal project that it was about casual party games to "break the ice". The dynamic was: someone with an account creates a lobby, then you join with the phone and the game starts. The thing is i had to make a little framework for the games inside the server. So I use Inheritance to force the people in the future to follow the gameloop, so every game has the same structure. I used the "Limiting factor" so future devs dont end up making a mess. With that little framework we could make a game in just 1 to 2 days (all tested, back and frontend, only 2 devs).
Not a game dev but we do the same for industrial automation and R&D. We use inheritance to handle most of the nitty gritty stuff, so that we only worry about project specifics. This also means that of someone goes off rails and builds something without documentation we at least know where to start reading and we can reconstruct the spec.
How is this different to passing an implementation of a "game loop interface" to a "game runner"? (As opposed to using inheritance) Not to say that that's an argument against inheritance (at least by itself) but one of the biggest issues with OOP is not being able to consider other options than inheritance.
@@SimonBuchanNz you use inheritance when you want to implement something in the parent class, if you only have to define a "type" just use an interface.
@@fludeo1307 I literally don't understand what you mean. That inheritance means you can call parent class methods? Sure, or you can pass in "this" to the interface, or you can not use a class and use free functions for common behavior, or whatever other features your language supports (eg Rust extension traits, Java/C# interface method defaults, ...)
@@SimonBuchanNz inheritance means you literally inherit behavior, data, and structure. if you pass "this" to the interface you still have to implement the method on the "child class", so you have the same code everywhere. Implementing the method in the parent class would save you writing the same method over and over.
I'm one of the Haskell nerds that has mostly made peace with OOP. Classes are just what OOP languages have instead of variant types and pattern matching. And they're extensible in different ways, since classes allow adding new types without changing existing code, and variants allow adding new functions without changing existing code. But classes can't add new functions to the interface without changing existing code, and variant types can't add new variants without adding it to all the existing pattern matches. It is often useful to be able to add more types, so FP languages will generally have mechanisms to do it as well. As a rule, inheritance is often coupling a lot of complicated stuff together, which you can do in any language but this particular mechanism was heinously encouraged in the earlier days of OOP. I use interfaces and abstract base classes and rarely override methods. And don't make things too abstract out of the gate. Your system will never support every imaginable type of extension. Just focus on the extension that you need. This is also possible any language. I have no doubt that architects of the 90s and 00s would have made us hate Haskell and FP if it were the language de jure. We don't know how to make software still and we really really really didn't back then. Another failure was obviously that well behaved stateful objects would easily compose into a well behaved stateful program. Absolutely not the case.
😂😂😂 Everytime Prime talks bout C# i😢have that same question! How good is he at C#?? OOP is more cumbersome but from an organizational standpoint, I think it's the best strategy. That being said, if you can't make ur C# code look like Zoran Horvat then I'm not sure what your aspirations are in the world of C#. His approach has functional and OOP blended together. Which is a true sign when you Master a programming language. Edit: hold up, prime does not know who Mrs. Liskov is?!? Wtf??
I have seen multiple thumbnails/clips/titles from him dismissing OOP, only to find that he does not even know what liskov substitution is... Not a cool use of his influence in the programming space.
In c# i dont bother with inheritance, I just use dependancy injection. Its much cleaner to me, just inject filght to sparrow and dont for the ostrich 🤷♂️
I've never done anything but OOP. It baffles me that senior developers don't know what it is, and make the same complaints about inheritance over and over again while using OOP in their own projects and not realising it.
Senior engineer at Netflix, 10+ of SWE experience, worked in academia, gave a very popular course about algorithms and didn't know about Liskov Substitution.
I was taught that getters and setters should never ever be used, and then I was tasked to write a whole (simple) game engine without them. I have never used them since, and I understand the point of OOP much better than before.
if everything is getters and setters you have too many. if nothing has getters and setters; you have a struct with functions. the big thing is "if it needs to be validated it should have a setter" "if the member should never be written to externally it should have a getter" Getters and Setters are just special function overrides for the =operator on the member.
There 2 ways you can write in OOP state and stateless . In one you get only operation results as object never manipulate the object itself and you are per default thread-safe but you have the cost of memory allocation like in FP. In the other you run often in getter/setter scenarios which is better for speed and memory optimization but also you have sometimes not all information all at once in this case it is from FP perspective like a closure. In general OOP is just making a list of your functions and variables under a name which doesn't oppose FP . Its more like the question of if you like your stuff messy or ordered but yes there is also stuff where OOP doesn't help like when you working internal data streams where everything is just logic . There FP is more helping hand when you are able to write in it.
Exposing setters for properties is something I do sometimes. It's for rare-use things in classes, or things that allow the user to customize it for special cases. Like "ThrowExceptions", making the class instance's methods throw when exceptions occur, or "AllowGuests". Anything that is rather important is already in the constructor. What I want to avoid is putting ALL of these into the constructor. Imagine you can only create an instance of a class if you have like 20 parameters, and no indication to what is default, or what constellation makes sense (yes that would be sloppy code, but it's just an argument).
Oh GOD no. "empty()"? *cough* Principle of Least Astonishment. I would expect "empty()" to empty something, and would expect "isEmpty()" to be a boolean returning function.
As for properties on interfaces (assuming you mean member variables), I get your point if those variables are mutable. However, I find no better way of categorizing shared constants which for all intends and purposes are "global" for all implementations of that interface
Categorizing is one thing but encapsulating and hiding even immutable data which is effectively constant (never changing once initialized) is another. OO design doesn't just bundle data. It bundles data and functions over that data while hiding (preventing access to) its data. If you'll forgive my very long-winded ramble, this overarching philosophical desire to encapsulate and hide data often contorts and skyrockets the complexity of many codebases. To illustrate, consider a design document for a simple game which describes requirements like these: >> A light healing potion can heal a creature for 5 HP. However, the creature must be able to drink the potion: it must be conscious, be a biped possessing functional arms/hands, and a functioning digestive system. Therefore, creatures absent digestive systems such as androids (although they can drink if they possess a mouth but to no effect as they lack the digestive enzymes to break it down for its effect let alone the organs to heal) along with quadrupeds like dragons along with bipeds with broken arms can't drink potions. Note that trolls also cannot drink this potion as it interferes with their natural regenerative abilities. Creatures with alkaloid allergies given the nightshade ingredients in the potion will become poisoned instead of healed by it. If we use OOP, in which object -- and at what level of abstraction/concretion -- do we declare and define this functionality? Let's say we define it in a 'use(Creature)' method for LightHealingPotion (which is a concrete type of Consumable); that's likely the most organized place to distribute most of the complexity. However, we only have access to a Creature instance, so we can't know whether they are conscious, bipeds with functional arms/hands, have a digestive system, alkaloid allergies, mouths, or are trolls, let alone modify their HP or poison them unless we downcast or expose extra methods which query/transform the underlying information: all options which weaken the encapsulation and abstractness of our Creature design. If we step back and consider what we just did, we took a design requirement for the functionality of using a light healing potion and distributed the complexity of those requirements not only into one concrete object but spilling into an abstraction and potentially multiple concrete derivations of it. That is to say, using natural language as an analogue, we spread the complexity of the specific verbal action of using a light healing potion across multiple nouns. Some of that complexity may not need be repeated for each new rule, but we still dispersed some degree of complexity for a relatively simple rule across potentially many things. If we approach this with a competent procedural or functional design where we don't try to bundle verbs and nouns together, all the nouns become plain old data and the entirety of the complexity of using a LightHealingPotion would be distributed into a single function designed specifically to use it which can be a near one-to-one translation of the design requirement (the code almost writes itself). To avoid requiring a boatload of code duplication to use a light healing potion separately from an antidote potion and so forth, we can use polymorphism at the verb/function/action level rather than noun level. Consider how profoundly simpler our codebase will be, especially if we have to implement 300+ pages worth of such rules as in the case of an AD&D rulebook. The relative difference compared to even the best, SOLID-conforming, OO designs with genius-level abstractions will be enormous. Even in terms of line count, we're likely talking about at least a 20x difference between the functional or procedural version vs. the best-designed OO version.
@@darkengine5931 Yeah I completely understand. I've been out of the OOP-phase for a while now, and usually keep any encapsulations rather shallow, if not completely ignoring inheritance and class-member-methods in turn for bounded generics and static pure functions. Even in Java you can gain about 90% of the convenience of inheritance that way - while not hiding anything and keeping most of the control flow pure. It mostly comes down to convention for me at this point - if I go 100% functional, no one where I work is going to understand wtf is going on. Even generics are still foreign to most - and I've already had my fair share of discussions about how and when to use lambdas.
@@gustavbw I have the same struggles in my team. We work in C++. I keep wanting to persuade members to favor pure functions but it is a slow progress to really sell the benefits, especially since I can't efficiently multithread my code nearly as much without locks since my colleagues are still writing objects whose methods I have to call which aren't thread-safe without syncs. What I like is a pragmatic idea like let's just try to work more and more functions that just input something and output something new, not input something and mutate it (this includes member functions/methods which mutate an object's member variables). I'm currently at the John Carmack stage 10 years ago where he was trying to persuade his team to favor more functional programming: >> The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don't let them slide back into impurity!) >> The whole point of modularity is to hide details, while I am advocating increased awareness of details. Practical factors like increased multiple checkouts of source files and including more local data in the master precompiled header forcing more full rebuilds do need to be weighed. Currently I am leaning towards using heavyweight objects as the reasonable break point for combining code, and trying to reduce the use of medium sized helper objects, while making any very lightweight objects as purely functional as possible if they have to exist at all. >> Most bugs are a result of the execution state not being exactly what you think it is.
What OO in the end actually is Is discipline imposed upon the use of pointers to functions. Discipline imposed upon the indirect transfer of control and in exchange for that discipline we got a benefit and that benefit was The easy ability to structure our modules! So that the source code dependencies oppose the flow of control in most structured applications the flow of control points in the same direction as source code dependencies in a good object-oriented program And this is how you know it's a good object-oriented program key dependencies are inverted to point against the flow of control and that inversion is accomplished through polymorphism So the OO Paradigm is polymorphism which is discipline imposed upon indirect transfer of control. (c) Uncle Bob
An array is obviously more performant, but you'll need to resize or allocate the correct size yourself if you use one. However, since an ArrayList is doing exactly that, it tends to be the most performant List implementation. Even more so if you can predict the size requirement as you'll have what is essentially an array.
@@hannessteffenhagen61 Because if you want performance, you need to measure, not assume. And even though there's reasons to use dynamic dispatch, it is typically not chosen for performance reasons, because it will be a cause for cache misses and branch mispredictions.
@@Muskar2 have you ever measured performance in your life? If yes, you'd know that ArrayList outperforms any other list implementation for typical things you do with a list. That's why it's the default you reach for unless you have a good reason to believe that something else would work better.
What I love about OO is how you can do inversion of control and can combine this with dependency injection. Now you have a Russian doll class that has a class that has a class that has a class all injected into it by pure magic to the point where nobody can figure out the stack of what method is called where and why. Especially love it when team members go crazy with this so nobody can ever maintain the code AND keep their sanity at the same time, not even the creator of the code in the future. Now the whole project needs to be rewritten before a new feature can be implemented insurring everyone's job security.
Yeah I also sure as hell love some if/else or some switch/case nested deeply down the call stack reacting on some random enum or boolean flag in some stupid global state that is completely unpredictable! I say this because this is the non OOP alternative I have to deal with constantly. And I am pretty sure it's not any better, probably worse.
@@freesoftwareextremist8119- weird take, you can use global variables in OOP as well. If/else and switches are much clearer than having to jump between the call stack 20 times because someone doesn't understand that dependency injection hurts readability.
IOC is not an OOP thing. I use it frequently in embedded C to avoid explicit coupling when writing reusable code. It helps to avoid having to test everything on the target, or on a slow simulator, or using complex tooling to mock out dependencies with code generation and linker magic. I know it's typically much more ridiculous in OOP languages that use it everywhere, but the concept is much more general than that.
C# has a too many features that require an editor/IDE that follows the dependencies since everything can be hidden by namespaces and classes can also be extended by other classes invisibly instead of composing them. I find that C# is better than Java, but the scary thing is that any primitive can be extended by extension classes, because boxing exists.
Getter and setter were actually rooted from C# concept that you can bind those attributes directly to UI components. So it's actually not for the better code, it's for better binding, which is not useful outside C#
The problem with inheritance is that it has to be 100% correct - if somewhere in the chain something is only 99% what its ancestor was, the hierarchy falls apart - and there's no way to predit on step 1 what step 17 will look like Also, even if technically 100% correct, it's not always useful, e.g. "square is a rectangle" example, where even if it's technically true, inheritence wouldn't work well for it
If inheritance isn't good for it, then don't use it. If you are building your code well then step 17 would be 1-3 levels down from step 1 in inheritance, and you can easily check the conditiions on step 1 in relation to your object. If you are going beyond that then you might need to refactor your code and maybe think about it differently (or stop listening to Java training). Maybe an interface would suit it better or maybe the objects should be broken up a bit more, or that function you dumped into the object should be taken out and used for the whole program. The issue here is lying with the way you have designed the code, not the fact you can inherit from another class. Take programming a game, something done more often in OOP these days than not. You may have entities -> dynamic Entities -> player character, alongside the PC may be NPCs. An entity may just have the var of the name of mesh used, a position in worldspace co-ords (often a struct or object itself), an overlap function IsOverlapping(Entity obj) etc. Dynamic entity may have the list of animations, bone structure, min/max movement speed, health , killDistance or whatever. The player Character may have number of lives, functions that fire events when the pc is killed, points total, weapons and inventory etc. Whilst the sibling NPC might just have a string for the mesh used as a weapon, aggro factors etc. I remember the A-Life system Stalker came up with that had all creatures have a hunger/satiation, fear, and risks values on them as well as their food types etc, this was applied to all creatures in the game right down to human npcs. The creatures would take bigger risks depending on their hunger level. So you could sit on a hill and watch a bunch of dogs hunt down the huge pig things, or the dogs would avoid them as they still posed a risk and the dogs had not reached the point where they would risk it. This sort of system is ideal for inheritance. But if each creature has a distinct set of values and works in a completely different way, then you wouldn't.
31:30 getters and setters do allow things like eg the classic “shapes”/“polygons” superclass Shape can have abstract .getPerimeter and .getArea and then square and pentagon and the mandelbrot set can all implement both of those differently from one another getters and setters allow u to have “traits” like perimeter and area that correspond to real traits of the instances of shape but don’t correspond to any hidden properties of those instances directly..
As a C++ enjoyer, structs with functions rock
Yes I do create real classes but we love glorified buckets of data
Glorified buckets of data is where is at
nailed it !!!
OOP and FP are both obsolete. There's a new programming paradigm called Dysfunctional Programming. It provides a bipolar flexible path forward. Instead of "this" property, we have "dys" and "dat".
the "that" keyword, which always references the most recently referenced object that isn't "this"
hustle{
await dys
}
watDysShit(shit){
burb(500,Shit)
}
@@aiacfrosti1772I would argue that this paradigm can empower bad practice such as double aggregation reference
41:33 "To a certain extend..."
Bro is so OOP-brained he made a Freudian typo
I finished your "The Last Algorithms Course You'll Want", and .. it was awesome. You motivation message is now deep in my heart. Thank you for doing this!
Pirate it
@@vasachisenjubean5944 its free
Liskov Substitution principle: "If it looks like a duck, quacks like a duck, but needs batteries, you've got the wrong abstraction"
what if we are talking about a duck toy?
@@davidlaraezm duck toy != a duck ;)
That's why you should figure out and organise your data structure before actually implementing it in code, huh
@@helgenlane I don't know if that was deliberate, but you just summarized why inheritance hierarchies always end up sucking ass in real projects. In most projects, nobody can predict what the appropriate OOP-correct data structure will be because it changes over the course of the project, and adjusting those inheritance hierarchies to fit after thousands of lines of code have been written using them can be awful. That's less of an issue with a model that relies more on composition and traits/interfaces, keeps things more flexible even when stuff changes.
Hahahahahaha I laugh so hard
Object Oriented Primeagen
Here's my take on getters/setters: they are useful for math vectors. Just do "vec.length += 2" to make vector longer by 2. Or "vec.length = 1/vec.length".
In fact, they are useful for many objects, that can have multiple parametrizations, but internally we just store one parametrization, as to not have multiple types doing same thing.
Real OOP has never been tried
Perhaps it has; but only in some remote closed commune, requiring unexpected levels of commitment... The legend of SmallTalk.
Actually, I hear SmallTalk is awesome. It's typically used in its own special fully-modifiable programming environment.
With an actor framework like proto actor you have actor objects that can send signals to each other. Feels like the original definition
@@NathanSmutz we use smalltalk at our company
is this a political joke
Just FYI Prime, Liskov's applies to Traits too, and any function pointer(ish thing)
edit: as a practical example: a function calls another function via a pointer, and expects that function to return the same value with the same parameters with every call, but if some implementation returns a new value with each call it violates the principle and may lead to incorrect behavior
Exactly! It is a design principle that is valid where ever functional variance can be implemented. It also applies to non-OO languages like c when they provide dynamical behavioral variance.
Are you saying that the Liskov substitution principle is equivalent to pure functions?
@@etodemerzel2627 No. Pure functions do qualify, but Liskov's does not prohibit side effects, only that those side effects adhere to the interface. The example was of idempotence (which does not require purity), but another example could be an implementation that terminates the application; unless the interface permits that, such an implementation could exhibit undefined behavior (e.g. files left behind, or perhaps the termination function itself has been overwritten in memory, so instead of termination, some other behavior is triggered [not likely in modern languages, but Liskov's applies to assembly too, where self modification isn't that uncommon historically]).
The principle is about the expectations / specifications of the interface, and reaches further than just the type information we can declare in modern languages.
A video of a man reading an article.
If you plug in your ears you can notice the commentary
Liskov still applies to interface/trait based programming. It means if something implements the interface it must do so without changing the intention of the interface. You can’t just pick and choose what methods of the interface you implement. (It may meant you need to split the interface or have the design wrong if something can only implement some of the interface)
The point of getters and setters is that the class can always take control of private data without changing the interface.
You can trigger onchange listeners or reject the new value.
But no, it almost never gets used.
Not only does it almost never get used, the "taking control" almost always requires adaptation in the client code anyway, at least long term, or you will just slowly create spaghetti. (taking control almost always means increasing the amount of responsibilities the class has)
I've used setters sparringly, but I do use getters a lot, especially for things that Prime showed..
If my class has lists, and I want to get the total from those lists I consider that those lists are properties themselves, length of those lists are in extension also properties of those properties, so a "public int TotalRecords {get { return list1.Count + list2.Count;}}" makes perfect sense.
This is still a "method" with additional contextual information making it clear as day to anyone what this Property contains and that it's read only. There's literally no purpose for something like this be it's own function. I believe that functions/methods should contain some kind of operation on the data that isn't exclusively reading, while getters are perfect for those situations.
Another use case is when writing object models for parsing json. Client maybe has cardinal directions as words ("up","down","left","right"), but for coding purposes it'd be more helpful to have this as an enum, so public Directions CardinalDirection {get { switch...case..."up": return Directions.Up;}}.
With this approach my object importing is a lot simpler and I have immediate access to properties I need in the format I need them.
i dont entirely agree with that.
sometimes, and for simple things, getters and setters are an east interface to connect two components when one has a very simple value that is only used in order to update anohter component, and also needed for other states.
not everywhere, but useful.
for example, a Unit's HP
This is why objects which are likely to require this type of control should use getters/setters but most should not. If your base class requires this type of control, use them. If it does not, don't. Then if needed, create special cases in the derived. Which will likely only be used in the special case derived, likely implying it should be private anyways. Which means the special case may well be hidden within the specialized derived implementation. Which brings us full circle that for the vast majority of cases, getter/setters should be avoided unless it's a requirement of the base.
Contrary to one of the of gallery comments, composition does not avoid some of these complexity or issues in any way. It simply changes the surface area where it does.
I'm honestly shocked so many people lack such a fundamental background on OOP yet are willing to rally against it.
I really like getters in C#. In other languages, usually I find necessary to write methods like len() to access read-only fields which is a little annoying.
The "L" response from ChatGPT caught be off guard 🤣
16:21
@@firen777 not all hero's have capes
"not all heroes have capes" does not mean "no heroes have capes" @@mrfact03s
@@mrfact03squite literally everyone here is a hero exept you.
Prime talking about traits as if they're fundamentally different from inheritance.
A first level trait is an interface (and possibly a base implementation class), combination of traits can be seen as multiple inheritence.
Fundamentally, the choice of your 'abstractions' is what defines how well either options do.
If you create a bird abstraction with the underlying assumption that all birds can fly, then your abstraction is wrong.
You forgot to mention the kinds of birds that can’t fly: kiwi, penguin, ostrich, and dead
@@airkami on top of birds what can't fly, how about an abstraction that accounts for birds that only fly three feet, like chickens? I mean they don't really fly-fly, but they can get off the ground, right? It gets absurd after a while because to create the abstraction "correctly" means you have foresight to account for everything in existence, or everything that can happen in advance. I dare say that even in biology that can't happen as we make discoveries. We need to account for the unknown somehow? Dunno, just asking.
God made birds to fly. Birds that cannot fly are literally fallen creatures and not deserving of further categorization. This includes flapping gliders like chickens.
Traits are kinda the parts of inheritance that make sense, and removing the part that does not. You don't get "parent class" type hierarchy, where you need some sorta flying-baseclass. This class might be nonsensical without context of concrete child classes. Also, the implementation of interface in class-system can only be done by one creating the class, inheritance is bound to class definition. Trait definition can be also just as well used along with interface definition. Further, inheritance muddies the location of where the data is defined. With traits, data is all in the struct, there is no inherited data coming from somewhere else. With inheritance, the parent class(es) might contain data that the child would inherit.
Traits are sorta like inheritance, if you removed the most significant problems in inheritance.
Classic "it's not the system that's the problem, it's everyone who uses it" defense of inheritance. If everyone constantly fucks it up, that's a big red flag that the system sucks.
Liskov substitution means that when a function wants a "bird" as an argument, you should be able to pass a "sparrow" or a "chicken" and have the program stay correct. In other words, all subclasses must contain all data from their superclass.
it does not say anything about data. It only states, that a base thing defines an assumption and that every child thing of that base thing must follow that assumption.
Collections are a great example. Every collection is iterable, but pretty much all of them have different internal implementations. But, again, they all hold an assumption: a collection is a thing, that represents sets of things, might be empty, and you can access thins in the set one by one. It might be a continuous block of memory, a linked list of block of memory, a hash map, a file, a network socket, even a generative corotine, that spits stuff when asked.
Liskov was high on drugs
There is no such thing as a bird.
There are things with feathers, things that can fly, and things that tweet
Traits/interfaces trump strict object definitions
And duck typing with compile time checks trumps traits
Java < Go < Rust < Zig
@@steveoc64I disagree with that last part. Explicitly mentioned interfaces are better.
Then again, I'm also against the "typeclass" mechanism, where the compiler finds the right implementation. Even *that* is too implicit for me.
"In other words, all subclasses must contain all data from their superclass." can you explain why??
@@steveoc64duck typing with compile time checks: Python 🐍 with pre-commit strict type checking.
😂😂😂
I'm not finished the video yet, but I felt the inheritance in oop is like directory and the trait in rust is like tag in note taking management. And I love use tag more then directory.
Duck typing is not checked only in runtime. In C#, a foreach loop uses duck typing and verify at compile time if the object has an "GetEnumerator" method (either a classic method, an implementation of IEnumerable, or an extension method)
There is a great definition of the OOP in the MIT 6.001 SiCP:
"One powerful design strategy, which is particularly appropriate to the construction of programs for modeling physical systems, is to base
the structure of our programs on the structure of the system being modeled. For each object in the system, we construct a corresponding computational object. For each system action, we define a symbolic operation in our computational model. Our hope in using this strategy is that extending the model to accommodate new objects or new actions will require no strategic changes to the program, only the addition of the new symbolic analogs of those objects or actions. If we have been successful in our system organization, then to add a new feature or debug an old one we will have to work on only a localized part of the system." (Part 3, Modularity, Objects, and State)
Without any classes, SOLID, GoF, GRASP, inheritance, and other stuff.
"Mutable objects with message passing."
Thank you for coming to my SmallTalk.
(FP bro take btw)
The main keyword here being "hope"
If the system you are modeling contains hierarchical relationships (most do), then you are not being faithful to this definition by avoiding inheritance for the sake of avoiding inheritance.
@@Prim-v1d Actually, nothing prevents you from building different objects with their own methods and fields, without inheritance. Taxonomy does not exist in the real world, and eagle, tit, and thrush are not inherited from birds, but unique things that have their own feathering, beak, and wings and they fly and feed in their own way. This is if we are extra-meticulous to the words in the definition. If not - we can rely on common sense and abstract out some stuff (aka methods) so far this does not interfere with us building the system.
@@Prim-v1d But in general, I cited this definition (from the MIT 6.001 SiCP) as an example of a fairly complete, but at the same time absolutely understandable definition of OOP, which is not overloaded with unnecessary details and restrictions. Like those that “if there are no classes, then it’s not OOP”, or “if there’s no encapsulation, then it’s not OOP” and the like. The OOP can be implemented in pure C, and it will quite be "Object Oriented" Programming, it simply will not fall under some definitions that are widely used as dogmas or axioms - although for some reason no one ever says when and why they became dogmas and axioms.
45:40 I'd like to point out that getters and setters ARE methods. Having them appear as fields with assignments is just weird syntactic sugar and functionally unimportant. Especially in a language like JavaScript.
I hate this in C# too. I just don’t get the point of properties. It really confuses beginners in programming.
As someone who's programmed in multiple paradigms, my favorite paradigm is whatever paradigm solves my problem for me.
I have no qualms mixing functional with OOP. It all depends on what the problem is.
I think most people get too caught up in one way being the right way, rather than being able to know when to use one or the other.
@@thederpykrafterikr. But where's the fun in that?
Even with strict design patterns, knowing when to break the rules is critical.
In clojure, there's side effects. Even though it's functional.
Globals and Singletons are bad, but sometimes they are the best solution.
Why? Because in the real world we deal with these problems. Purity is impossible.
The science is knowing the correct thing to do. The art is knowing when to do the incorrect thing.
I will create an object with no real world significance with a load of methods with no persistent state, while totally deriding functional programmers.
I will also crate an object with all the data in it, which I will call either O or wOrLD and pass he bloody thing everywhere.
@@TheLucanicLord that sounds painful 😂😂
I call it Context
@@thederpykrafterYES! This! These are tools to help you complete different tasks. Don't be too wedded to one paradigm or another. Learn as much as you can about all of them, and use the correct "tool" for the job(s).
I think you might be interested in Odins solution to this. While it generally behaves a lot like C, there is support for subtyping and generics. As a bonus there is even an (experimental) feature on the lsp called "fake methods" to make it easier to find the procedures that are associated with a struct.
5:19 "get is not a fking procedure" 😂
@5:38 In ODIN you simply perifx function with struct type name car_add, car_delete and everything is clear, what belongs where
Yeah, the syntax argument is silly. You can also write Object Oriented code in Odin as well, it just doesn't have any specific syntax for it.
What do you name a function that takes two things?
Car_road_drive? Road_car_drive? Both?
The power of typing "car." and getting a list of functions that are available is specifically what he referring to.
@@amoskevitz first thing (argument) is treated as object on wich methods are called on other languages
People love to tout that getters and setters for private members are "good" because they compile as functions and can replace that implementation with a calculation, or redirect to a different property, in the future.
In practice, this rarely happens. The more commonly needed point for getters and setters, at least in some languages, is to define asymmetric access levels to state. Unless I am mistaken, in C# it is still impossible to grant public access to reading a member value, without either granting write access, or encapsulating via property.
Strictly speaking you are right but C# simplify and hides getter and setter and reduce it to 1 liner so you need only change {get;set;} to {get;}. Actually encapsulation only makes sense when others work on your code or at least works as bridge like a public API or sever -client communication.
I think that getters and setters are useful when we need to add some checks and boundaries to the attributes. Like, if we want to verify the parameters before modifying it, we create a get and set, where the set verifies the inputs. If there is no need to verify the data that we are setting, just make the attribute public.
Always when I tried to create a good inheritance chain (bird), I later in time find the "ostrich" and have to change all user to "flying bird)....
That sounds like poor planning. 99% of the time it's predictable.
"OOP is a path to many abilities that some consider...unnatural."
CLOS oop*
@@Oi-mj6dv What do you mean?
Majority of Prime's viewers are college students and non professional devs, college students are taught Java in school which is OOP so the poll makes sense.
meh. In college, I never wrote any code to model AST nodes. After college, I found that a class hierarchy was the most natural way to do it I could find. Is it the best? I don't know. But it seems to work better for me than anything else I've seen. Skill issue? Maybe.
My college taught c++
I was taught C, C++, Python, web tech and Java personally. Prolly should be learning Rust on my own. It really depends, usually good schools teach you C and FP first before implementing OOP in C (through custom vtables and stuff) and then tell you how C++ abstracts all of that painful stuff, then it's off to general OOP with java or smthg else
@@recursivThe problem with strictly OOP languages w ASTs is that once u build your tree, it's a pain in the ass to perform general operations on your tree. Having currying, match patterns, and higher order functions makes AST stuff way more interesting imo. ZIGs source code is insanely clean if you're looking for more procedural/functional examples
@@xx-vg5fj OOP isn't mutually exclusive with pattern matching.
But I take your point.
The problem I'm having is I'm encountering people that do not understand procedural programming! Some people I work with think that my code is really weird because I rarely write classes. Their default position is to encapsulate and information hide. They are defending themselves against some imaginary future battle that is raging in their minds where the code has to be modified or re-factored, and "putting everything in classes makes it easier" - because they read it in a book in college. However, I've been writing code for money since 1988, and I can tell you that code re-factoring never happens. The code just gets junked and re-written from scratch. It really does. If you understand the problem domain, it's just easier to start from scratch unless the change is very trivial. For me, if I don't need multiple instances of something (I'm talking about C++ here) then it does not go into a class. It's that simple. Sometimes you are forced into them - for example in C# you'll often be given an object instance as a return type. Fair enough. I do like how the dot notation can make code self describing. In C++ I can get a similar effect from namespaces without requiring classes. OOP is fine, just don't over use it.
Hmm I wonder why code is easier to write from scratch then refactor when you made it hard to refactor 🤔
@@arch126 To me, re-factoring non-trivial code requires getting into the mind of the person that wrote their code and understanding their style. I have been successful over the years in doing that - sometimes you know which colleague wrote a piece of code. However, it's often just too damn difficult, or too damn time-consuming. So it gets junked and re-written. I'm not saying it's the correct thing to do - I'm just saying that it happens.
He is Unlimited coding content provider
ccp mentioned
Getters and setters are a useful debugging aid, that's all i.e. you can stick a breakpoint on a setter to see when it gets changed and by who.
wouldn't a watcher do the same thing?
Their real benefit shines when using reflection features. That's the main reason of their existence instead of a mere method. They have no reason to exist in languages devoided of reflection capabilities.
@@defeqel6537 It would
@@kirkanos771 I call them vampire languages
@@jacksonlevine9236 ironically, they are less vampiric when not having those features. As the main purpose of those libraries is to vampirize the code at runtime.
Loved hearing even a tiny bit of your thought on lua and metatables, I begun my coding journey in lua (im sure you can figure out where..) and the learning of metatables and metamethods was for me a godsend when I learned when, where and how to use it properly.
Would love to see you go through some lua some day
in c# getters and setters let you set breakpoints on prop change and also work a little better with intellisense. also if a class is defined in a dll, you can add code later to hook the getter or setter without altering the interface. so I kinda like them.
Prime: OOP or FP?
Me: yes
Me: Smalltalk.
Found the scala dev
@@gagagero Chad
@@colemanroberts1102 Kotlin, but Scala is fun too. (Actually a java dev though professionally. Java+Lombok+Manifold can get quite close to Kotlin though)
Crazy how long the discourse between paradigms has been going on when it's so obvious that 1. it's almost always domain/implementation dependent and 2. a combination of everything is almost always the best. Procedural by default with OOP concepts for managing data/custom data types and FP concepts for managing logic and control flow, and 1-2(max) layers of declarative functions wherever is practical with the rest prioritizing an imperative style. Extension traits and composition over inheritance every time for the OOP side and you're golden. Just need a good, *un-bloated* language with a best-in-class type-system (never used but I only hear great things about Hindley-Milner) that isn't trying to be a one-trick for once and devpression would plummet.
I think people forget that a paradigm is not 100% bound to language. You can write OO code in c ,as well you can go procedural in java. OOP doesnt means infinte heritances with mixins and abstract classes with a bilion interfaces, its just a style of programming.
Yeah - it can be abused. We've all seen code that abuses OOP principles more as a cock-measuring contest "ooh... Look how leet my OOP skills are" that result in a confusing and over-complex mess.
I just wanna say that asking the chat for vote or testing something with coding or searching anything is VERY helpful,
Thanks man..
Kotlin does well by eliminating all boilerplate to properties (fields with getter and setter) by basically just automatically wrapping declared fields in getters and setters. Still begs the question of why do it at all tho. Only advantage is that you can overload the get/set, which I like for pretty readability, but part of me thinks that code that does something expensive under the hood but just looks like a property access is code that’s lying to you.
It’s just some part of me really want to obey data = noun, function = verb
i actually like Kotlin's getter and setter for all object property assignment and referencing but I think there has to be a better syntax to whatever Kotlin is delivering
Fighting off a creeping understanding of OOP is a daily struggle.
The problem with inheritance (and LSP) is that it is strictly hierarchical. It breaks when real life introduces cross-cutting concerns across multiple classes with different inheritance trees. We get all the weird object names because the "fix" for cross-cutting concerns is to add new classes to make the hierarchy self-consistent. Then as the code runs into more and more reality, more and more classes are added to the hierarchy, because hierarchies are only occasionally applicable to some real life problems, but not most.
You should not use inheritance to model everything because not everything in real life is hierarchical. Some things are compositional, and you should use composition to model them. But many things in real life are hierarchical, and it is awkward to avoid using inheritance in those scenarios.
manually rebuilding inheritance tree over and over with ad hoc nodes that make no sense
@@Prim-v1d Exactly. Some domains inherently lean into inheritance. Some into composition. Some into neither. The failure isn't OOP or composition or functional or whatever. It's the designer. The right tool is always the right tool. The trick is knowing when to use the right tool.
This reality which a lot of software engineers like you @uumlau paint is a compelling one. However, it's due to the abuse of inheritance and not inheritance itself. I recently believe that there's nothing wrong with inheritance as a tool for code reuse. Yet, there's everything wrong with how inheritance is used and implemented in most codebases around the world.
Inheritance hierarchies should be built bottom-to-top and not top-to-bottom. All of the problems and issues people experience with inheritance has to do with software engineers building the hierarchies from top-to-bottom. Issues like tight coupling, deep levels of inherited classes.
Most examples of inheritance innocently suggest you should create the parent class(es) first. This is wrong!
When using inheritance create your sub class(es) first and build the hierarchy from the bottom (subclass) to the top (parent class). For instance, why do i need to create a Shape class before i create a Circle class OR Why do i need to create a Bird class before i create an Ostrich class ? Why ?
There's no reason. Even the Java Standard Library got this wrong!
And now for years and decades, we have carried on like this. Doing the wrong thing and expecting things to be fine and when they're not fine, we blame to tool. We blame inheritance for all the bad things that we encounter. When we should blame our ways and methods of using inheritance.
Most of the nagging issues around Inheritance disappear when we build from bottom-to-top. We even discover that most of our parent classes become Abstract classes as opposed to concrete classes when we use inheritance properly.
I know this because i have applied it to my work and seen better results.
Read more here (in my article): isocroft.medium.com/the-growing-spate-of-ill-subjectivity-and-presentism-about-software-engineering-practices-c74c6bf34cad
@@Prim-v1d- "some things are compositional". Soooo... not OOP, then? Because composition existed long before OOP. If you're advocating NOT using an OOP-specific features like inheritance, and you're instead saying a non-OOP-specific Ike composition feature be used, you're essentially confirming what OP was saying - use less OOP.
6:35 "OOP just means we model our problem using these objects", isn't it domain driven design?
Generic functions give the best of both worlds, e.g. Common Lisp Object System (CLOS)
Based take.
people who say that they love OBJECT oriented programming usually either have no idea about FP, or actually love CLASS oriented programming.
majority of practices, patters and principles are about how to use CLASSES.
the favorite feature of OOP lovers is inheritance, and it's purely the feature of classes.
instead of calling it OOP, we should call it COP.
Because its cope, yes
Just in case: COP stands for Compression Oriented Programming. See Semantic Compression article by Casey Muratori.
How about erlang processes as objects? They fit those OOP definitions even better than classes. They hold state shared between a set of functions, fully encapsulated, effectively subtyped since eny message can be received by any process that accepts it. Sounds very OOP to me, you write them with FP though 🤘
Re: inheritence -- you should definitely use sparingly, but its great for things like Strategy Pattern
You down with OOP (Yeah you know me)
29:48 - Caching is a big one imo. It's nice to have a property which caches the reults of some slow calculation. It'd debatable that you would want this as a property though instead of a method.
The use-case for getters and setters over functions/direct access isn't "it's better to hide the function" or "it's better to have a trivial getter/setter function over direct access"
The real use-case is when you have code that exposes properties that are already being used directly (sometimes outside your control, like when you ship a library) and you want to change the underlying implementation without breaking clients
Obviously there isn't a reason to expose a .length getter in new code, just expose .length() and it's clearer
inheritance has really specific use cases that don't come up that often. while technically useful, I don't think it should be considered a "pillar" of oop.
Tbf, Composition should replace it as a Pillar. Then again, the four pillars most people consider OOP, well not all are…exclusive to OOP.
Namely Encapsulation or Polymorphism.
True
Its more (actually, all) about interface (contract) inheritance, not about actual code inheritance
I agree. But to give context: in the early days of OO like the 1980s and 90s, interfaces were mostly classes and not defined as an abstraction of its own. So, there were different methodology types of inheritance: type inheritance, interface inheritance and actually interface implementation. Bc the latter was only deriving from an interface and implement the behavior.
Also, in the beginning type inheritance was much more praised. With experience "good" OO principles were defined. This is where LSP and Gang of Four come from.
Nevertheless, inheritance is a pillar of oop. It is only not to mistakenly used where a has-a relationship is the better option. And that is often the case. A guiding principle is to think of if the relationship is static or dynamic.
I agree too. I already did another reply like this, but my point was, when you leave inheritance away, OOP no longer conflicts with functional programming too.
Somebody mentioned that OOP is actually more opposite too procedural than to imperative. This is at least correct for the concept of methods.
Languages like Rust still use methods, which add behaviour, logic to data types, effectively turning those into objects.
Polymorphism in functional programming languages is achieved via type variants, discrimated unions, like the enums in Rust. Generics are also used.
And extension methods like C# has also count here, because these are often applied to interfaces, and are even used as a system of higher order functions known as LINQ.
@@twenty-fifth420There are cases where inheritance is the superior option. I just don't trust myself or others to know when that is the case, because if it isn't then you're going to hate life.
19:50 thats y we do interface composition and ignore inheritance in all instances besides very specific, well-known, specialized cases.
I use subtyping extensively in my c++ UI/rendering/game library. It is very useful for describing eg Window -> Control -> (Listbox / EditBox / Scrollbar) or other similar items that all have a position on screen, rendering and input similarities. I don't understand why people have such disdain for inheritance.
I guess it works until you have to duct tape the object graph. While it's pretty shallow it's all sunshine and rainbows.
What if I wanted to have something that's both a ListBox and an EditBox?
Two levels deep is manageable. Ten levels though... I guess people are just burned out on way too big inheritance chains so they decided to just throw the entire concept out the window.
What if you have a control that needs to behave like both a listbox and a scrollbar?
one does not need inheritance for that. in low-level languages like C, just cast a pointer of several types to a common type, that has same field offsets and sizes as the common part. it's also possible to do composition or exclusive unions.
20:26 you can get that same inheritance vs composition complexity when considering only interfaces (or rust traits). Like if using interfaces/traits to assign behaviour, at some point you need a way to require a thing that implements multiples of these interfaces/traits, at which point you’d probably introduce a new interface that implement these interfaces. So classes and subclasses aren’t where the complexity exists or where it’s defined. The classes are just implementing the complexity.
You wouldn’t make a new trait that requires other traits. You would write functions that expect some type that implements exactly what you need to use in that function
Come to the black side, my son. Don't give up the coffee. Give up the cream and milk, the froth. the sugar.
Like my coffee like my women 👩🏿
@@Jabberwockybird Ground coarsely and dipped in boiling water?
@@Jabberwockybird roasted from greens using a portable heat gun and a frying pan, then ground and drawn through a glass syphon ?
Yeah Boi
I'd say cycle off caffeine for a few days every couple of weeks. That way you don't get addicted, and it works more effectively.
@@andybrice2711 100 mg of caffeine, i.e., around just under 250 mL (1 cup) is all ya need in a single day. The hard part is keeping it limited to a single cup.
One of my favourite feature in c++ 23 is how good the concepts have gotten, its like attaching behaviour checks like requirements of methods and variables etc. I haven't touched C++ inheritance in a new project
C++ is kind of trying to phase out inheritance. Its still there, but programmers have been coding complex problems for the last 20 years and we know inheritance is bad (for non-gui stuff).
@@complexity5545 yeah the langauge proposal lead by bjarne stroustrup if I remember correctly, he wanted it to land in C++0x/11 but it didn't happen because of radical change in language. I am glad its here though, I absolutely hated the previous errors lead by templated code (its still here but concepts. are better)
19:15 Well, isn't programming about getting things right? Isn't good design about getting things right?
Good thing about these principles is that: 1) you don't really need to adhere to them (except for maybe S and O) 2) Your extension methods or mixins or addable traits or what have you don't really impact the adherance, except you have to implement them for every object you have (if you have lots)
"Omg it IS like communism. If you do it correctly, it works!" - The Primeagen
The thing with communism is that no one is able to do it correctly. With Liskov substitution principle, a fucking word calculator is able to come up with idea that works.
Same goes for capitalism.
@@Rockyzach88No, capitalism doesn't work even when you *do* do it right. See: current state of the world.
In theory, communism works.
In theory. - Homer Simpson
Yeah I mean look at all these modern communist inventions like electricity, digital computers, combustion engines, HVAC. Just imagine if the world was capitalist, we'd all be starving in a Holodomor @@NXTangl
37:16 How does go-lang get the encapsulation checkmark “on package level” while JavaScript *doesn't* get one “on module level”?
Only Alan Kay understands OOP. The rest just think classes (Simula style) = OOP, which is NOT the case.
You'll understand too once you've reached Lisp enlightenment.
@@freesoftwareextremist8119 I have and knowing that closures are a poor man's objects & objects are a poor man's closures, SBCL is one of my best tools in my toolbox alongside NeoVim :)
@freesoftwareextremist8119 ALL HAIL THE CLOS
29:39 Have to do Java at my job and our use case requires eclipse. Using getters and setters works much more reliably when you want to see where a member variable will be accessed from.
However I think a lot of other IDEs handle that better. And I really don't wanna use eclipse anymore
5:40 what prime is talking about is dot notation, and it's not exclusive to OOP. You can do that with well made modules, OCaml has the best handling of it that I've seen.
Skill issue
Oh, yes. This is the answer for my ponders of how you wrap pure functions together in usefull maner. And it was so obivious, too close and maybe because people has been trashed idea of good file names too much... and whole OOP dominace in this space.
Bonus is, if you have design rule of not hiding or mutate module names in import, then you will think bad or long module names....
Please no more Helper_util files.
helper_util.smooth()
helper_util.parse_line()
or
rate.smooth()
style.parse_line()
When lightning strikes a tree, who's behavior is that?
Idk, what are you talking about? What the - why did I read it out loud?
one of the best things about this video🤣
also... the lightning's behavior. It's right in the question: "when lightning strikes".
I think his point was that since the core idea of OOP is that data and behavior should live together, it leaves a big gap for interactions, where it isn't clear which one of two objects own the behavior, often leading to artificial "helpers", "managers", etc. that are basically just trying to OOP something that's inherently imperative
It's easy to marvel at "look how nice it is where only the object can access it's properties", until you hit these and the abstraction breaks
@@TruthAndLoyalty "You wanted a banana but what you got was a gorilla holding the banana and the entire jungle" OOP encourages not only this type of nonsense but also weird metaphisical questions regarding behavior is your tree stricken by the lightning or is your lightning the striker of the tree. Fucking nonsense
@@neko6this.
@@Oi-mj6dv it really doesn't cause those issues. IMO that's a product of teaching oop as if its a matter of taxonomy. It doesn't actually matter "who" the behavior "belongs" to.
Oop is a tool for managing complexity. It's not classifying species, designing convoluted inheritance trees, and answering philosophical questions about the is/has duality. The pitfall is that people tend to have difficulty learning how to do that, focusing on the idea of real world objects and "classes".
No tool in programming is an automatic win. Shooting yourself in the foot with oop is largely a skill issue. A lot of people hit that roadblock a couple times and decide OOP is the problem and never get good at it. It's apparently hard to teach. I have yet to see a good class or book on the matter.
There are some specific disadvantages, but surprisingly those are rarely mentioned in these conversations.
There are edge cases where setters and getters are useful.
1. you only want a getter to make a variable readonly externally
2. you want a setter to perform actions on the value every time you try to change it
I'm sure there are a few other use-cases out there, but those are the 2 that I come across most myself. If I don't have a specific use for setter/getter, then I just use a property because like you said it's exactly the same thing at that point but more work, as well as harder to maintain / follow.
Functional programmers like Erlang programmers are actually the best OOP programmers
It's probably because they try to clean their functions and make them as pure as possible.
Yes and unicorns are the best horses… btw Linus would like to have an f word with you
Erlang is an object oriented language
18:48 I have one gripe with the way Rust does it (don't know about Go) is that unrelated modules can attach behaviours too.
Like if two different modules attach implementations of the same trait (like from a shared dependency) for the same struct (like from a different shared dependency), shouldn't that clash? Especially when you have a trait pointer to that struct?
techinically. you can either attach internal trait to external struct or a internal struct can implement an external trait. importantly this done at the crate level not module level
the closest you can get to external to external is to create a new type that wraps over the old type.
@@jadonbelezos2583what I mean in specific is:
crate a: defines struct X
crate b: defines trait L
crate c: implements L for X internally, has a function which returns a Box which is an X
create d: implements L for X internally, has a function which wants a Box
The moment a programmer writes getters and setters, they are no longer doing OOP.
why?
@@ayoubelhioui2205Because it violates encapsulation. Whenever my users want me to show more than an empty screen, I tell them that it is not possible because I cannot expose the internal state of my objects to them.
There are many bad getters and setters and there are many good getters and setters and there are many bad developers and only a few good developers.
@@ayoubelhioui2205 because if you manipulate those objects from a services for example you are just doing procedural code.
You could think of c as object oriented, just at a module level. If the identifier isn't extern, theres nothing pointing into it from the outside. So to access it, you have to access it through the outward facing public interface(methods/accessors) that were compiled into the module. Otherwise you would have to like, parse the memory of another module to in order to get at the data. Each module can matain the storage of a different collections of data types, and everyone else just points into these types via handles/pointers.
The biggest problem I've found with oop(I'm a junior dev) is that it forces me to make my code into a specific structure that very easily loses its structure by implementing a virtual method in the superclass that doesn't belong there but ends up there anyways because it's easier and then if you want to change a class, you have to change every class that has that class, I've found it more useful when making something that needs to or benifits from hiding details behind abstractions.
To be blunt, that isn't an OOP issue but a skill issue. You are using inheritance wrong if you run into such a situation. It is a sign that you need to rethink how you are doing things.
I'm confused virtual methods are the ones which are able to be overwritten in the inheritance so why are you run into problems with it ?
The biggest problem with OOP is that it is usually taught wrong. If they teach you inheritance as one of the major things to know, they are doing you a disservice. I agree with most things in this video.
If you need to hold data and pass data around: use records (structs). Think about making the fields readonly.
If you need to connect more distant parts of your code: use Interfaces that define behavior.
If you have a thing that has internal state and some exposed behavior: use an object with public methods and private fields.
Objects can implement one or more interfaces.
Interfaces can extend other interfaces.
Base classes and virtual methods are almost never used. Only in some cases and only within a single module. Never make public Base Classes. Use Interfaces.
-- The way C++ "invented" OOP in the beginning was a big hack to make message passing work relatively fast with the technology of the time. That way of programming stuck around for to long. Now C++ is much better, but habits die hard.
@@evancombs5159No. sometimes it’s best for the „code to do what the code needs to do“. OOP often feels like translating simple things into a completely different and very static way.
go is just works, no fancy concepts, easy syntax, good standard library, fast...
The most OOP I go is Object. Composition is far superior to classic o o classes and their instances and avoids the spiral down the rabbit hole that oo inheritance takes you.
This is a ailure of your education. Do they not teach "is a" vs "has a"? "Is a", means inheritance. "Has a" means composition. You can use both at the same time. The complaints here about OOP is really a skill failure in understanding proper OOP design and engineering.
For example, people often create a Car and then derive a Truck and then mandate a Car or a Truck also has wheels and transmission and an engine. Then try to create interfaces off of these abstractions. Never mind the abstraction is completely wrong from the beginning. Of course it all breaks. That's not OOP's failure. Composition and Inheritance are not mutually exclusive. They are extremely complimentary. The issue is, apparently, schools are no longer properly teaching the paradigms.
A car "is a" vehicle. A truck "is a" vehicle. A vehicle "has a" motor. A vehicle "has a" transmission. A vehicle "has a" wheels. This well highlights the lines of abstraction and each associated interface to compliment each of those abstractions. Suddenly you're using both and the abstractions well identify the interfaces.
@@justanothercomment416 or you could just use vectors and build the car out of different vectors(one for each component) and just use an index to link everything together.
@@Ghorda9 Wild assumptions of the criteria and completely misses the mark of the comment's purpose.
Does structural typing still let you explicitly implement some of the interfaces? I like purposefully implementing an interface and being forced to implement the necessary methods by the compiler
OMG. Why so complex? How do you come up with OOP concept? By thinking about inheritance, modeling? Really? NOOOO! It started with a simple problem, you have some state, and you have function or procedure to manipulate this state. Everything works fine, because you wrote it. Now another dev wants to use your function but tries to manipulate the state by themself directly instead of your functions and in non-supported manner. Everything crashes, world ends. So, this is the core problem that OOP tries to solve. That's it. That's it.
Naturally, you say let's combine state and methods so nobody except you can access the state. Ok, so what should I do if I want to extend original functionality, should I create my own state? Can I extend yours? Naturally, inheritance appears. That is, it. And then all other features appeared from people who deviate from original problem.
Original concept of OOP is great and useful, and inheritance is useful. How these concepts are implemented - it's another question. Simple inheritance ok. Classes ok. Everything else is useful only for wide distribution of large managed libraries developed by large companies. It does not mean all features are for you.
And agree. The interfaces are great.
Nicely written. People really seem to over complicate these things.
"We are using languages that force us to think in classes with architectures that don't require objects" 👏
The thing with abstraction is that everyone see the "limiting factor" as a downside.
In my job, we had this internal project that it was about casual party games to "break the ice". The dynamic was: someone with an account creates a lobby, then you join with the phone and the game starts.
The thing is i had to make a little framework for the games inside the server. So I use Inheritance to force the people in the future to follow the gameloop, so every game has the same structure.
I used the "Limiting factor" so future devs dont end up making a mess. With that little framework we could make a game in just 1 to 2 days (all tested, back and frontend, only 2 devs).
Not a game dev but we do the same for industrial automation and R&D.
We use inheritance to handle most of the nitty gritty stuff, so that we only worry about project specifics. This also means that of someone goes off rails and builds something without documentation we at least know where to start reading and we can reconstruct the spec.
How is this different to passing an implementation of a "game loop interface" to a "game runner"? (As opposed to using inheritance)
Not to say that that's an argument against inheritance (at least by itself) but one of the biggest issues with OOP is not being able to consider other options than inheritance.
@@SimonBuchanNz you use inheritance when you want to implement something in the parent class, if you only have to define a "type" just use an interface.
@@fludeo1307 I literally don't understand what you mean. That inheritance means you can call parent class methods? Sure, or you can pass in "this" to the interface, or you can not use a class and use free functions for common behavior, or whatever other features your language supports (eg Rust extension traits, Java/C# interface method defaults, ...)
@@SimonBuchanNz inheritance means you literally inherit behavior, data, and structure. if you pass "this" to the interface you still have to implement the method on the "child class", so you have the same code everywhere. Implementing the method in the parent class would save you writing the same method over and over.
I'm one of the Haskell nerds that has mostly made peace with OOP. Classes are just what OOP languages have instead of variant types and pattern matching. And they're extensible in different ways, since classes allow adding new types without changing existing code, and variants allow adding new functions without changing existing code. But classes can't add new functions to the interface without changing existing code, and variant types can't add new variants without adding it to all the existing pattern matches. It is often useful to be able to add more types, so FP languages will generally have mechanisms to do it as well.
As a rule, inheritance is often coupling a lot of complicated stuff together, which you can do in any language but this particular mechanism was heinously encouraged in the earlier days of OOP. I use interfaces and abstract base classes and rarely override methods.
And don't make things too abstract out of the gate. Your system will never support every imaginable type of extension. Just focus on the extension that you need. This is also possible any language. I have no doubt that architects of the 90s and 00s would have made us hate Haskell and FP if it were the language de jure. We don't know how to make software still and we really really really didn't back then.
Another failure was obviously that well behaved stateful objects would easily compose into a well behaved stateful program. Absolutely not the case.
😂😂😂
Everytime Prime talks bout C# i😢have that same question! How good is he at C#??
OOP is more cumbersome but from an organizational standpoint, I think it's the best strategy.
That being said, if you can't make ur C# code look like Zoran Horvat then I'm not sure what your aspirations are in the world of C#. His approach has functional and OOP blended together. Which is a true sign when you Master a programming language.
Edit: hold up, prime does not know who Mrs. Liskov is?!? Wtf??
I have seen multiple thumbnails/clips/titles from him dismissing OOP, only to find that he does not even know what liskov substitution is... Not a cool use of his influence in the programming space.
"You just have to do it correctly and it works"- Prirme, you have nuggets of wisdom sometimes
Comparing the terror that is OOP with the theoretical intent of the original creator, is like comparing Nuclear Weapons to Relativity.
so real
In c# i dont bother with inheritance, I just use dependancy injection.
Its much cleaner to me, just inject filght to sparrow and dont for the ostrich 🤷♂️
I've never done anything but OOP. It baffles me that senior developers don't know what it is, and make the same complaints about inheritance over and over again while using OOP in their own projects and not realising it.
Using objects and being objected oriented aren’t the same thing any more than C is functional because it has function pointers.
Senior engineer at Netflix, 10+ of SWE experience, worked in academia, gave a very popular course about algorithms and didn't know about Liskov Substitution.
I was taught that getters and setters should never ever be used, and then I was tasked to write a whole (simple) game engine without them. I have never used them since, and I understand the point of OOP much better than before.
This is the way.
if everything is getters and setters you have too many.
if nothing has getters and setters; you have a struct with functions.
the big thing is "if it needs to be validated it should have a setter" "if the member should never be written to externally it should have a getter"
Getters and Setters are just special function overrides for the =operator on the member.
There 2 ways you can write in OOP state and stateless . In one you get only operation results as object never manipulate the object itself and you are per default thread-safe but you have the cost of memory allocation like in FP. In the other you run often in getter/setter scenarios which is better for speed and memory optimization but also you have sometimes not all information all at once in this case it is from FP perspective like a closure. In general OOP is just making a list of your functions and variables under a name which doesn't oppose FP . Its more like the question of if you like your stuff messy or ordered but yes there is also stuff where OOP doesn't help like when you working internal data streams where everything is just logic . There FP is more helping hand when you are able to write in it.
Exposing setters for properties is something I do sometimes. It's for rare-use things in classes, or things that allow the user to customize it for special cases. Like "ThrowExceptions", making the class instance's methods throw when exceptions occur, or "AllowGuests". Anything that is rather important is already in the constructor. What I want to avoid is putting ALL of these into the constructor. Imagine you can only create an instance of a class if you have like 20 parameters, and no indication to what is default, or what constellation makes sense (yes that would be sloppy code, but it's just an argument).
Oh GOD no. "empty()"? *cough* Principle of Least Astonishment. I would expect "empty()" to empty something, and would expect "isEmpty()" to be a boolean returning function.
06:57 is u8 (also known as unsigned char, uint8, uint8_t, byte) object?
I’m a simple man. I see Prime, I smash that like button
As for properties on interfaces (assuming you mean member variables), I get your point if those variables are mutable. However, I find no better way of categorizing shared constants which for all intends and purposes are "global" for all implementations of that interface
Categorizing is one thing but encapsulating and hiding even immutable data which is effectively constant (never changing once initialized) is another. OO design doesn't just bundle data. It bundles data and functions over that data while hiding (preventing access to) its data. If you'll forgive my very long-winded ramble, this overarching philosophical desire to encapsulate and hide data often contorts and skyrockets the complexity of many codebases.
To illustrate, consider a design document for a simple game which describes requirements like these:
>> A light healing potion can heal a creature for 5 HP. However, the creature must be able to drink the potion: it must be conscious, be a biped possessing functional arms/hands, and a functioning digestive system. Therefore, creatures absent digestive systems such as androids (although they can drink if they possess a mouth but to no effect as they lack the digestive enzymes to break it down for its effect let alone the organs to heal) along with quadrupeds like dragons along with bipeds with broken arms can't drink potions. Note that trolls also cannot drink this potion as it interferes with their natural regenerative abilities. Creatures with alkaloid allergies given the nightshade ingredients in the potion will become poisoned instead of healed by it.
If we use OOP, in which object -- and at what level of abstraction/concretion -- do we declare and define this functionality? Let's say we define it in a 'use(Creature)' method for LightHealingPotion (which is a concrete type of Consumable); that's likely the most organized place to distribute most of the complexity. However, we only have access to a Creature instance, so we can't know whether they are conscious, bipeds with functional arms/hands, have a digestive system, alkaloid allergies, mouths, or are trolls, let alone modify their HP or poison them unless we downcast or expose extra methods which query/transform the underlying information: all options which weaken the encapsulation and abstractness of our Creature design.
If we step back and consider what we just did, we took a design requirement for the functionality of using a light healing potion and distributed the complexity of those requirements not only into one concrete object but spilling into an abstraction and potentially multiple concrete derivations of it. That is to say, using natural language as an analogue, we spread the complexity of the specific verbal action of using a light healing potion across multiple nouns. Some of that complexity may not need be repeated for each new rule, but we still dispersed some degree of complexity for a relatively simple rule across potentially many things.
If we approach this with a competent procedural or functional design where we don't try to bundle verbs and nouns together, all the nouns become plain old data and the entirety of the complexity of using a LightHealingPotion would be distributed into a single function designed specifically to use it which can be a near one-to-one translation of the design requirement (the code almost writes itself). To avoid requiring a boatload of code duplication to use a light healing potion separately from an antidote potion and so forth, we can use polymorphism at the verb/function/action level rather than noun level.
Consider how profoundly simpler our codebase will be, especially if we have to implement 300+ pages worth of such rules as in the case of an AD&D rulebook. The relative difference compared to even the best, SOLID-conforming, OO designs with genius-level abstractions will be enormous. Even in terms of line count, we're likely talking about at least a 20x difference between the functional or procedural version vs. the best-designed OO version.
@@darkengine5931 Yeah I completely understand. I've been out of the OOP-phase for a while now, and usually keep any encapsulations rather shallow, if not completely ignoring inheritance and class-member-methods in turn for bounded generics and static pure functions. Even in Java you can gain about 90% of the convenience of inheritance that way - while not hiding anything and keeping most of the control flow pure.
It mostly comes down to convention for me at this point - if I go 100% functional, no one where I work is going to understand wtf is going on. Even generics are still foreign to most - and I've already had my fair share of discussions about how and when to use lambdas.
@@gustavbw I have the same struggles in my team. We work in C++. I keep wanting to persuade members to favor pure functions but it is a slow progress to really sell the benefits, especially since I can't efficiently multithread my code nearly as much without locks since my colleagues are still writing objects whose methods I have to call which aren't thread-safe without syncs.
What I like is a pragmatic idea like let's just try to work more and more functions that just input something and output something new, not input something and mutate it (this includes member functions/methods which mutate an object's member variables). I'm currently at the John Carmack stage 10 years ago where he was trying to persuade his team to favor more functional programming:
>> The real enemy addressed by inlining is unexpected dependency and mutation of state, which functional programming solves more directly and completely. However, if you are going to make a lot of state changes, having them all happen inline does have advantages; you should be made constantly aware of the full horror of what you are doing. When it gets to be too much to take, figure out how to factor blocks out into pure functions (and don't let them slide back into impurity!)
>> The whole point of modularity is to hide details, while I am advocating increased awareness of details. Practical factors like increased multiple checkouts of source files and including more local data in the master precompiled header forcing more full rebuilds do need to be weighed. Currently I am leaning towards using heavyweight objects as the reasonable break point for combining code, and trying to reduce the use of medium sized helper objects, while making any very lightweight objects as purely functional as possible if they have to exist at all.
>> Most bugs are a result of the execution state not being exactly what you think it is.
What OO in the end actually is Is discipline imposed upon the use of pointers to functions.
Discipline imposed upon the indirect transfer of control and in exchange for that discipline we got a benefit and that benefit was
The easy ability to structure our modules!
So that the source code dependencies oppose the flow of control in most structured applications the flow of control points in the same direction as source code dependencies in a good object-oriented program
And this is how you know it's a good object-oriented program key dependencies are inverted to point against the flow of control and that inversion is accomplished through polymorphism
So the OO Paradigm is polymorphism which is discipline imposed upon indirect transfer of control.
(c) Uncle Bob
I see you know your clean architecture well
@@RoyRope As my team lead said 8 years ago, I get paid to think, and in order to think you need to know things well.
"when lightning strikes a tree who's behavior is that", made me spit my water out 🤣
"At the point where I instantiate the List I choose ArrayList, since it's usually the more performant implementation" 😅
whenever they use "performant" word you know its bullshit
What's the smiley for? ArrayList is the correct choice for nearly every scenario.
An array is obviously more performant, but you'll need to resize or allocate the correct size yourself if you use one. However, since an ArrayList is doing exactly that, it tends to be the most performant List implementation. Even more so if you can predict the size requirement as you'll have what is essentially an array.
@@hannessteffenhagen61 Because if you want performance, you need to measure, not assume. And even though there's reasons to use dynamic dispatch, it is typically not chosen for performance reasons, because it will be a cause for cache misses and branch mispredictions.
@@Muskar2 have you ever measured performance in your life? If yes, you'd know that ArrayList outperforms any other list implementation for typical things you do with a list. That's why it's the default you reach for unless you have a good reason to believe that something else would work better.
"have you seen this, have you heard about this?" - hi there Jimmy - "wow, what a terrific audience"
50-minutes video? oh man, this is much better than a Netflix episode !
Wait, 50 minutes? It definitely didn't feel like that.
Clojure also has type classes. Is called extension. Say you make an interface and you extend to the build in string type.
What I love about OO is how you can do inversion of control and can combine this with dependency injection. Now you have a Russian doll class that has a class that has a class that has a class all injected into it by pure magic to the point where nobody can figure out the stack of what method is called where and why.
Especially love it when team members go crazy with this so nobody can ever maintain the code AND keep their sanity at the same time, not even the creator of the code in the future. Now the whole project needs to be rewritten before a new feature can be implemented insurring everyone's job security.
Yeah I also sure as hell love some if/else or some switch/case nested deeply down the call stack reacting on some random enum or boolean flag in some stupid global state that is completely unpredictable!
I say this because this is the non OOP alternative I have to deal with constantly. And I am pretty sure it's not any better, probably worse.
@@freesoftwareextremist8119 I feel sorry for you, unfamiliar with union types.
@@freesoftwareextremist8119- weird take, you can use global variables in OOP as well.
If/else and switches are much clearer than having to jump between the call stack 20 times because someone doesn't understand that dependency injection hurts readability.
IOC is not an OOP thing. I use it frequently in embedded C to avoid explicit coupling when writing reusable code. It helps to avoid having to test everything on the target, or on a slow simulator, or using complex tooling to mock out dependencies with code generation and linker magic.
I know it's typically much more ridiculous in OOP languages that use it everywhere, but the concept is much more general than that.
I find it hilarious that this video was recomended to me as I am learning OOP from my course!
Gotta say thats a win for a algorythm this time!
C# has a too many features that require an editor/IDE that follows the dependencies since everything can be hidden by namespaces and classes can also be extended by other classes invisibly instead of composing them. I find that C# is better than Java, but the scary thing is that any primitive can be extended by extension classes, because boxing exists.
Getter and setter were actually rooted from C# concept that you can bind those attributes directly to UI components. So it's actually not for the better code, it's for better binding, which is not useful outside C#
The problem with inheritance is that it has to be 100% correct - if somewhere in the chain something is only 99% what its ancestor was, the hierarchy falls apart - and there's no way to predit on step 1 what step 17 will look like
Also, even if technically 100% correct, it's not always useful, e.g. "square is a rectangle" example, where even if it's technically true, inheritence wouldn't work well for it
If inheritance isn't good for it, then don't use it.
If you are building your code well then step 17 would be 1-3 levels down from step 1 in inheritance, and you can easily check the conditiions on step 1 in relation to your object. If you are going beyond that then you might need to refactor your code and maybe think about it differently (or stop listening to Java training). Maybe an interface would suit it better or maybe the objects should be broken up a bit more, or that function you dumped into the object should be taken out and used for the whole program. The issue here is lying with the way you have designed the code, not the fact you can inherit from another class.
Take programming a game, something done more often in OOP these days than not.
You may have entities -> dynamic Entities -> player character, alongside the PC may be NPCs. An entity may just have the var of the name of mesh used, a position in worldspace co-ords (often a struct or object itself), an overlap function IsOverlapping(Entity obj) etc. Dynamic entity may have the list of animations, bone structure, min/max movement speed, health , killDistance or whatever. The player Character may have number of lives, functions that fire events when the pc is killed, points total, weapons and inventory etc. Whilst the sibling NPC might just have a string for the mesh used as a weapon, aggro factors etc.
I remember the A-Life system Stalker came up with that had all creatures have a hunger/satiation, fear, and risks values on them as well as their food types etc, this was applied to all creatures in the game right down to human npcs. The creatures would take bigger risks depending on their hunger level. So you could sit on a hill and watch a bunch of dogs hunt down the huge pig things, or the dogs would avoid them as they still posed a risk and the dogs had not reached the point where they would risk it. This sort of system is ideal for inheritance. But if each creature has a distinct set of values and works in a completely different way, then you wouldn't.
4:35 Structs with functions should have been an option on the poll. Unfortunately the "Java way" contaminated how people think about OOP.
I swear, when my professor explained getters and setters, I knew that it‘s stupid and useless in 99% of all cases.
31:30 getters and setters do allow things like eg the classic “shapes”/“polygons” superclass
Shape can have abstract .getPerimeter and .getArea
and then square and pentagon and the mandelbrot set can all implement both of those differently from one another
getters and setters allow u to have “traits” like perimeter and area that correspond to real traits of the instances of shape but don’t correspond to any hidden properties of those instances directly..
that being said i don’t care i’m still a fp shill i hate classes
5:21 Jonathan Blow would get an aneurysm watching that part.
Web developers, what do you expect..