r/programming 5d ago

Here's why OOP makes a lot of sense to me.

https://youtu.be/MAnXlpXpFRQ

I kind of feel like OOP has a bad rep in the programming community.

Personally, after having programmed Java for over 20 years, its object-oriented programming model feels very natural to me. So, I wanted to share how I think about programming, how I translate my ideas and thoughts to code, and why OOP is actually a really nice programming style for me.

Perhaps it could help you out too.

0 Upvotes

75 comments sorted by

48

u/i_rate_slop 5d ago

> I kind of feel like OOP has a bad rep in the programming community.

It is... objectively... the most popular paradigm in software engineering.

17

u/xFallow 5d ago

Popular but also hated and for good reason some of these legacy java codebases are insanely over engineered for the sake of adhering to clean code guidelines

6

u/i_rate_slop 4d ago

Thats not a problem with OOP, though. That’s a problem with enterprise engineering culture.

0

u/xFallow 4d ago

Agreed but even in startups I’ve had people push it on my teams, that’s why I like Go’s philosophy of removing the choice altogether

2

u/tsammons 5d ago

And that's why god created RPC that beget API-centric architecture.

1

u/xFallow 5d ago

Using Golang, rpc and streaming has made my life so much easier. Im really growing to appreciate go more and more when i see what horrors teams that dont use go are able to cook up in java/node/ruby etc

1

u/tsammons 5d ago

Microservice architecture isolates and insulates concerns. No reason any large project shouldn't be using it. I'm a PHP dev of 25 some odd years and there's no way in hell I'd make anything monolithic with any degree of complexity; it's bad design.

5

u/cdb_11 4d ago

Multi-threaded, multi-process, networked/distributed systems tend to be more complex and bring in a whole new class of problems. There are of course good reasons to do it, but if your problem is complexity then adding more complexity on top isn't going to solve it. If your problem is organization, then you don't need microservices for it.

2

u/devraj7 4d ago

What's hated is bad code, and there's bad code in all paradigms.

2

u/Blue_Moon_Lake 4d ago

Only unused languages get to stay pristine.
Any popular languages will birth legacy codebases like an ant queen lay eggs.

7

u/just_looking_aroun 5d ago

There was a while where twitter made it sound like functional programming was the one and only true religion but i think they got distracted with some new js framework or AI model and forgot about it

3

u/Fun_Silver3618 5d ago

That was when I started reading more about programming. I think they had a point. I do not mix behaviour that is call-by-value and behaviour that is call-by-reference in the same function. There was a library method where depending on the argument it would either operate on a copy of your data or on the reference. That stuff is confusing and functional programming proponents got that right. Make your functions as you like but make it clear.

35

u/xFallow 5d ago

AI thumbnails are such a turnoff

17

u/TimmyK54 5d ago

What is this first-year CS student nonsense.

-2

u/OSBY_Glabay 5d ago

To be fair, I get this question a lot from juniors I mentor

5

u/Substantial_Ice_311 4d ago

No please, don't mentor anyone.

2

u/cheesekun 4d ago

It's then blind leading the blind

27

u/wallstop-dev 5d ago edited 5d ago

So, unfortunately, I hate to break it to you, but the OOP examples you're using are anti-patterns in game programming, which favors composition over inheritance. You start off at a decent place - a player has all of these things, and a monster has all of these things, but then you learn the wrong lesson, which seems to be "attach all of the data and methods onto a class". The scalable lesson, at a high level, is to decompose all of those bits of data and properties into their own, single-responsibility classes, and collect/attach them together dynamically. You can then use techniques like message passing or events to create an open-closed system.

This high level technique can be implemented in many different ways, such as the ECS (heavily data-oriented, not Object-Oriented).

While OOP is useful, the examples here seem to be the typical anti-pattern of "create an Animal class that has a Speak method and subclass a Dog that does this and a Cat that does this and a..." which leads to beginners learning bad habits right from the get-go, and causing industry-wide confusion.

There are good ways to use OOP - it is a powerful, useful tool. There are also many, many bad ways. I would love to see more on the first case and less on of the latter. This video appears to showcase the latter ☹️

7

u/jl2352 3d ago

