📜 ⬆️ ⬇️

I don't know OOP

I do not know how to program in object-oriented languages. Not learned. After 5 years of industrial programming in Java, I still do not know how to create a good object-oriented system. I just don't get it.

I tried to learn, honestly. I studied patterns, read open source code for projects, tried to build coherent concepts in my head, but did not understand the principles of creating high-quality object-oriented programs. Maybe someone else understood them, but not me.

And here are a few things that cause me to misunderstand.

I don't know what OOP is


Seriously. I find it difficult to formulate the main ideas of the PLO. In functional programming, one of the main ideas is the lack of state. In structural - decomposition. In the modular - the division of functionality into complete blocks. In any of these paradigms, the dominant principles apply to 95% of the code, and the language is designed to encourage their use. For OOP, I do not know such rules.
')
It is believed that object-oriented programming is based on 4 basic principles (when I was small, there were only 3, but then the trees were large). These principles:


It looks like a set of rules, is not it? So here it is, the very rules that must be followed in 95% of cases? Hmm, let's take a closer look.

Abstraction

Abstraction is the most powerful programming tool. That is what allows us to build large systems and maintain control over them. It is unlikely that we would ever come at least close to the current level of programs if we were not armed with such an instrument. However, how does abstraction relate to OOP?

First, abstraction is not an attribute exclusively of OOP, and indeed programming. The process of creating levels of abstraction applies to almost all areas of human knowledge. So, we can make judgments about materials, without going into details of their molecular structure. Or talk about objects, without mentioning the materials from which they are made. Or talk about complex mechanisms, such as a computer, an airplane turbine, or a human body, without recalling some details of these entities.

Secondly, abstractions in programming have always been, starting with the records of Ada Lovelace, which is considered to be the first programmer in history. Since then, people have continuously created abstractions in their programs, often with only the simplest means for this. So, Abelson and Sassman in their well-known book describe how to create a system for solving equations with the support of complex numbers and even polynomials, having only procedures and linked lists in service. So what additional abstraction tools does the OOP carry? I have no idea. Is the code highlighted in subroutines? This can any high-level language. Merge routines in one place? For this, enough modules. Typification? She was long before the PLO. An example with a system for solving equations well shows that the construction of levels of abstraction depends not so much on the means of the language, but on the abilities of the programmer.

Encapsulation

The trump card of encapsulation in hiding implementation. The client code sees only the interface, and can only rely on it. This unleashes the hands of developers who may decide to change the implementation. And this is really cool. But the question is again, where does OOP come from? All of the above paradigms imply hiding implementation. Programming in C, you select the interface in header files, Oberon allows you to make the fields and methods local to the module; finally, in many languages, the abstraction is built simply by means of subroutines that also encapsulate the implementation. Moreover, object-oriented languages ​​themselves often break the encapsulation rule , providing access to data through special methods — getters and setters in Java, properties in C #, and so on. (In the comments, it was found out that some objects in programming languages ​​are not objects from the OOP point of view: data transfer objects are solely responsible for transferring data, and therefore are not fully-featured OOP entities, and, therefore, they do not need to preserve encapsulation. On the other hand , access methods are better preserved to maintain the flexibility of the architecture. That's how difficult it is.) Moreover, some object-oriented languages, such as Python, don't try to hide anything at all, but rely solely on reasonableness azrabotchikov using this code.

Inheritance

Inheritance is one of the few new things that really came to the scene through OOP. No, object-oriented languages ​​did not create a new idea - inheritance can be fully realized in any other paradigm - however, for the first time, the PLO brought this concept to the level of the language itself. The advantages of inheritance are also obvious: when a class almost suits you, you can create a descendant and override some part of its functionality. In languages ​​that support multiple inheritance, such as C ++ or Scala (the latter at the expense of traits), there is another use case — mixins, small classes that allow functionality to be “added” to the new class without copying code.

So, here it is - what distinguishes the PLO as a paradigm among others? Hmm ... if so, why do we so rarely use it in real code? Remember, I was talking about 95% of the code, obeying the rules of the dominant paradigm? I'm not joking. In functional programming, at least 95% of the code uses immutable data and functions without side effects. In modular, almost all of the code is logically packaged in modules. The followers of structured programming, following Dijkstra's precepts, try to break all parts of the program into small parts. Inheritance is used much less frequently. Maybe in 10% of the code, maybe in 50%, in some cases (for example, when inheriting from the framework classes) - in 70%, but not more. Because in most situations it is simply not necessary .

