Look at the following piece of jQuery code:
Of the two patterns it demonstrates, one is almost decisively bad: you shouldn’t build up DOM nodes this way. To get more concise and maintainable code, it’s better to use one of the client-side templating engines.
The second pattern, however, is hugely interesting. Most often called method chaining, it also goes by a more glamorous name of fluent interface. As you can see by a careful look at the code sample above, the idea is pretty simple:
Whenever a method is mostly mutating object’s state, it should return the object itself.
Prime example of methods that do that are setters: simple function whose pretty much only purpose is to alter the value of some property stored as a field inside the object. When augmented with support for chaining, they start to work very pleasantly with few other common patterns, such as builders in Java.
Here’s, for example, a piece of code constructing a Protocol Buffer message that doesn’t use its Builder
‘s fluent interface:
And here’s the equivalent that takes advantage of method chaining:
It may not be shorter by pure line count, but it’s definitely easier on the eyes without all these repetitions of (completely unnecessary) builder
variable. We could even say that the whole Builder pattern is almost completely hidden thanks to method chaining. And undoubtedly, this a very good thing, as that pattern is just a compensation for the deficiencies of Java programming language.
By now you’ve probably figured out how to implement method chaining. In derivatives of C language, that amounts to having a return this;
statement at the end of method’s body:
and possibly changing the return type from void
to the class itself, a pointer to it, or reference:
It’s true that it may slightly obscure the implementation of fluent class for people unfamiliar with the pattern. But this cost comes with a great benefit of making the usage clearer – which is almost always much more important.
Plus, if you are lucky to program in Python instead, you may just roll out a decorator ;-)
Many caveats clutter the JavaScript language. Some of them are quite hilarious and relatively harmless, but few can get really nasty and lead to insidious bugs. Today, I’m gonna talk about something from the second group: the semantics of this
keyword in JavaScript.
this
?It is worth noting why JS has the this
keyword at all. Normally, we would expect it only in those languages which also have the corresponding class
keyword. That’s what C++, Java and C# have taught us: that this
represents the current object of a class when used inside one of its methods. It only makes sense, then, to use this
keyword in a class scope, denoted by the class
keyword – both of which JavaScript doesn’t seem to have. So, why’s this
even there?
The most likely reason is that JavaScript actually has something that resembles traditional classes – but it does so very poorly. And like pretty much everything in JS, it is written as a function
:
Here, the Greeting
is technically a function and is defined as one, but semantically it works more like constructor for the Greeting
“class”. As for this
keyword, it refers to the object being created by such a constructor when invoked by new
statement – another familiar construct, by the way. Additionally, this
also appears inside greet
method and does its expected job, allowing access to the text
member of an object that the method was called upon.
So it would seem that everything with this
keyword is actually fine and rather unsurprising. Have we maybe overlooked something here, looking only at half of the picture?…
Well yes, very much so. And not even a half but more like a quarter, with the remaining three parts being significantly less pretty – to say it mildly.
Obiekty mają metody. Tak, w tym stwierdzeniu nie należy doszukiwać głębokiego sensu – jest ono po prostu prawdziwe :) Gdy mówimy o metodach obiektów czy też klas, zwykle mamy jednak na myśli tylko jeden ich rodzaj: metody instancyjne. W wielu językach programowania nie jest to aczkolwiek ich jedyny rodzaj – z takim przypadkiem mamy do czynienia chociażby w Pythonie.
Jak zawsze metody instancyjne są domyślnym typem, który tutaj jest dodatkowo zaznaczony obecnością specjalnego parametru – self
– występującego zawsze jako pierwszy argument. To odpowiednik this
z języków C++, C# czy Java i reprezentuje instancję obiektu, na rzecz której wywoływana jest metoda:
Fakt, że musi być on jawnie przekazany, wynika z zasad tworzenia zmiennych w Pythonie. Nie muszą być one jawnie deklarowane. Dlatego też odwołanie do pola obiektu jest zawsze kwalifikowane, gdyż przypisanie do _value
zamiast self._value
stworzyłoby po prostu zmienną lokalną.
Istnieją jednak takie metody, które nie operują na konkretnej instancji klasy. Typowo nazywa się je statycznymi. W Pythonie nie posiadają one parametru self
, lecz są opatrzone dekoratorem @staticmethod
:
Statyczną metodę można wywołać zarówno przy pomocy nazwy klasy (Counter.format_string()
), jak i jej obiektu (Counter().format_string()
), ale w obu przypadkach rezultat będzie ten sam. Technicznie jest to bowiem zwyczajna funkcja umieszczona po prostu w zasięgu klasy zamiast zasięgu globalnego.
Mamy wreszcie trzeci typ, mieszczący się w pewnym sensie pomiędzy dwoma opisanymi powyżej. Nie wydaje mi się jednak, żeby występował on w żadnym innym, popularnym języku. Chodzi o metody klasowe (class methods). Nazywają się tak, bo są wywoływane na rzecz całej klasy (a nie jakiejś jej instancji) i przyjmują ową klasę jako swój pierwszy parametr. (Argument ten jest często nazywany cls
, ale jest to o wiele słabsza konwencja niż ta dotycząca self
).
W celu odróżnienia od innych rodzajów, metody klasowe oznaczone są dekoratorem @classmethod
:
Podobnie jak metody statyczne, można je wywoływać na dwa sposoby – przy pomocy klasy lub obiektu – ale w obu przypadkach do cls
trafi wyłącznie klasa. Tutaj akurat będzie to Counter
, lecz w ogólności może to być także klasa pochodna:
Powyższy kod – będący przykładem dodatkowego sposobu inicjalizacji obiektu – to dość typowy przypadek użycia metod klasowych. Korzystanie z nich wymaga aczkolwiek nieco wprawy w operowaniu pojęciami instancji klasy i samej klasy oraz ich poprawnego rozróżniania.
W gruncie rzeczy nie jest to jednak nic trudnego.