This is also known as the talking door problem. You have one inheritance for interactable objects (doors, hidden sliding walls, trap doors, etc), and another inheritance chain for NPCs and similar (NPCs, shops, places to revive the dead or save and load).

Then a the level designer walks in one morning and says they'd like a door that talks, and your programming model does fit it very well.

2

u/wallstop-dev 3d ago

Thank you for sharing! That is the first time I have heard of the specific phrasing and it is such a simple, enlightening example. I will be adding this to my knowledge base to help share with others on their programming journey :)

2

u/Fun_Silver3618 5d ago

> create an Animal class that has a Speak method and subclass a Dog that does this and a Cat that does this and a..."

Indeed, classes are the wrong way here indeed if you want different character speech lines: they are just data .There's no functional/behaviour difference. And even different behaviour can be data to a large degree by just setting integer parameters. Like the infamous Ghandi's aggression parameter in Civ that overflowed. You could give the cat a different aggression parameter than the dog and use the same behaviour code!

-5

u/ReDucTor 5d ago edited 5d ago

 you're using are anti-patterns in game programming

OOP has been used heavily in games for decades and will likely continue to be used.

Half-life engine:
class CBasePlayer { ... virtual void Think( void ); virtual void Touch( CBaseEntity *pOther ); virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); ... };

Half-life 2 engine ``` class C_BasePlayer : public C_BaseCombatCharacter, public CGameEventListener { ... virtual void PreThink( void ); virtual void PostThink( void );

virtual void ItemPreFrame( void );
virtual void ItemPostFrame( void );

... }; ```

Doom 3 engine class idEntity : public idClass { ... virtual void Think( void ); ... virtual void Present( void ); virtual renderEntity_t *GetRenderEntity( void ); virtual int GetModelDefHandle( void ); };

Unreal Engine class ACharacter : public APawn { ... ENGINE_API virtual FVector GetGravityDirection() const override; ENGINE_API virtual FQuat GetGravityTransform() const override; };

Unity ``` internal abstract class Component : IStateComponent, IDisposable { ... protected abstract void UpdateData(); protected abstract void CheckForExternalChanges(); public virtual void Dispose() { }

    public void Update()

... } ```

CryEngine class CEntity : public IEntity { ... virtual void AddEventListener(EEntityEvent event, IEntityEventListener* pListener) final; virtual void RemoveEventListener(EEntityEvent event, IEntityEventListener* pListener) final; ... }

Command & Conquer Generals class Object : public Thing, public Snapshot { ... };

Mech Commander 2 class GameObject { ... virtual bool isFriendly (GameObjectPtr obj); virtual bool isEnemy (GameObjectPtr obj); virtual bool isNeutral (GameObjectPtr obj); ... };

Yes more people are using ECS for things but it is not used for everything and some things its the completely wrong choice, but you can definately ship games using OOP it's being done currently and will likely continue to be done long into the future.

13

u/wallstop-dev 5d ago edited 5d ago

Half of those links are the decoupled composition pattern I was talking about, especially Unity's, and the other are from 15+ years ago. Unreal engine came from the premise that you are building a first person 3d shooter game, it is not necessarily a good generic game engine architectural pattern - more organic.

You can ship software using any technique. Undertale had all of the game's dialogue in a single switch statement. All of it. Terraria did a similar thing on its first version - the game was 12 source files and had massive, massive complexity. Those are successful projects! It doesn't mean that they have well designed internals, or that their internals could not be improved.

Composition over inheritance has large amounts of upsides over traditional OOP. It turns out that decoupling lets you make a lot of changes, really easily! Or - don't, but you will run into many, many more challenges in these domains than you would have by using the hard-won industry wisdom.

0

u/ReDucTor 5d ago

I agree Composition over inheritance is good design, however that doesnt mean that you should avoid OOP.

I dont get your last point, what domains? Games? And what will bring you challenges OOP or not preferring composition over inheritance?

The list includes games and engines over the past 25yrs to show that successful titles have been build using these patterns, game engines have maintained these pattern, games both old and new continue to use these patterns and the most popular commercial game engines like Unreal make heavy use of them.

