⬆️ ⬇️

I probably know OOP. Experience in object-oriented programming and design. The answer is "not knowing OOP."

After the appearance of articles like " I do not know the PLO " - there is a desire to clarify, "to break cover" and "get to the bottom of the truth."



Principles of object orientation



Usually, there are four “principles of object-oriented programming”: abstraction, encapsulation, inheritance, and polymorphism.



In my opinion (not to mention that abstraction and polymorphism can easily be assigned to subsections of inheritance), the principle here is the same , in general, the same as in database design: representing everything as an object - some thing with properties . A set is usually fixed, and then people talk about a class of objects, and even if the concept of a class is not, then the presence of properties with certain names is implied by the logic of the program, i.e. Something like a class in the form of a certain minimum set of properties is still present. In general, views go back to the old C-shnuyu / Pascal type of data struct / record . Then a bit of “functionality” was added to this (in the sense of functional programming): the value of a property can be a function, and one that has access to the structure / record itself, the value of one of which properties it is. This phenomenon, in the best traditions of German Latin naming (when the option is called “option”, and the degree of the number - “potency”), was called “ method ”. The desire to reuse the code, in combination with the presentation of each object as a kind of Pascal “record”, led to the concept of “ inheritance ”.

')

With the advent of “methods”, another pleasant moment has emerged regarding the organization of the functional: instead of creating 900 functions with cross duplicating first and second halves of names (remember WordBasic?), You can create 30 classes with 30 methods each.



Also, it became possible to make languages ​​with strict static typing , which made it possible to transfer the time of detection of many errors from the program execution phase to the compilation phase. And in the case of strict static typing, it has sometimes become necessary to make classes in which the method is declared despite the fact that it is implemented only in descendants. So the concept of pure virtual methods and abstract classes appeared .



And encapsulation, inheritance, abstraction, and polymorphism are not “principles”, but rather “guidelines” of object-oriented programming. Encapsulation, in particular, makes proxying possible. We note, by guidelines only and only the process of “programming”, and not “design” or “approach”, which can often be misleading.



Criticism. Inheritance. The birth of interfaces



With the development of the concept of inheritance, it would be desirable to have the possibility that an object belong to several classes at once, which are not parents and descendants in relation to each other. This can be done by entering the possibility of multiple inheritance. But, as it turned out, real multiple inheritance usually raises the problem of “ rhombic inheritance ”: when inheriting from two descendants of one class, it is not clear how to resolve multiple inheritance of the same members - fields, properties, and methods.



The problem was largely resolved by the invention of “ interfaces ” - completely abstract classes in which only purely virtual, ie, abstract, methods. “Places to insert static implementations” (and in general any class method in a strongly typed language, if considered as the value of something, is static: this “value” - the definition of the method is the same for all instances of the class) - is the only what was allowed in these methods could not, by definition, cause problems with “rhombic inheritance”. Moreover, with the spread of encapsulation, when only methods “stick out”, this is not perceived as something extraordinary.



The interface is also good because it almost implements the functional paradigm for a non-functional language, allowing you to actually pass a set of algorithms as an argument.



Criticism. Why so few "real" classes. Multiple dispatching. The birth of "managers" and their separation from Domain objects. Singletons





A class is considered to be a set of properties plus “behavior”. I recall the words from Help to VisualBasic 5 that I read in 1998, something like: “I ’m telling you how to tell object to do. The event is what you’ve done . So what about the "behavior" here is another question - a method or something else.



Design problems begin when “behavior” includes two or more classes. As here in the commentary - “Height, weight, age - properties. To walk, cn (r) at - behavior. ”Here, in the case of modeling behavior with the letter“ p, ”there are more Than and Where (and there is also“ Where to go ”- there is also Where). In general, when several non-primitive classes are involved in the action, when the object model is developed, the question arises - which of the two, three, four, etc. Classes - in this case “who”, “than” or “where” - should belong the method that simulates this action? And in any case, this is likely to be tight coupling and in the process of further building up the system (and constant adding, expanding and improving should be considered as a regular rather than an exceptional situation) “will come out sideways”.



This problem is solved by the allocation of a special entity - " manager ". As a result, an object of the type “bank account” does not have a method to transfer_to (account, amount) , but instead an object of type such as “account manager” has a method of transfer_cause (c_account, into_account, amount) .



Classes themselves, which are not “managers”, but reflect real-world objects — domain object classes, with the continuation of such a trend, there are no methods left at all except those required to ensure the principle of encapsulation — the most primitive accessors and mutators. And that is typical in the object system in which the problem of multiple dispatching is solved immediately and radically - there are no methods as such in the CLOS, but there are special functions of the same name with different types of arguments that belong to the entire system.



What is interesting is that, in general, there is no need to know anything about the type of "account manager" except that he has a method of transferring_to_n_to (c_to account, into_to account, sum) . Those. This is a typical implementation interface. And if you need to change the data integrity mechanism (from transactions to manual locks or vice versa), you don’t need to rewrite the “account” type code, you don’t need to rewrite anything at all (and you don’t need to re-test something old, either) write a new interface implementation.



In the same place where such methods were decided to be left in one of the participants, one has to use the Visitor design pattern ugliness - when the functionality is actually brought out, and it is pulled from the inside, as if it were left inside.



