The "gang of four" was wrong, the standard Ruby library is also wrong, and so is Rails. But is something wrong if everyone does it?
Yes.
The book "Gangs of four" "
Design Patterns " gives us a general vocabulary for understanding basic OOP patterns. It helps us use the same terminology when discussing software. Unfortunately, it is also the cause of confusion.
')
They say: "composition before inheritance." Well, that makes sense. They say: “use delegation”. Fine. Although the book does not have a single example of delegation.
Delegation is a technique that is credited with the possibility of introducing flexibility into programs. It is usually said that delegation is the way to achieve composition. But delegation is not what you think, and the “Gang of four” has misled you. Worse, almost all references to delegation contain only the joint work of objects with forwarding messages. These are examples of method calls, not delegations.
Surely your programming teacher would tell you that you need to understand basic programming concepts well. And understand them correctly.
So what is delegation?
Delegation is easy to understand - but let's correct the fact that we constantly saw this term along with a description of something else.
Henry Lieberman described this term in detail in the article “Using prototypical objects to implement shared behavior in object-oriented systems”. But I do not refer you to it, although it would be useful to
read it (or its
online version ) - I cite a key point describing the delegation. Lieberman discussed this issue in the context of a GUI drawing tool. Here is the basic idea:
When a pen delegates a drawing message to a pen prototype, it says: “I don’t know how to handle the drawing message. Please respond if you can, and if you have more questions, such as what is the value of the variable x, or you need to do something else, you will have to go back to me and ask. ” If the message is delegated further, all questions by the value of variables or requests for replies to messages are sent to the object that originally delegated the message.
I mean, when you send a message to an object, it has the concept of self, in which it can find attributes and methods. When this object delegates authority to another, each reference to self always points to the original object. Is always.
Other inheritance
Inherit can be not only from classes. Prototype-based inheritance is another way to organize objects so that they can share behavior. One approach establishes behavior in an abstract place (class), and the other in instances (objects).
Self is a programming language that implements what Lieberman is talking about. In Self, there are objects containing slots. Each slot can contain a method or a reference to the prototype object in the parent slot. If an object receives a message and does not understand it, it can delegate it to an object in the parent slot.
This is an inheritance of prototypes, not classes. This is about the same, though not identical, behavior when an object has a class (like Ruby objects) containing additional behavioral options.
Providing objects with parent slots in Self is similar to providing an object in
JS with references to prototypes. JS is the most popular language using prototypes, and it is much easier to try it out. Therefore, we will not teach Self, but immediately try JS. The following code can even be executed directly in the browser console.
In JS, you can assign a prototype - the equivalent of the parent slot in Self.
function Container(){}; Container.prototype = new Object(); Container.prototype.announce = function(){ alert("these are my things: " + this.things) }; function Bucket(things){this.things = things}; Bucket.prototype = new Container(); bucket = new Bucket("planes, trains, and automobiles") bucket.announce()
In the context of delegation, the bucket object is a client that sends a message to the delegate. In our example, you can see that the calculation of this.things occurs in the context of the client object. When we call announce, it is found on the delegate object. When calculating the function, this points to the client.
When a prototype object in JS has a function, it is calculated as if the object has such a method. The first example shows that this (in JS, self) always points to the original recipient of the message.
What about Ruby?
First, let's look at the forwarding of messages. Forwarding is the transfer of a message from one object to another. The standard forwardable library is named this way, and allows you to forward messages from one object to another.
Let's now take the not so well-named delegate library, which also allows you to send messages from one object to another.
require 'delegate'
What happens inside the greeter? When its instance is initialized, it contains a link to the person. When an unknown method is called, it is sent to the target object (namely, person). After all, we are still working with the delegate library, which helps us with sending messages. Confused? And I, too, was misunderstood. Like the rest of the world, apparently.
Forwarding is just forwarding a message to an object — calling a method.
The difference between it and delegation is that these libraries allow you to easily send messages to additional objects, and not just call the method of another object in the context of the first, as happens in the case of inheriting prototypes in JS.
Normally, we don’t think about it, since Ruby's method_missing capabilities perform this focus inside the SimpleDelegator. And we think that methods are magically invoked on the desired object. And although our Greeter refers to self, when the client does not have the required method, the message is sent to another object and processed there.
If we need to share the behavior without extending the object with additional methods, then method_missing and / or SimpleDelegator can help us. For simple options, this works well. But this system breaks the reference to the object class.
Suppose we need to refer to the client object class with some new type of greeting. Instead of the usual, let's say: “Hi! I'm the esteemed Person, Jim. ” We will not rewrite the method, but just hope for super to get what is defined in the ordinary Greeter class.
class ProperGreeter < Greeter def name "the esteemed " + self.class.name + ", " + super end end proper_greeter = ProperGreeter.new(person) proper_greeter.hello
It turned out a little not what we wanted. We wanted to see "the esteemed Person".
This is a simple example of the fact that we actually have two objects, and of how schizophrenia begins in OOP due to self. Each object has its own identity and its own understanding of self. We can fix the situation, change the link to __getobj__ (as SimpleDelegator expects from us) instead of self, but this is an example of how self does not refer to what we need. We have to work with two objects and our idea of the work of the program requires that we think about two objects interacting with each other, while we, in fact, change the behavior of only one.
This is not a delegation, but an interaction of two objects. Despite a bunch of articles, books and libraries that convince you otherwise.
Well, call it whatever you like
Who cares, because everyone does it?
Yeah, in the "Design Patterns" are examples in C ++. And C ++ does not know how to delegate. Is this argument enough to redefine the meaning of a term? If the language you are using does not have this capability, do not say that it can "delegate."
Fix concepts
To develop an application, you need to understand not only the concepts of the logic of its work, but also possess a variety of tools and approaches to the design of the architecture. Patterns of design programs - generally accepted ways to solve common problems. Understanding how they work and when they can be used is one of the key features of a successful developer.
The book "Gangs of four" "Design Patterns" gives us a general vocabulary for understanding basic OOP patterns. It helps us use the same terminology when discussing software.
When I started my research to write the
Clean Ruby book, I turned to Design Patterns. “I’m sure this book will help build the right understanding of using different templates,” I thought. Unfortunately, there is a gross error in it, which was repeated after it in a huge number of books, articles and libraries.
“Design Patterns” promote the idea of “composition before inheritance” well. This phrase is liked by such a large number of people that the next time you discuss the inheritance of classes, you will most likely get it over the head.
Design patterns are often praised not for innovation, but for consolidating terms. It serves as a guide for us in the language we use to understand and discuss various methods of solving problems. She gave the names to common patterns so that we could communicate more productively with each other. Unfortunately, these praises are out of place as soon as you try to understand the delegation.
My book, Clean Ruby, plunges into the understanding of delegation and explores the possibilities of how to properly organize your projects, make them supported and loosely coupled. It gives you ways to organize the separation of behavior and clarifies the definition of important concepts.