Object-oriented programming is an extremely bad idea that could only occur in California.
- Edsger Vibe Dijkstra
target - & gt; data architecture - & gt; code. There is no way to change the order here! When designing a program, you should always begin with finding out the goal to be achieved, and then at least approximately present the data architecture: the structure and infrastructure of the data necessary to achieve it effectively. And only after that you need to write code to work with such an architecture. If the goal changes over time, you must first change the data architecture, and then the code.
Customer? It is sent to
class Customer. Do I have a rendering context? It is sent
class Customerhas a link to
class Order, and vice versa. The
class OrderManagercontains links to all
Order, and therefore indirectly to
Customer. Everything tends to refer to everything else, because gradually more and more places appear in the code that refer to the related object.
Contextobjects just to cut the number of arguments passed to and fro, you realize that you are writing a real OOP code of the Enterprise level.
class Playerstrikes using the
class Monstermethod, where do you really need to change the data? The
hpvalue of the
Monsterobject should decrease by
xpvalue of the
Player object should increase by
Monsterin the case of
Monster. Should this happen in
Player.hits (Monster m)or in
Monster.isHitBy (Player p)? What if you need to take into account
class Weapon? We pass the argument to
This simplified example with just three interacting classes is already becoming a typical OOP nightmare. Simple data conversion turns into a bunch of clumsy intertwined methods that call each other, and the reason for this is only in the OOP dogma - encapsulation. If we add a bit of inheritance to this mix, we will get a good example of what stereotypical Enterprise-level software looks like.
Schizophrenic encapsulation of objects
Let's take a look at the definition of encapsulation :
Encapsulation is an OOP concept that binds data and functions to manipulate this data, protecting them from outside interference and misuse. Data encapsulation has led to the concept of data hiding important to OOP.
Intention is good, but in practice, encapsulation with the fragmentation of an object or class often leads to the code trying to separate everything from everything else (from itself). This creates a huge amount of boilerplate: getters, setters, numerous designers, strange methods, and they all try to protect us from mistakes that are too unlikely to occur on such a modest scale. You can use this metaphor: I put a padlock on my left pocket so that my right hand could not take anything from it.
Don't misunderstand me - imposing restrictions, especially in the case of ADT , is usually a good idea. But in OOP with all these cross-references of objects, encapsulation often does not achieve anything useful, and it is rather difficult to take into account the restrictions scattered across many classes.
In my opinion, classes and objects are too fractional, both in terms of isolation, API, etc. it is better to work within the "modules"/"components"/"libraries".And in my experience, it is in OOP codebases (Java/Scala) that modules/libraries are not used. Developers are focused on building fences around each class, not really thinking about which groups of classes together form a separate, reusable, holistic logical unit.
You can look at the same data differently
OOP requires ordering data in an inflexible way: to divide it into a set of logical objects, which determines the data architecture - a graph of objects with related behavior (methods). However, it is often useful to have different possibilities for the logical expression of data manipulation.
If the program data, for example, is stored in a tabular, data-oriented form, you can create two or more modules, each of which works with the same data structure, but in a different way. If the data is broken into objects with methods, then it is no longer possible.
This is also the main cause of the object-relational gap . Although the relational data structure is not always the best, it is usually flexible enough to work with it in various ways using different paradigms. However, the stiffness of data organization in OOP causes incompatibility with any other data architecture.
The combination of scatter of data across many small objects, the active use of indirection and pointers, the lack of a proper data architecture lead to low execution speed. This justification is more than enough.
What approach should be used instead of OOP?
I don’t think there is a “silver bullet”, so I’ll just describe how it usually works in my code today.
First I study the data. I analyze what goes to the input and output, data format, their volume. Understand how data should be stored at run time and how it is stored: which operations should be supported and at what speed (processing speed, latency), etc.
Usually, if the data has a significant amount, my structure is close to the database. That is, I will have an object, for example
DataStorewith an API that provides access to all the necessary operations for querying and saving data. The data itself will be contained in the form of ADT/PoD structures, and any links between data records will be presented in the form of ID (number, uuid or deterministic hash). Internally, it usually strongly resembles or actually has support for a relational database:
HashMapstore most of the data for Index or ID, other structures are used as “indexes Required to perform a quick search, and so on. Other data structures are also located here, such as LRU caches and the like.
The main part of the program logic receives a link to such
DataStoreand performs the necessary operations with them. For the sake of concurrency and multithreading, I usually connect different logical components through message passing like actors. Example actor: stdin reader, input data handler, trust manager, game state, etc. Such "actors" can be implemented as pools of subprocesses, elements of conveyors, etc. If necessary, they can have their own or common with other
Such an architecture gives me convenient testing points:
DataStorecan have different implementations using polymorphism, and the communicating instances of the actors can be created separately and managed through test message sequences.
The basic idea is this: just because my software works in an area where there are concepts, for example, customers and orders, it will not necessarily have a
Customerclass and related methods. Quite the contrary: the concept of
Customeris just a set of data in tabular form in one or several
DataStore, and the “business logic” code directly manipulates this data.
Like so much in software design, criticism of OOP is not an easy topic. Perhaps I did not manage to clearly convey my point of view and/or convince you. But if you're interested, here are some more links:
- Two videos of Brian Will, in which he makes excellent arguments against using OOP: Object-Oriented Programming is Bad and Object-Oriented Programming is Garbage: 3800 SLOC example
- Report by Stoyan Nikolov with CppCon 2018: "OOP Is Dead, Long Live Data-oriented Design" , in which the author conducts an excellent analysis of the example code base of the OOP and points out its problems.
- Arguments against OOP on wiki.c2.com - a list of standard arguments against OOP.
- Article by Lawrence Krubner " Object-oriented programming is an expensive disaster that needs to be stopped ” - a long post, deeply considering many ideas.
- Quora: OOP in C ++ is slower than C? If so, is the difference significant?