The question arises - if there are no properties in the class that are significant from the point of view of the object model of a piece of reality, if it does not model a single physical object, how many instances should it exist? Isn’t it enough? In general, the smaller the copies, the better, and the number is determined by technical reasons - for example, one by one per connection from the pool. In a particularly degenerate case, one instance is sufficient, especially if its creation is quite resource intensive. This is called the design pattern " singleton " - "loner". The class is written so that there is a single instance of it, obtained through a static method.



In today's era, when the issue of instantiating service classes is given to different dependency injectors, the implementation of the “singleton” pattern, as an example of rigid manual instantiation control, is something like a kind of tight coupling and, therefore, a violation of the design principle and very uncomfortable.



Criticism. "I want a redefinable constructor." Factories



The use of constructors with complex behavior, as well as the intensive use of inheritance, is, in general, an example of tight coupling (fields and interface, hehe) and, therefore, moveton. When calling a descendant class constructor, constructors of all ancestor classes will be called first, which, in general, is often not necessary and generally resource-intensive and simply harmful. It is better to concentrate the logic of instantiating a class in a separate service class, driving all the “presale preparation” into it at the same time. The service class can be arranged as an interface and so on with all the stops already described. It turns out " factory ".



A factory has a couple more advantages: 1) it can return null and 2) it can return the same object many times.



Criticism. Primitives and other Value objects. Immunity. Internisation.





The principle “everything is an object”, of course, attracts with its sequence. Although initially it is assumed that the values ​​of the properties of objects themselves are not objects. And, in general, the class system should eventually have exits to the “real”, and the “real” is always the data of one of the FoxPro types: number, string, date-time or yes / no. Therefore, if excessive flexibility is not required, then it is better not to do everything with objects and leave some non-object data types.



This idea acquires an especially important meaning in high-level languages ​​— languages ​​with automatic garbage collection, from which objects are represented exclusively by reference to them. To speed up performance, not all data types should be referenced.



C primitives in their design border Value objects . These are objects without special methods, having a set of properties with values, primitive or also Value objects, which are checked for equality based not on physical sameness (the same references), but from property values.



Often (even, rather, as a rule) Value objects are made immutable , i.e. unchangeable Immunity allows you to fearlessly use complex value objects as keys and generally gives you a bunch of additional “buns”, in particular, an immutable square can be the heir of an immutable rectangle without any back logic, which is not the case for their mutable versions.



Next to the concept of immunity, there is the concept of internment — so that all equal (in the sense of property values) objects of a certain class are physically equal, that is, were the same object. This is ensured by storing all ever used instances of a given class in a static or otherwise “sigletonny” set (Set), and, ideally, getting a new one only through the “factory” “passing” all the “products” through the check for uniqueness and issuing only unique instances. In the creation phase, this slows down a lot, but use then accelerates many times over.



Classes IRL. Criticism. “Some classes are more equal than others”



Close to the top of the pyramid of classes "equality" and the equivalence of classes is violated. Especially when you want to “put on object-orientedness” the execution environment itself.



In Java, the root class for all classes is Object. But the Object class has a getClass () method that returns an object of type Class, and a toString () method that returns an object of type String. It also has methods that throw CloneNotSupportedException, InterruptedException, IllegalArgumentException, and Throwable. In this case, the classes Class, Throwable and String are direct descendants of the class Object, and CloneNotSupportedException, InterruptedException and IllegalArgumentException are even more than indirect. In general, a class that has, in half of the methods, references to its notorious descendants, often indirect ones, is a bad design (as well as any cyclic dependencies), but "if it is impossible, but it is very necessary, then it is possible once." And the root class for exceptions and errors - Throwable - also contains references to its indirect descendants: IllegalStateException and IllegalArgumentException, so, again, in a limited dosage "not a dogma."



Although, the String and Class classes are final, i.e. cannot have descendants, which reduces the “criminality” of their use in the Object class.



Classes IRL. What they became in the end



In the Java language classes have become largely a kind of labels on objects. In the situation of intensive use of method overloading, they also became, to a large extent, a part of the method name. Strong typing with time played an unexpected positive role: the Java language proved to be the most suitable for refactoring.



Inheritance, as well as the attachment of algorithms to the domain object class, is recognized as tight coupling and bad practice, and is applied strictly metered. In particular, the preferred way to implement the “social account” entity for a program that works with different social networks is not creating the abstract class SocialAccount with the descendants of FBAccount, VKAccount and TwitterAccount, but creating the normal domain class of the SocialAccount and converting factories to it from the data from different social networks, maybe even JSON-to-SocialAccount-converters. Reminds ORM? So 10 years ago everything was back: DB-persistence-platforms were distributed in which the database read / write logic was in the methods of the persistent classes themselves, because of which they had to be inherited from a special class such as some DBExternalizableObject. Later, “as it is known,” they refused it.



Modern domain classes, in addition, are in fact almost always a layer of records in the tables of melon bases and are designed initially in the DBMS or in the DBMS design tool.



And "normal", "classical" classes with properties, methods and inheritance are found mainly in application servers and other libraries.



UPD : In the comments correctly suggested that it was necessary to talk not about "strict" typing, but about "static". I am confused regularly, and not one.

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



All Articles