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

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

Na szczęście szok ten trwa tylko do momentu, kiedy zrozumiemy, że wszystko co w Javie robił static, w Kotlinie możemy zrobić wygodniej za pomocą 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ą, że 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ż, że 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, że tak bardzo przyzwyczailiśmy się do tego „wytrychu”, że 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ć, że 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śli 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 że 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ę, że 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 nadal 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!

 

Nie przegap kolejnych wpisów - subskrybuj!

6 komentarzy

  1. Bądź co bądź klasy ‚Utils’ nadal są – bo gdzieś kod tych metod rozszerzających trzeba trzymać… tylko teraz wołamy je w bardziej elegancki sposób 🙂

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

    Myśle że teraz patrząc na trendy ciekawsze pytanie brzmi: Czy zastanawiałeś się czy programowanie funkcyjne jest zgodne z ideą OOP? 🙂

    Z mojego doświadczenia wynika że OOP do aplikacji biznesowych jest mocno przereklamowane. Rzadko kiedy bardziej zaawansowane paradygmaty OOP się przydają, a często nawet zbytnio komplikują. Ważniejszą rolę grają moduły / komponenty funkcjonalności, w różnych formach jak kontrolery, serwisy, fabryki, itd.

    Najprężniej rozwijającym środowiskiem jest chyba świat JavaScripta, szczególnie obserwując ostatnie lata. Tam wygląda na to że tendencje idzie bardziej w stronę odseparowanych modułów do stanów które są niezmienne (patrz flux np. redux z immutable), niż zarządzaniem stanów w hermetycznie zamkniętych obiektach. Całość dopełnia reaktywne programowanie i IoC.

    A tak poza tym to Świetne artykuły, trzymaj tak dalej. Pozdrawiam

    1. Dzięki! 😀 Zgadzam się w zupełności 🙂 Do tej wyliczanki modułów dodałbym jeszcze usługi SaaS, PaaS, i ostatnio FaaS. Piękna sprawa 😉
      Od niedawna bawię się Google Firebase, i muszę przyznać, że model usługowy jest mega-przyjemny.
      Ale, koniec końców, my – prości developerzy – musimy korzystać z jakiegoś języka. Więc bardzo dobrze, że na JVM mamy teraz Kotlina! 😀
      BTW, słyszałeś o Kotlin/JS? 😉 https://kotlinlang.org/docs/reference/js-overview.html

    2. Kotlin dla JSa? Nieprawdopodobne (chociaż w dzisiejszych czasach prawie wszystko już się transpiluje do JSa ;)). Super sprawa nie wiedziałem, dzięki, może kiedyś będę miał chwilę się tym pobawić.

    3. Yup 🙂 Kotlin/JS jest rozwijany właściwie od samego początku, razem z Kotlin/JVM. A od jakiegoś czasu pracują też nad Kotlin/Native. Już teraz możesz kompilować natywne binarki dla Linuxa i MacOS, a w drodze jest iOS 🙂 Także widać, że naprawdę inwestują w Kotlina sporo czasu i środków 😉

Dodaj komentarz