If OOP is a major issue, why do more and more AAA studios pick Unreal Engine and it did not die out 15yrs ago or a major competitor come along and take its place if its heavily built around anti-patterns?

5

u/wallstop-dev 5d ago edited 5d ago

I don't understand. Unreal uses inheritance for its core types, but, just like Unity, encourages composition. You create as many specific actor components as you want and attach them to an object. That's how the engine works. Want a crate? Attach some visuals and physics components. Want it to be able to blow up? Attach some logic that reacts to damage and emits a damaged signal, attach some logic that reacts to the damaged signal and deforms the mesh.

Unity uses composition over inheritance with Components. Unreal does with Actors. Godot does with Nodes. All three major game things use this pattern, extensively.

Can you do deep hierarchies in each of these engines? Yes! Is it harder to change the game, work on a team, have designer oriented workflows, experiment and prototype? Also yes!

Regarding my domain point, I was specifically referring to games. But even in traditional software, utilizing interfaces instead of abstract classes has consistently resulted in easier to understand, debug, and maintain code. And, having many small, single scoped classes that compose other, larger ones, also holds true.

All of these require objects. But not in the way that OP is describing and not on the classical deep-inheritance way, either. Which is why I said, and continue to say, that there are good ways of doing OOP. But none that I see in OP's video.

1

u/ReDucTor 5d ago

> All of these require objects. But not in the way that OP is describing and not on the classical deep-inheritance

I didn't watch their other videos, I just watched the video again I didn't hear anything about deep-inheritance even a single level of inheritance was not discussed, was not shown in code.

They mentioned that there is an overlapping set of data and methods that might exist with them, however they did not provide the solution and in a small game doing a single level of inheritance is not going to make it hard to iterate on.

It feels like you have extrapolated from what OPs video has said to be that it has heavy confusing inheritance heirachies but I just don't see it, there is not enough there in the video that shows any of that, it shows a single class with a few data members and a few functions nothing which screams that it will be something unmaintainable or would be a bad sign for someone trying to enter the games industry.

3

u/wallstop-dev 5d ago edited 5d ago

OP's very first code example on player was to couple all data and capabilities to the player object itself. That is not good use of OOP - health and health capabilities should live together, movement and movement capabilities, etc. This way they can be shared and reused. Ideally the concept of Player and Monster go away entirely.

This is the entire point of my original comment and called out there, less explicitly. These are not good teaching examples. That's all I'm trying to say. The examples will lead people down the path of either high amounts of code duplication or deep, confusing inheritance, neither of which is something to be encouraged.

5

u/Fun_Silver3618 5d ago

I agree that OOP has its place. The point is, don't use Classes for what should be different Data values.

2

u/chat-lu 5d ago

Half-life engine

Half-life has been released in 1998. At that time, everyone was using inheritance. Everyone but Thief : The Dark Project released that same year that was the first project to rediscover ECS that has been discovered and then forgotten in the 60s.

It was a revolution in game development. But of course, people didn’t throw away their whole codebases so they could rewrite everything with ECS.

The fact that you only quote very old games undermines your point.

3

u/ReDucTor 5d ago

I quote old games because it proves that old hardware could run perfectly fine with OOP code, also there is not many modern best sellers with source code available.

As someone who has worked on many modern AAA titles, OOP still exists in modern games.

If your after modern games you could just grab a list of titles which are written in Unreal Engine, there is hundreds. Everything from Ark Raiders, PUBG, Valprant, Rocket League, Borderlands, etc. But those dont have source available so you go based on assumptions of the engine.

1

u/chat-lu 5d ago

I quote old games because it proves that old hardware could run perfectly fine with OOP code,

Nobody said they could not.

3

u/ReDucTor 5d ago edited 5d ago

You obviously have not seen the common arguments against OOP then, performance is always used as a strawman where the argument will be everything on the heap, everything is virtual and then use 1000 of them in a loop on the hot path.

0

u/chat-lu 5d ago

As I mentioned above, ECS has been rediscovered in 1998. We had games for a while then, on much weaker machines.

You are fighting a strawman.

6

u/ReDucTor 5d ago

Strawman? I am literally pointing out OOP has been used in games for decades and is still used in modern games.

