Jason. Cała informatyka w jednym miejscu!

Głodni kolejnej wiedzy z programowania? To teraz mam dla Was na podwieczorek garść informacji o tym, czym się charakteryzuje "enum class" w języku Kotlin. To kolejny bardziej poboczny temat, który umożliwi pisanie kodu źródłowego w sposób jeszcze bardziej profesjonalny!

"ENUM CLASS" W JĘZYKU KOTLIN. TAJNIKI KLASY WYLICZENIOWEJ

Słowo o jakim wspomniałem to bardzo przyjemny sposób definiowania grupy stałych wewnątrz bloku kodu, należących do jednej kategorii. Tak działa "enum", czyli typ wyliczeniowy znany historii rozwoju programowania wysokopoziomowego tak długo, jak długo język C istnieje w informatyce. Kotlin pozwala na zrobienie o jeden krok naprzód dalej i tym sposobem poznajecie jeszcze jeden rodzaj klasy (obok klasy zwykłej, klasy otworzonej i klasy danych) jaką jest klasa wyliczeniowa.

CO NAM DAJE KLASA WYLICZENIOWA?

W roli głównej występuje "enum class" w języku Kotlin. Tak się uprzedza kompilator, że chcemy sobie utworzyć klasę wyliczeniową. Daje nam to kilka plusów:

  • zdefiniowanie szeregu wartości stałych "siedzących" w jej wnętrzu np. nazwy stopni trudności
    • sprzyja to czytelności kodu (nazwy lepsze od numerków) oraz bezpieczeństwu (używając "when" albo instrukcji warunkowych, uzależniamy sprawdzanie równości od istniejącej wartości która MUSI istnieć, a nie od liczby całkowitej która może być, ale nie musi)
  • pozwala na przypisanie właściwości oraz funkcji (jak w każdej klasie), które mogą być przesłaniane przez każdą stałą wartość
    • po zadeklarowaniu, że klasa wyliczeniowa ma mieć właściwość, kompilator wymusza aby konkretna wartość znalazła się obok każdej stałej wartości
    • jeśli klasa wyliczeniowa zawiera w sobie metodę, to można ją wywołać z poziomu stałej wartości wyliczeniowej (instancja "enum class" w języku Kotlin) i wykorzystać do własnych celów

Jak widzicie, klasa wyliczeniowa ma się czym pochwalić co do użyteczności i dużej swobody w działaniu. Obejrzyjmy ją w akcji!

PRZYKŁAD KODU ŹRÓDŁOWEGO (ROZWIĄZANIE PRYMITYWNE)

Przykład problemu, który może zostać rozwiązany lepiej z użyciem wyliczenia. Jest on bardzo uproszczony więc przymknijcie oko na wątpliwy sens. Mamy program lokalizujący sygnał o wskazanej częstotliwości. W zależności od tego, w którym kierunku świata odbierze sygnał, może zwrócić jeden z czterech wyników: północ, południe, wschód i zachód. Normalnie, dałoby się to zrobić w następujący sposób:

if(retrievedDirection == 1) {
	println("Północ")
}
else if(retrievedDirection == 2) {
	println("Południe")
}
else if(retrievedDirection == 3) {
	println("Wschód")
}
else if(retrievedDirection == 4) {
	println("Zachód")
}

Tylko na wierzch wysuwają się dwie DUŻE wady takiego prostolinijnego użycia. Po pierwsze, uzależniamy weryfikację od cyferek "podstawiając" je pod wskazane intencje. A co, jeśli lokalizator otrzymałby hipotetyczną łatkę, która identyfikuje nie na cztery, a na osiem kierunków świata? I dodatkowo, każda cyfra określa kierunek zgodnie ze wskazówkami zegara poczynając od południa (cały czas hipotetycznie)? Wtedy nasz kod idzie pod nóż i rozbiórkę. Druga rzecz, to skąd wiedzieć która cyfra odpowiada za jaki kierunek? Chyba nie "z kątowni", jak się czasem słyszało taki suchar. Na pomoc wysuwają się klasy wyliczeniowe!

PRZYKŁAD KODU ŹRÓDŁOWEGO (ROZWIĄZANIE CZYTELNIEJSZE)

Jak wspomniałem, na front wysuwa się "enum class" w języku Kotlin. W celu ukazania pierwszej korzyści, utwórzmy sobie wewnątrz klasy wyliczeniowej cztery stałe dla kierunków świata:

enum class Directions {
	NORTH, SOUTH, EAST, WEST
}

po drugie, utworzyć sobie gdzieś na uboczu zmienną typu "klasa wyliczeniowa", o tak:

val retrievedDirection = Directions.EAST

a po trzecie, zamienić cyferki w instrukcjach warunkowych na zmienną typu wyliczeniowego:

