> If you're coding in Java, you've better think OO is a great idea. It's an object oriented language.
There's plenty of room in Java for nice, clean code. All "java is OO" means in practice is that your code needs to be in classes. Some things that work great in java:
- Separates out value types from everything else. Value types should usually be tiny, and have public fields. They should not contain references to any other data, and any methods should be simple. ("getTempInCelcius()" is ok, "updateFromDatabase" is not.)
- Express saving / loading / processing work as (ideally) pure functions which operate on that data. You can use the class keyword + static functions to make a module / namespace in java. Use it.
- Use interfaces sparingly. Try not to use inheritance at all.
- (Controversial): If you find yourself with 18 different classes instantiated at runtime, where all those classes have exactly 1 instance (and probably all hold references to each other), you're doing it wrong. Use fewer, larger "controller" style objects (ideally 1), arranged in a tree (no references should point up the tree). If your classes get big, move utility code out into pure functions or short lived utility classes in adjacent files.
- Don't use factories. If you need to, use value types for configuration data.
Is this still "OO"? Depends what you mean by OO. I've heard this style of programming referred to as "data oriented programming", or something like that. Done well, data and data processing often end up in separate files. (Which is the opposite how OO is usually done). Features can often land in separate folders. Performance usually ends up better, because you have less pointer chasing and indirection. You often get much clearer separation of concerns between classes. And its usually much easier to write unit tests for code like this - since almost all classes can be instantiated and tested directly.
A lot of modern game development uses patterns like this, coded in C++. Most C code looks like this too. I've also read and written plenty of javascript / typescript which follows this pattern. Its very simple in JS/TS because you can use object literals (with interface definitions in TS) for your value types. In rust, your large "controller" style classes with a million methods can have all those methods spread out throughout different files in your project. (Ideally grouped by feature.)
That can be OO. I think in general object inheritance is pretty unfashionable, and while it occasionally does solve problems, it's rarely the first thing to reach for. It can be useful in library code though, were you might otherwise end up with significant code duplication.
Overall the move is toward having data classes (basically records) that are light on logic, as well as logic classes that are light on data. I don't think pure functions are necessary or in many cases even desirable in Java, as it largely lacks the tools required for this not to be crippling; although I will concede that any state changes typically ought to remain local.
Factories can be pretty good for separation of concerns, although in many cases it's been superseded by dependency injection frameworks in modern java. I still reach for it every one in a while though.
Also fwiw, game development is a bit of a weird case. It usually reaches for design patterns like ECS. This is in part a data locality optimization, since you can just iterate through all components in a "straight line", but mostly it's for the sake of malloc, which generally doesn't deal well with billions of objects being allocated and deallocated over and over randomly with growing fragmentation as a result. There are many types of programs this or other game dev patterns aren't suitable for.
There's plenty of room in Java for nice, clean code. All "java is OO" means in practice is that your code needs to be in classes. Some things that work great in java:
- Separates out value types from everything else. Value types should usually be tiny, and have public fields. They should not contain references to any other data, and any methods should be simple. ("getTempInCelcius()" is ok, "updateFromDatabase" is not.)
- Express saving / loading / processing work as (ideally) pure functions which operate on that data. You can use the class keyword + static functions to make a module / namespace in java. Use it.
- Use interfaces sparingly. Try not to use inheritance at all.
- (Controversial): If you find yourself with 18 different classes instantiated at runtime, where all those classes have exactly 1 instance (and probably all hold references to each other), you're doing it wrong. Use fewer, larger "controller" style objects (ideally 1), arranged in a tree (no references should point up the tree). If your classes get big, move utility code out into pure functions or short lived utility classes in adjacent files.
- Don't use factories. If you need to, use value types for configuration data.
Is this still "OO"? Depends what you mean by OO. I've heard this style of programming referred to as "data oriented programming", or something like that. Done well, data and data processing often end up in separate files. (Which is the opposite how OO is usually done). Features can often land in separate folders. Performance usually ends up better, because you have less pointer chasing and indirection. You often get much clearer separation of concerns between classes. And its usually much easier to write unit tests for code like this - since almost all classes can be instantiated and tested directly.
A lot of modern game development uses patterns like this, coded in C++. Most C code looks like this too. I've also read and written plenty of javascript / typescript which follows this pattern. Its very simple in JS/TS because you can use object literals (with interface definitions in TS) for your value types. In rust, your large "controller" style classes with a million methods can have all those methods spread out throughout different files in your project. (Ideally grouped by feature.)