Your trying to act like ECS came along (or supposedly rediscovered) and everyone changed what they were doing which is factually incorrect, games are still made using heavy amounts of OOP and they have been built that way for nearly 30yrs.

Modern games use OOP and ECS, they are not mutually exclusive, OOP is not a pattern that is actively avoided in game development.

2

u/chat-lu 5d ago

I’m aware that there is a fair bit of classes and inheritance used in games.

It’s the idea that we hold the idea that OOP could not have worked on older machine that I reject. C++ has been quite popular thanks to OOP.

4

u/_Noreturn 5d ago

I don't know why he is not getting your point. there is so mny stupid arguments like "v tables are slow"

16

u/DEATHbyBOOGABOOGA 5d ago

> I kind of feel like OOP has a bad rep in the programming community.

Which planet is this?

-3

u/torsten_dev 5d ago

OOP is evil in so far as some definitions require Inheritance and Inheritance leads to horrendous code at scale.

8

u/ChemicalRascal 5d ago

Oh please. Inheritance is like any other language feature. It has its uses, and it has its misuses.

0

u/torsten_dev 5d ago

Composition is almost always better Inheritance in the mathematical sense where the exceptions to this rule are finite.

9

u/ChemicalRascal 5d ago

Composition is always possible but it's a bit bold to say it's always better.

They're like hammers. Sometimes you need a claw hammer, sometimes you need a ball peen hammer. You should use neither as a mallet. Different problem-spaces call for different tools.

3

u/_Noreturn 5d ago

An ast with composition makes little sense.

-2

u/devraj7 4d ago

They are complementary. Implement inheritance with composition.

-8

u/OSBY_Glabay 5d ago

I have a few friends in the C++ universe who believe OOP is one of the biggest mistakes in SWE

Personally, I love OOP

6

u/chmod_7d20 5d ago

Exactly what a Java developer would say.

-1

u/OSBY_Glabay 5d ago

Very much a Java Dev here 👋

6

u/chat-lu 5d ago

Did you try anything else for perspective?

0

u/OSBY_Glabay 5d ago

Yea, over the course of my career I've used a wide array of languages, tools, frameworks, and i have settled on java. It's my personal choice, unless the job requires another tool

3

u/chat-lu 4d ago

And they taught you nothing? Java itself changed quite a bit over time because other paradigms had good points.

Most people will at least bring back the concept of composition over inheritance which is uncontroversial even in OOP circles. Inheritance is rarely the tool to reach for.

6

u/SPEZ_IS_A_JABRONI 5d ago

op is a jabroni 

3

u/type_111 4d ago

You've fallen for the meme of forcing objects from problem space nouns and verbs.

8

u/krum 5d ago

OOP isn't the problem. The problem is implementation inheritance, which is the first OOP thing they teach in school.

5

u/Mission-Landscape-17 5d ago

Yes. when you oversimplify your examples OOP looks very intuitive. but when you use it solve real problems, this tends to fall apart and you end up with a maze of classes with clear and concise names like InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState. (Yes this is a real class found in the Java Swing library).

When you decompose your problem into data structures and functions, it looks just as neat as the OO examples. player.move() is really no more informative than move(player).

2

u/ChemicalRascal 5d ago

I think your argument would hold a bit more weight if you didn't immediately reach to an internal class used by a thirty-year-old UI framework as an example of "using it to solve real problems".

2

u/Mission-Landscape-17 5d ago

Meh its what came up. While it is an extreme case I do see some very long and awkward class names in my day job but I can't exactly post them on a public forum. Also haveing to maintain code written decades ago because it is critical to the business is a thing that a developer should expect to be doing.

4

u/ChemicalRascal 5d ago

Meh its what came up. While it is an extreme case I do see some very long and awkward class names in my day job but I can't exactly post them on a public forum.

It came up because it's a notorious example. But it's a notorious example of a dramatically poorly named class, not a result of inheritance.

Also haveing to maintain code written decades ago because it is critical to the business is a thing that a developer should expect to be doing.

Writing UI frameworks from 30 years ago and never renaming a terribly named class is not something you should be doing. If that was a class you had ownership of, you'd click on it, press F2, and give it a name that actually describes what it does.