if(retrievedDirection == Directions.NORTH) {
	println("Północ")
}
else if(retrievedDirection == Directions.SOUTH) {
	println("Południe")
}
else if(retrievedDirection == Directions.EAST) {
	println("Wschód")
}
else if(retrievedDirection == Directions.WEST) {
	println("Zachód")
}

jeszcze lepiej zamienić na instrukcję wielokrotnego wyboru:

when (retrievedDirection) {
	Directions.NORTH -> println("Północ")
	Directions.SOUTH -> println("Południe")
	Directions.EAST -> println("Wschód")
	Directions.WEST -> println("Zachód")
}

Czyż nie jest lepiej? Od tej pory, jeśli doszłyby nowe kierunki świata, to wystarczyłoby jedynie dorzucić więcej stałych wartości do klasy wyliczeniowej i z głowy! Ewentualnie jeszcze zaprogramować przypisywanie wartości na wyjściu lokalizatora do odpowiedniej stałej, ale i tak wychodzi znacznie mniej roboty, bo skończy się na modyfikacji w jednym miejscu.

ROZBUDOWA KLASY WYLICZENIOWEJ

W drodze dalszego brnięcia w temat "enum class" w języku Kotlin, możemy się przekonać że ma ona więcej do powiedzenia niż sam "enum" w języku C.

WŁAŚCIWOŚĆ

Utwórzmy sobie prostą właściwość do konstruktora, tak jak to robiliśmy w zwykłej klasie:

enum class Directions(val identifier: String)

Uwaga! Definiując właściwość w konstruktorze klasie wyliczeniowej, jesteście zobowiązani przypisać konkretną wartość KAŻDEJ Z NICH w nawiasach okrągłych (tak jak przy rzeczywistym tworzeniu kopii klasy), tuż obok każdej stałej wartości:

enum class Directions(val identifier: String) {
	NORTH("Północ"),
	SOUTH("Południe"),
	EAST("Wschód"),
	WEST("Zachód")
}

Zdradzę Wam dlaczego. Ponieważ każda stała wartość jest w rzeczywistości instancją klasy wyliczeniowej! Nie tworzy się kopii klasy wyliczeniowej, tak jak to miało miejsce przy klasie zwykłej oraz danych. Po prostu nie! Używając "enum class" w języku Kotlin w innych miejscach kodu, jedynie przypisujemy jedną z istniejących instancji, czyli stałą wartość "wplecioną" w środek bloku klasy wyliczeniowej. Dlatego taki manewr jest w pełni legalny.

Aby Wam to udowodnić, popatrzcie na kod poniżej:

var retrievedDirection = Directions.EAST

retrievedDirection = Directions.NORTH

Korzystając z operatora przypisania, podstawiamy jedną z instancji naszej klasy wyliczeniowej, którą następnie możemy potem dowolnie zmieniać na dowolną inną stałą pochodzącą z jej samej (o ile zmienna korzysta ze słowa "var"). Gwarantuję Wam, że to przejdzie.

W takiej sytuacji, możemy bez problemów odnieść do właściwości w taki sam stary sposób "kropkowy", pozbywając się tym samym całej instrukcji "when":

println(retrievedDirection.identifier)
FUNKCJA

Co powiecie na funkcję wewnątrz "enum class" w języku Kotlin? Składnia taka sama, użycie takie same, tylko sprowadzacie na siebie pułapkę składniową w postaci średnika:

enum class Directions(val identifier: String) {
	NORTH("Północ"),
	SOUTH("Południe"),
	EAST("Wschód"),
	WEST("Zachód");

	fun write() {
		println("Kierujemy się na $identifier")
	}
}

Kiedy definiujecie funkcję albo właściwość, nie zapomnijcie o postawieniu średnika zaraz po ostatniej!!! Można się na tym tanio wyłożyć.

retrievedDirection.write()

Przesłanianie? Też dozwolone, tylko po raz kolejny przypominam Wam o prawdziwej "twarzy" stałych wartości. To są instancje, zatem "override" drodzy moi, ląduje w środku każdej z nich. A to oznacza klamerki:

enum class Directions(val identifier: String) {
	NORTH("Północ") {
		override fun write() {
			println("Kierunek - $identifier")
		}
	},
	SOUTH("Południe"),
	EAST("Wschód"),
	WEST("Zachód");

	open fun write() {
		println("Kierujemy się na $identifier")
	}
}
Klasa wyliczeniowa w języku Kotlin

Klasa wyliczeniowa stanowi doskonałą broń do określania stałych nazwanych posiadających czytelny identyfikator, które automatycznie podnoszą bezpieczeństwo.


Przykład może koślawy jednak miał na celu tylko zobrazować ogrom potencjału jaki nosi za sobą "enum class" w języku Kotlin. Argumentem jaki stoi za tym, żeby się pofatygować jest to samo. Wszyscy razem: CZYTELNOŚĆ! Ale bezpieczeństwo też.

PODOBNE ARTYKUŁY