Kotlin + (Companion) Object. Czyli dlaczego Kotlin nie ma static’ów?

blog.geekydevs.com 6 lat temu

Jedną z rzeczy, które najbardziej szokują programistów Javy czy C# przechodzących na Kotlina, jest brak słówka static. A choćby gorzej – zupełny brak konceptu elementów statycznych!

Na szczęście szok ten trwa tylko do momentu, kiedy zrozumiemy, iż wszystko co w Javie robił static, w Kotlinie możemy zrobić wygodniej dzięki tzw. „deklaracji obiektów”.

Czy static jest obiektowy?

Zastanawiałeś się kiedyś, czy elementy statyczne są w ogóle zgodne z ideą OOP?

Jeśli zajrzymy do najważniejszej książki o Javie – „Thinking in Java” by Bruce Eckel – zobaczymy tam taki zapis:

Niektórzy twierdzą, iż metody statyczne nie są zorientowane obiektowo, gdyż mają semantykę metod globalnych. W przypadku metod statycznych nie przesyłamy komunikatu do obiektu, ponieważ nie istnieje *this*. Jest to prawdopodobnie słuszny argument (…)

I jest w tym sporo prawdy. OOP zakłada przecież, iż wszystko jest obiektem. A tu nagle static pozwala na takie cuda jak np. wywołanie metody bez potrzeby tworzenia obiektu. „Jak tak można?!” – słychać krzyki purystów językowych

A najciekawszy jest fakt, iż tak bardzo przyzwyczailiśmy się do tego „wytrychu”, iż nie potrafimy już bez niego żyć. Oparliśmy na nim wiele najważniejszych wzorców projektowych, takich jak statyczne metody factory, czy singleton…

Czy można inaczej?

Skoro static jest zgoła nie-obiektowy, to czym go zastąpić? Przecież musimy mieć możliwość tworzenia tych naszych ukochanych (znienawidzonych?) singletonów

Otóż rozwiązanie okazuje się bardzo proste i intuicyjne, a zmiana jest tak naprawdę czysto ideologiczna/semantyczna.

Z pomocą przychodzi nam zaprojektowany już w latach ’70 język Smalltalk. W języku tym naprawdę wszystko jest obiektem – włącznie z samą klasą. Powtórzmy to: „klasa również jest obiektem”.

Przy takim założeniu możemy powiedzieć, iż elementy statyczne są powiązane właśnie z tym „obiektem klasy” – specjalnym obiektem, przyporządkowanym do klasy. Taki obiekt wykazuje też cechy singletonu, gdyż każda klasa posiada dokładnie jeden taki obiekt.

Ma to sens? Najwyraźniej tak, bo właśnie takie rozwiązanie zastosowano w Scali, oraz… Kotlinie

Kotlin ma Obiekty

Kotlin zapożycza od Scali dwie koncepcje:

Pierwsza, jak sama nazwa wskazuje, pozwala na łatwe tworzenie singletonów. Druga umożliwia tworzenie owych „obiektów klasy”. W uproszczeniu jest więc odpowiednikiem Javowych elementów statycznych.

Singletony

Tworzenie singletonu w Kotlinie jest banalne. Używamy tutaj słowa kluczowego object w miejscu zwyczajowego class:

object Basket { val products = mutableListOf<Product>() }

Po dekompilacji do Javy wygląda to tak:

// Java public final class Basket { @NotNull private static final List products; public static final Basket INSTANCE; @NotNull public final List getProducts() { return products; } private Basket() { INSTANCE = (Basket)this; products = (List)(new ArrayList()); } static { new Basket(); } }

Niezłe, prawda? Pewnie nie zdajesz sobie sprawy z doniosłości tego rozwiązania. Oznacza ono bowiem koniec pewnej ery: już nigdy nie dostaniesz w czasie rozmowy kwalifikacyjnej pytania „Jak robimy singleton w Javie?”

Obiekty Towarzyszące

Obiekty możemy również deklarować wewnątrz klas. jeżeli taką deklarację poprzedzimy słowem companion, stanie się on „obiektem towarzyszącym”:

class MyFragment { companion object { fun newInstance(): MyFragment = MyFragment() } }

Elementy takiego obiektu możemy wołać tak, jakby były elementami klasy:

val instance = MyFragment.newInstance()

Czyli wygląda to identycznie jak wywołanie metody statycznej w Javie/C#. Jednak posiada dwie istotne zalety:

  • Zachowanie spójności – wszystko jest obiektem
  • Grupowanie – wszystkie składowe obiektu są w jednym miejscu, a nie porozrzucane po całej klasie

Klasy *Utils

Częstym zastosowaniem dla metod statycznych w Javie były klasy typu *Utils – np. StringUtils, Arrays itd.

Ten tamat już kiedyś poruszaliśmy, i wiemy iż problem ten w 100% rozwiązują w Kotlinie funkcje rozszerzające.

Wyrażenia obiektowe

Słówko object ma jeszcze jedno zastosowanie. Używamy go, gdy chcemy stworzyć odpowiednik Javowej anonimowej klasy wewnętrznej. I tutaj znów, nazwanie jej w Kotlinie „obiektem” ma sporo sensu, gdyż w „Thinking in Java” czytamy:

Ta dziwna składnia dosłownie oznacza: „Stwórz nowy obiekt anonimowej klasy dziedziczącej po XXX”.

Poniżej typowe zastosowanie:

editText.addTextChangedListener(object : TextWatcher { override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { //... } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { //... } override fun afterTextChanged(s: Editable?) { //... } })

Podsumowanie

Mam nadzieję, iż brak staticów już Cię tak nie przeraża. Zastapienie ich „obiektami” ma sporo zalet – ze spójnością i czytelnością na czele. Z kolei tworzenie singletonów to czysta bajka

Jeśli przez cały czas masz problemy z tą koncepcją – daj znać w komentarzu. A może znasz kogoś, kto jeszcze nie wyszedł z opisywanego szoku? Poleć mu ten artykuł.

To wszystko na dziś. Do następnego wpisu!

Idź do oryginalnego materiału