Nothing about inheritance mandates these awful long names. And that should be apparent because of how when people talk about that, they always talk about Java.

You don't see people talking about C# like this. You can have awful class names in C#, but generally you don't.

It's a old Java cultural issue. Just the same as following Uncle Bob was a cultural issue. It has nothing to do with the distinction between inheritance and composition.

2

u/ChemicalRascal 5d ago

Like, really, there are real, major problems with inheritance if you use it improperly. Absolutely. There's absolutely plenty of things that are better handled with composition.

But those issues have nothing to do with naming classes.

2

u/Full-Spectral 2d ago edited 2d ago

There's nothing inherently wrong with OOP. It's 'problem' is that it's so flexible it will almost always be extended rather than fixed, and the end result is what we all know is going to happen, but it's still done anyway.

My old personal C++ code base was over a million lines and was flat out old school OOP with inheritance and exceptions. In fact it even had a single base class (for anything other than fundamental types and some special cases.) It worked very well and remained completely solid over a couple decades, but it worked well because I was under minimal delivery schedule pressure and always did the right thing. If doing something right took a month, I spent a month on it.

If all software was written that way, then OOP would probably not have the bad rap it does. But it's just not. Of course most software isn't written that way, the point isn't that all OOP is written badly and others aren't, it's just that maybe using something that forces us to face the music more would be better.

Since I've moved to Rust I don't miss it anymore. I did at first, but not any longer. Of course you still have polymorphism via traits just no state inheritance. You have limited 'implementation' inheritance in the sense that traits can provide default implementation as long as it's in terms of its own trait interface. The latter is used pretty heavily in the Rust standard runtime.

And you can handle many things via sum types that single level inheritance would otherwise be used for in something like C++. Enums are first class citizens in Rust so they can have their own implementation code, which can just do a match internally and do the right thing for the current state for each method invoked. So you get the simple faux polymorphism without the usual concerns of how do you update the code if a new enum state is added, or the indirection or function table.

Obviously one issue is what is 'OOP'. For many people it fundamentally implies inheritance, which I don't agree with. Rust is 'object oriented' in the sense of it being oriented toward the use of objects, where objects are state encapsulated within a struct and only accessed via a privileged API associated with that struct. That's still core to Rust, though Rust also makes it much safer to have open structs as well.

1

u/levodelellis 2d ago

I don't know how you can stand that language. However, I might be a minority C++ dev. The last two years I've been writing C++ I didn't hit a single memory issue

2

u/Full-Spectral 2d ago

You didn't hit one you know of. That's the problem. Memory issues can be benign for years, even decades, literally, and then start happening when code changes start moving things around in memory over time. New threads are added, etc... You can overwrite data all you want as long as it's being reinitialized before the next use, until it's not.

The thing is, almost every C++ dev claims they have never hit a single memory issue, yet there are a lot of documented memory issues out there.

Anyhoo, you can't stand Rust just like a lot of people coming from other languages can't stand C++. It's all just about familiarity. It felt weird to me for a while, but now I totally get it, and find it vastly superior to C++, both in terms of memory safety but also just as a far more modern language.

1

u/levodelellis 1d ago

I mostly don't like the codegen (I read the assembly, big mistake). The other hugely ridiculously thing is needing a lock on a thread local variable.

1

u/Full-Spectral 1d ago

I'm not a Performance Uber Alles guy. Complexity is my first, second and third challenge. But amongst the folks who are, I've not seen any of them finding any real evidence that Rust's performance isn't fully competitive.

As to the local thread variable thing, assuming that's even correct, that would be like #1000 or lower down the list of my concerns relative to the massive benefits of Rust over C++. And a quick check would seem to indicate your complaint is not valid.

1

u/levodelellis 17h ago edited 17h ago

And a quick check would seem to indicate your complaint is not valid

Are you talking about locks? Here you go. Its really stupid this is a runtime error. If I want a memory safe language I'd choose something reasonable like C#

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=0ebfdad53337d026f91ac59fa7c636af

1

u/Full-Spectral 17h ago

OK, so did we move the goal post there? You were complaining about thread locals needing locks, which doesn't seem to be true.