Moreover, inheritance is dangerous for good design. It is so dangerous that Banda of Four (seemingly PLO preachers) in their book recommend replacing it with delegation if possible. Inheritance in the form in which it exists in currently popular languages ​​leads to fragile design. Inheriting from one ancestor, a class can no longer be inherited from others. Ancestor change also becomes dangerous. There are, of course, private / protected modifiers, but they also require some weak psychic abilities to guess how the class can change and how the client code can use it. Inheritance is so dangerous and inconvenient that large frameworks (such as Spring and EJB in Java) discard them, switching to other non-object-oriented tools (for example, metaprogramming). The consequences are so unpredictable that some libraries (such as Guava) prescribe modifiers that forbid inheritance to their classes, and in the new Go language it was decided to abandon the inheritance hierarchy altogether.

Polymorphism

Perhaps polymorphism is the best thing about object-oriented programming. Due to polymorphism, an object of type Person appears as “Shandorkin Adam Impolitovich” at the output, and an object of type Point as “[84.23 12.61]”. It is he who allows you to write "Mat1 * Mat2" and get the product of matrices, similar to the product of ordinary numbers. Without it, it would not have been possible to read the data from the input stream, without worrying about whether they come from the network, a file or a string in memory. Wherever there are interfaces, polymorphism is implied.

I really like polymorphism. Therefore, I will not even talk about his problems in mainstream languages. I also keep silent about the narrowness of the approach of dispatching only by type, and how this could be done . In most cases, it works as it should, and this is not bad. The question is: is polymorphism the very principle that distinguishes OOP from other paradigms? If you asked me (and since you are reading this text, then we can assume that you asked), I would answer “no”. And the reason is still in the same percentage of use in the code. It is possible that interfaces and polymorphic methods are a bit more common to inheritance. But compare the number of lines of code occupied by them with the number of lines written in the usual procedural style - the latter are always more. Looking at languages ​​that encourage such a programming style, I cannot call them polymorphic. Languages ​​with polymorphism support - yes, this is normal. But not polymorphic languages.

(However, this is my opinion. You can always disagree.)

So, abstraction, encapsulation, inheritance and polymorphism - all this is in the PLO, but none of this is its integral attribute. Then what is OOP? There is an opinion that the essence of object-oriented programming lies in, in fact, objects (sounds quite logical) and classes. It is the idea of ​​combining code and data, as well as the idea that the objects in the program reflect the essences of the real world. We will return to this opinion, but first we will dot some points over i.

Whose OOP is cooler?


From the previous part it is clear that programming languages ​​can vary greatly in the way object-oriented programming is implemented. If you take the totality of all implementations of OOP in all languages, then most likely you will not find any features common to all. In order to somehow limit this zoo and bring clarity to the reasoning, I will focus on only one group - purely object-oriented languages, namely Java and C #. The term “pure object-oriented” in this case means that the language does not support other paradigms or implements them through all the same OOP. Python or Ruby, for example, will not be clean, because You can easily write a full program on them without a single class declaration.

To better understand the essence of OOP in Java and C #, let's go over examples of implementing this paradigm in other languages.

Smalltalk. Unlike its modern colleagues, this language had a dynamic typing and used message-passing style to implement OOP. Instead of calling the methods, the objects sent messages to each other, and if the recipient could not process what came, he simply forwarded the message to someone else.

Common Lisp. Initially, CL followed the same paradigm. Then the developers decided that writing `(send obj 'some-message)` is too long, and converted the notation into a method call - `(some-method obj)`. Today Common Lisp has an advanced object-oriented programming system (CLOS) with support for multiple inheritance, multimethods, and metaclasses. A distinctive feature is that OOP in CL does not revolve around objects, but around generalized functions.

Clojure. Clojure has as many as 2 object-oriented programming systems — one inherited from Java, and one based on multimethods and more similar to CLOS.

R. This language for statistical data analysis also has 2 object-oriented programming systems - S3 and S4. Both are inherited from the S language (which is not surprising, given that R is an open source implementation of commercial S). S4 for the most part meets the implementation of the PLO in modern mainstream languages. S3 is a more lightweight version, elementarily implemented by the means of the language itself: a single common function is created that dispatches requests for the “class” attribute of the received object.

Javascript By ideology similar to Smalltalk, although it uses a different syntax. Instead of inheritance, prototyping is used: if the object itself does not have the desired property or the method called, the request is passed to the prototype object (the prototype property of all JavaScript objects). An interesting fact is that the behavior of all objects of a class can be changed by replacing one of the prototype methods (for example, adding the `.toBASE64` method for the string class looks very nice).

Python. In general, it adheres to the same concept as mainframe languages, but besides it supports transferring an attribute search to another object, as in JavaScript or Smalltalk.

Haskell. In Haskell, there is no state at all, and therefore no objects in the usual sense. Nevertheless, there is still a peculiar OOP there: data types (types) may belong to one or more type classes. For example, almost all types in Haskell are in the class Eq (responsible for comparing 2 objects), and all numbers are additionally in the classes Num (operations on numbers) and Ord (operations <, <=,> =,>). In menstrim languages, classes (data) correspond to types, and types to classes - interfaces.

