Język Scala dla programistów PHP pt.2 – klasy

Witam w drugiej części cyklu „Język Scala dla programistów PHP”. Dzisiaj weźmiemy pod lupę jedno z podstawowych zagadnień każdego języka programowania – strukturę jego kodu.

Każdy język programowania, posiada pewne archetypy, które pozwalają nam systematyzować nasz kod i czynić go czytelnym. Elementami takimi w językach proceduralnych są procedury i funkcje. Języki funkcyjne posiadają funkcję i przestrzenie nazw. Języki obiektowe – klasy i paczki.

Język Scala jako język wieloparadygmatowy posiada trzy główne elementy strukturyzujące kod – paczki, klasy i funkcje. Jednak w przeciwieństwie do „klasycznych modeli obiektowych” znanych chociażby z Javy czy PHP, oprócz „zwykłych” klas, mamy jeszcze trzy ich odmiany. Klasy w Scali dzielą się na:

  • klasy (class)
  • singletony (object)
  • cechy (trait)
  • rekordy (ciekawe czy to właściwe tłumaczenie) (case class)

Poniżej postaram się pokrótce omówić każdy rodzaj klasy wraz z kontrprzykładem z PHP/i lub Javascriptu.

Klasa:


class SampleClass(foo: Int, bar:Double) {

    private var _foo = foo;
    private var _bar = bar;

    def showFooAndBar: String = {
      return foo.toString + " i " + bar.toString;
    }

}

Standardowe klasy w Scali definiowane są podobnie jak w Javascripcie – przez konstruktor. Jak widzimy na załączonym przykładzie mamy zdefiniowany konstruktor klasy SampleClass, który przyjmuje jako argumenty zmienne foo i bar. Argumenty te przypisywane są do prywatnych zmiennych _foo i _bar. Konstruktor ten definiuje również jedną metodę – showFooAndBar , który zwraca obiekt typu String. Dla porównania kontrprzykład w javascripcie:


function SampleClass(foo, bar) {

   var _foo = foo
   var _bar = bar

   this.showFooAndBar = function(){
        return _foo + ' i ' + _bar;
   }

}

Użycie:

Scala:


object Main {
  def main(args: Array[String]): Unit = {

    val sample = new SampleClass(1,5.5)
    println(sample.showFooAndBar)
  }

}

Javascript:


var sample = new SampleClass(1, 5.5)
console.log(sample.showFooAndBar())

Jak widzimy podobieństwo jest uderzające. Dla tych, dla których model obiektowy Javascriptu jest okropny i niezrozumiały mam dobrą wiadomość – mimo podobieństwa w tym przypadku kodu jednego i drugiego języka, Scala nie bazuje na prototypach.

Przy okazji omawiania klas w Scali warto zauważyć jedną rzecz – metody klasy nie muszą być wywoływane z użyciem nawiasów. Ten fakt wynika z zastosowania w języku zasady „Uniform access principle”, która mówi, że dostęp do pól klasy i jej metod powinien być jednakowy. Praktyczną konsekwencją tego faktu jest to, że nie musimy zawczasu deklarować tony getterów i setterów, możemy zostawić pola jako publiczne, natomiast później w miarę potrzeby, przesłonić je funkcją bez zmiany zewnętrznego interfejsu klasy.

Mały przykład dla lepszego zrozumienia:
Oryginalna klasa:

class Foo {

  var bar = 5;

}

Klasa po refaktoringu:

class FooRefactored {

  private var _bar = 5;

  def bar: Int = {
    return this._bar;
  }

  def bar_=(bar:Int) :Unit = {
    this._bar = bar;
  }

}

Jak widzimy w drugim przykładzie, zmienna bar stała się prywatna. By uzyskać do niej dostęp stworzyliśmy getter – def bar: Int i setter – def bar_=(bar:Int): Unit. Deklaracja settera może wydać się trochę dziwna, bo niby czemu jest tam operator podstawienia i czemu jako typ zwracany mamy Unit ? Żeby rozjaśnić trochę sytuację – w Scali, każda operacja przeprowadzona na instancji klasy jest wywołaniem metody. Co więcej, wszystkie zmienne instancji są w rzeczywistości prywatne, jeżeli jednak nie dodamy im modyfikatora „private” przed deklaracją, kompilator automatycznie wygeneruje getter i setter właśnie w wyżej wymienionej postaci (btw. Unit == void).

Skoro już poznaliśmy mniej więcej w jaki sposób korzysta się z klas, słów kilka o tym, czym jest twór który deklarujemy słowem kluczowym „object”.

Otóż „object” jest singletonem. Z matematyki wiemy, że singleton to zbiór o tylko jednym elemencie. Jeżeli przyjmiemy, że klasyczna klasa jest listą warunków, które musi spełnić obiekt by należeć do zbioru jej instancji, to „object” jest jednocześnie definicją zbioru i jego jedynym elementem. Object jest również jedynym sposobem w Scali na uzyskanie funkcjonalności statycznych metod, ponieważ nie występują one w tym języku.

Pomiędzy zwykłą klasą a objectem istnieją dwie fundamentalne różnice:

  • w momencie deklaracji, tworzona jest jedyna instancja „objectu”, nie można tworzyć nowych instancji operatorem new
  • object nie posiada konstruktora, co wynika bezpośrednio z punktu pierwszego

Poza tymi dwoma różnicami, właściwości obiektu i klasy są identyczne np. obiekt może dziedziczyć z klasy tak jak i klasa z obiektu.

Przykład singletona w Scali i jego syntaktycznego odpowiednika w PHP:
Scala:

object Main {
  def main(): Unit = {    
    println("Hello world"); 
  }
}

PHP:

class Main {
    public static main(){
        echo "Hello world";
    }
}

Na koniec tego wpisu jeszcze kilka słów o pewnej charakterystycznej rzeczy, która występuje w Scali. Mowa o słowach kluczowych var i val. Czym się różnią ? Var deklaruje klasyczną zmienną. Natomiast val w zasadzie jest odpowiednikiem słowa const znanego z innych języków oprogramowania. Zadeklarowanie czegoś jako val oznacza, że nie będziemy mogli podstawić pod taką zmienną już innego obiektu. Próba ponownego zadeklarowania nowej wartości pod stałą oznaczoną słowem kluczowym val powoduje błąd kompilacji, oraz podkreślenie na czerwono przez nasz ulubiony edytor ;).

Wpis zrobił się już nieco długi, dlatego też zapraszam na dalszą część, poświęconą cechom (traits) i być może paczkom.

  1. mc pisze:

    Case class to chyba trochę więcej niż po prostu rekord. Kojarzy mi się to raczej ze scenariuszami użycia znanymi z UMLa. Z resztą:

    http://www.scala-lang.org/node/107

  2. Ja bym w całą sprawę nie mieszał UML-a. Zresztą, będę eksplorował temat szerzej to postaram się więcej coś o nich powiedzieć 😉

  1. There are no trackbacks for this post yet.

Leave a Reply

Informuj mnie o odpowiedziach poprzez e-mail. Możesz również subskrybować wpis bez zostawiania komentarza.