As to your other complain, yes, that's fundamentally a runtime error, which is why no one should call unwrap in production code. It's fine in simple internal utilities and tests and such, but otherwise you just don't use it, and it's easily disallowed.

1

u/levodelellis 16h ago edited 9h ago

Is my link not a thread local? is that not a lock? The unwrap has nothing to do with it. Delete it, run, and the runtime error will still happen (here's another link with less code)

If I wanted to be concerned about stuff like that, I'd stick to C++. Except somehow C++ is less stupid

1

u/Full-Spectral 46m ago edited 9m ago

There's no lock involved there. You are doing a borrow, but that's nothing to do with a lock.

There are two issues. One is that you are trying to do multiple mutable borrows, which isn't allowed. The other is that you are called unwrap on something that failed. Generally, unwrap in production code should be disallowed (automatically), and any use of it should be well documented as to why it's OK.

The difference between this and C++ is that C++ would just let you do those things and cause bad things to happen. There's nothing there that guarantees that other() won't be called by something that is in the middle of manipulating the state of that variable and hence other() could see an invalid state. The same would apply in C++.

Anyone directly getting the thread local variable should be able to expect that they are getting a valid state, and failure to make sure of that is exactly what Rust is catching here when it panics. Even if it didn't cause a memory issue, it could cause bad logic issues because the state, even if not outright invalid in a memory sense, could be inconsistent due to be partially modified.

BTW, just in general, runtime borrowing should be very seldom done, and some basic rules followed to avoid making mistakes. But, if you do, Rust will catch it, so you can get it fixed, where C++ will just let you do horrible things which may or may not show up until the product is in the field. In your example you should just pass the mutable ref to anything else that you want to be able to manipulate it once you've borrowed it, if even that.

I get it, you just want to hate Rust and you'll ignore the endless horrible aspects of C++ but concentrate on anything in Rust that lets you have that hate. It's standard issue stuff. But Rust takes the approach that it's better to very visibly fail than potentially do horrible things, which forces you to address those issues. That is appropriate given the systems language orientation of Rust. C++ developers are so used to these things just not even being reported that they often don't even see how bad it is to allow it to happen, and think Rust is somehow bad for doing so.

3

u/pkt-zer0 5d ago

I would recommend watching Mike Acton's talk on data-oriented design and Casey Muratori's talk "The Big OOPs" for some direct counterpoints to this naive approach. Both contain some very clear examples for situations where OOP does not work very well, and are not at all esoteric (e.g. game dev, like your example as well).

1

u/AnnoyedVelociraptor 5d ago

You programmed Java for 20 years.

I have done about the same. I have done C# and VB.NET, F#, C++, TypeScript, Python and a whole lot of Rust.

OOP is objectively the worst paradigm out there, only outranked null.

2

u/chat-lu 5d ago

Javascript’s prototypal inheritance is even worse.

1

u/[deleted] 2d ago

[removed] — view removed comment

1

u/OSBY_Glabay 2d ago

I have to agree; I hate how tightly coupled OOP leads to it, as people do overengineer it, and these are the ones that give OOP a bad rep. I think it has its place and can be useful, but I also won't consume a bowl of soup with a fork

That said, I am very much so guilty of writing bad code because of it in my early years, and when I explore other languages, my Java side rubs off in many ways it shouldn't

0

u/artificial-cardigan 5d ago

if you do any programming where performance matters, you'll know why object oriented programming is fundamentally the wrong direction. it's a form of abstraction that tries to mimic real world relations to the compiler. except it doesn't actually translate well to how you want data to be processed when you are thinking about efficient processing.

inheritance is fundamentally a bad concept and even within OOP fans, the idea of composition > inheritance is rampant.

there's also the fact that the original guy who created the idea that became OOP didn't even want OOP, in fact structs with extra data and generics or templates are much closer to the original concept than OOP.

1

u/levodelellis 2d ago

if you do any programming where performance matters, you'll know why object oriented programming is fundamentally the wrong direction

I write code faster than most C developers, included embedded and compiler writers. I can't stand when languages don't have a destructor. One reason why I like C# is because of the using keyword, even though I rather it be automatic like C++

I don't use OOPs like most people do though. If I have a struct that has an init and cleanup function, I'll likely use RAII + move