Stateful or Stateless?


But back to the more common object-oriented programming systems. What I could never understand was the relationship of objects with the internal state. Prior to the study of OOP, everything was simple and transparent: there are structures that store several related data, there are procedures (functions) that process them. walk (dog), remove (account, amount). Then the objects came, and it was also nothing (although it became much more difficult to read the programs - my dog ​​was walking [for whom?], And the account was withdrawing money [from where?]). Then I found out about data hiding. I could still walk the dog, but I could not see the composition of its food. The food did not perform any actions (probably, it was possible to write that food. Eat (dog), but I still prefer my dog ​​to eat food, and not vice versa). Food is just data, and I (and my dog) just needed to access them. It's simple . But it was already impossible to get into the framework of the paradigm, as in the old jeans of the late 90s.

Well, we have data access methods. Let's go to this little self-deception and pretend that our data is really hidden. But now I know that objects are primarily data, and then, perhaps, their processing methods. I understood how to write programs, what to strive for when designing.

Before I could enjoy enlightenment, I saw the word stateless on the Internet (I swear it was surrounded by a shine, and a halo hung above the letters t and l). A short study of literature has opened up the wonderful world of transparent control flow and simple multithreading without the need to track the consistency of an object. Of course, I immediately wanted to touch this wonderful world. However, this meant a complete rejection of any rules - now it was not clear whether the dog should walk itself, or for this you need a special Walking Manager; do you need an account, or does the bank handle all the work, and if so, then he should write off money statically or dynamically, etc. The number of use cases increased exponentially, and all options in the future could lead to the need for serious refactoring.

I still do not know when the object should be made stateless, when stateful, and when just a data container. Sometimes this is obvious, but most often not.

Typification: static or dynamic?


There is one thing that I cannot decide about languages ​​such as C # and Java, that they are statically or dynamically typed. Probably most people will exclaim, “What nonsense! Of course statically typed! Types are checked at compile time! ”. But is it really that simple? Is it true that a programmer, writing type X in the method parameters, can be sure that objects of type X will always be transferred to it? True - can not, because In method X, you can pass a parameter of type X or its successor . It would seem, so what? The heirs of class X will still have the same methods as X. Methods methods, but the logic of the work may be completely different. The most common case is when a child class is optimized for other needs than X, and our method can rely on that optimization (if this scenario seems unrealistic to you, try writing a plugin to some developed open source library - or you spend a few weeks to parse the architecture and library algorithms, or you’ll just randomly call methods with a suitable signature). As a result, the program works, but the speed of work falls on the order. Although from the point of view of the compiler everything is correct. It is significant that Scala, which is called the heir of Java, in many places by default only allows to pass arguments of the specified type, although this behavior can be changed.

The other problem is the null value that can be passed in place of virtually any object in Java and in place of any Nullable object in C #. null belongs to all types at once, and at the same time does not belong to any. null has no fields or methods, so any call to it (except checking for null) leads to an error. It seems that everyone is used to this, but to compare Haskell (and the same Scala), they are forced to use special types (Maybe in Haskell, Option in Scala) for wrapping functions that in other languages ​​could return null. As a result, Haskell is often told “to compile a program on it is difficult, but if it still works, then most likely it works correctly”.

On the other hand, mainstream languages ​​are obviously not dynamically typed, and therefore do not have such properties as simplicity of interfaces and flexibility of procedures. As a result, writing in the style of Python or Lisp also becomes impossible.

What is the difference, what is the name of such typing, if all the rules are still known? The difference is from which side to approach the design of architecture. There is a long-standing argument about how to build a system: do many types and few functions, or few types and many functions? The first approach is actively used in Haskell, the second in Lisp. Modern object-oriented languages ​​use something in between. I don’t want to say that this is bad - it probably has its advantages (in the end, you shouldn’t forget that Java and C # have multilingual platforms), but each time I start a new project, I wonder where to start designing - with types or with functionality.

And further...


I do not know how to model a task. It is believed that OOP allows you to display real-world objects in the program. However, in reality, I have a dog (with two ears, four paws and a collar) and a bank account (with a manager, clerks and a lunch break), and in the program - VygulManager, AccountsFactory ... well, you understand. And the point is not that the program has auxiliary classes that do not reflect the objects of the real world. The point is that the flow of control is changing . , (, , ?).

, , , . , , .

, . Python C++, , . Java C# StringUtils. -- ad hoc ( ). . ( ) . 150 , — , , .

. — . - . HTTP URL, HttpConnection, Request… , . , . , — . .

, — . , , . , -, - . , .

Source: https://habr.com/ru/post/147927/


All Articles