Blog Cargo culty v Jave: objektovo-orientované programovanie

Cargo culty v Jave: objektovo-orientované programovanie

Cargo cult

V južnom Pacifiku na ostrove Tanna žije primitívny kmeň, ktorý uctieva američana Johna Fruma. Aby ho k sebe privolali, postavili si lietadlá a obrovské vysielače. Vyzbrojili sa bajonetmi a pochodujú v šíkoch pod americkou zástavou. Presne takto to odpozorovali, keď bol John Frum na ostrove Tanna: stačí pochodovať s bajonetmi, otáčať s vysielačmi a mať pri sebe lietadlá. A stane sa zázrak! Pristane lietadlo naložené potravinami, oblečením a iným tovarom (cargo).

John však neprichádza. Možno preto, lebo lietadlá, vysielače a bajonety sú z bambusu a trávy. Takže členovia cargo cultu robia síce všetko správne, dokonale napodobňujú činnosť amerických jednotiek, ktoré kedysi na ostrove žili, ale výsledok sa nedostaví.

Mechanické napodobňovanie postupov bez porozumenia robí z programátorov členov cargo-cultu. Koľko bambusu a trávy obsahuje váš zdrojový kód?

Cargo cult objektovo-orientovaného programovania

„Každá trieda musí mať všetky svoje atribúty „private“ a k ním vygenerované get/set metódy.“

Dodržiavanie tejto zásady bez toho, aby programátori rozumeli jej dôvodom, robí z programátorov členov cargo cultu.
V niektorých situáciách je výhodnejšie používať dátové štruktúry namiesto tried. Pre ne však neplatia pravidlá o zapúzdrenosti atribútov v triedach.

Vysvetlením je objektovo-orientované programovanie

Základom objektovo-orientovaného prístupu je zodpovednosť. Aby trieda udržala zodpovednosť za svoje dáta, musí mať prístup k nim pod kontrolou – čiže dáta musia byť zapúzdrené. Preto je nutné definovať atribúty ako privátne.
Ak trieda poskytuje ku každému atribútu aj get/set metódy, k žiadnej zapúzdrenosti nedochádza. Samotná trieda potom nie je v zmysle dizajnu trieda, ale dátová štruktúra.

Trieda verzus dátová štruktúra

Trieda (class):

  • primárne definovaná cez zodpovednosť
  • obsahuje dáta, ale aj chovanie, ktorým implementuje zodpovednosť

Dátová štruktúra (record)

  • primárne definovaná cez ukladané dáta
  • obsahuje iba atribúty

Primárny rozdiel medzi triedou a dátovou štruktúrou spočíva v spôsobe návrhu. Ide o rozdiel, ktorý nemusí byť na prvý pohľad zrejmý. Mnohé triedy z objektovo-orientovaného dizajnu môžu v konečnom dôsledku skončiť s veľmi podobným zdrojovým kódom ako pri návrhu dátových štruktúr. Obvykle však ide o okrajové triedy – číselníky a pomocné pojmy.

Praktická ukážka – Osoba (Person)

Osoba má v doméne tieto atribúty:

  • registrationNumber – rodné číslo
  • birthDate – dátum narodenia
  • givenName – krstné meno
  • familyName – priezvisko

Pre účely identifikácie osoby slúži jednoznačný umelý identifikátor „personId“ typu Long.

Person ako dátová štruktúra (cargo cult)

import java.time.LocalDate;

public class Person {
	
	private Long personId;
	// rodne cislo
	private String registrationNumber;
	private LocalDate birthDate;
	private String givenName;
	private String familyName;
	
	//getters
	public Long getPersonId() {				return personId;	}
	public String getRegistrationNumber() {		return registrationNumber;}	
	public LocalDate getBirthDate() {			return birthDate;	}
	public String getGivenName() {				return givenName;	}
	public String getFamilyName() {				return familyName;	}

	// setters
	public void setBirthDate(LocalDate birthDate) {	this.birthDate = birthDate;}
	public void setGivenName(String givenName) {		this.givenName = givenName;}
	public void setFamilyName(String familyName) {	this.familyName=familyName;}
	public void setRegistrationNumber(String registrationNumber) {
		this.registrationNumber = registrationNumber;
	}	
}

(Kód je zámerne stlačený kvôli priestoru.)
Dátová štruktúra má všetky atribúty prístupné cez get/set metódy. Kód obsahuje veľa nadbytočného balastu („boilerplate code“) – sú to práve get/set metódy automaticky generované nástrojom. Z pohľadu dizajnu je lepšie dátovú štruktúru v Jave písať naozaj ako dátovú štruktúru, t.j.:

import java.time.LocalDate;

public class Person {
	
	public Long personId;
	// rodne cislo
	public String registrationNumber;
	public LocalDate birthDate;
	public String givenName;
	public String familyName;
		
}

Herézia! Atribúty triedy nesmú byť „public“! Domorodci z ostrova Tannu sú zdesení!
Presne tak: atribúty triedy musia byť private. Ale ak z pohľadu dizajnu nie je Person navrhnutá ako trieda, ale ako dátová štruktúra, tak sa na ňu pravidlá pre triedy nevzťahujú. A z pohľadu zrozumiteľnosti je tento kratší zápis bez get/set metód lepšie čitateľný. Z pohľadu používania sa nijako nelíši od triedy Person, ktorá má síce všetky atribúty private, ale zároveň prístupné cez get/set metódy.

Person ako trieda

import java.time.LocalDate;

public final class Person {
	
	private final PersonId personId;
	// rodne cislo
	private final RegistrationNumber registrationNumber;
	private LocalDate birthDate;
	private String givenName;
	private String familyName;
	
	public Person(PersonId personId, RegistrationNumber registrationNumber) {
		this.personId = personId;
		this.registrationNumber = registrationNumber;
		this.birthDate = registrationNumber.date();
	}

	public PersonId getPersonId() {		return personId;	}

	public String getRegistrationNumber() {
		return registrationNumber.text();
	}

	public String getGivenName() {		return givenName;	}
	public String getFamilyName() {		return familyName;	}
	public LocalDate getBirthDate() {	return birthDate;	}
	public void setGivenName(String givenName) {	this.givenName = givenName;}
	public void setFamilyName(String familyName) {this.familyName = familyName;	}
	
}

Pri objektovo-orientovanom dizajne si dizajnér neustále kladie otázku: „Čo je zodpovednosťou danej triedy?“ Triedy, ktoré predstavujú objekty z reálneho sveta (ako je Person), obvykle majú jednoduchú zodpovednosť: zabezpečiť dátovú konzistenciu. Táto zodpovednosť je v prípade triedy Person rozbitá na toto chovanie:

  1. Nesmie existovať objekt typu Person, ak nemá nastavený identifikátor a rodné číslo
  2. Dátum narodenia osoby musí zodpovedať nastavenému rodnému číslu
  3. Rodné číslo osoby sa nesmie zmeniť.
  4. Z bodov 1-3 vyplýva, že ani dátum narodenia osoby sa nesmie zmeniť.

Rozbíjanie zodpovednosti v objektovo-orientovanom dizajne viedlo k definovaniu triedy RegistrationNumber, ktorá v doméne reprezentuje rodné číslo. Ako vidno z návrhu, má metódy date() a text() na vrátenie dátumu z rodného čísla.

Triedu Person by bolo možné navrhnúť tak ako v prípade dátovej štruktúry – čiže s atribútom registrationNumber typu String. Tým pádom by trieda Person na seba prevzala zodpovednosti súvisiace s vyhodnocovaním rodného čísla – t.j. načítanie dátumu.

Lenže: single responsibility principle sa v dobrom dizajne neporušuje!

Formálny pohľad na rozdiely

Hlavný rozdiel medzi dátovou štruktúrou a triedou spočíva v prístupe k ich dizajnu. Rozdielny prístup v dizajne však vedie aj k formálnym odlišnostiam:

  1. Trieda má obvykle konštruktor, ktorý definuje povinné atribúty, bez ktorých nemá zmysel inštanciu triedy vytvárať.
  2. Trieda obvykle nemá všetky set-metódy, lebo niektoré atribúty sa počas životného cyklu objektu nesmú zmeniť. Povinné atribúty sú častokrát nastavené iba raz na začiatku.
  3. Trieda môže mať set-metódy doplnené o ďalšie vstupné parametre – napr. o používateľa, ktorý atribút zmenil.
  4. Trieda častokrát nemá ani všetky get-metódy, lebo niektoré atribúty slúžia iba na interné spracovanie logiky vo vnútri triedy a nie sú publikované.
  5. Niekedy majú get/set metódy v triede nastavenú nižšiu viditeľnosť, t.j. nie sú public.
  6. Niekedy sú návratové typy get-metód iné než je interný typ daného atribútu v triede (pozri registrationNumber).

Pre get/set metódy teda platí, že zatiaľ čo pri dátových štruktúrach sú tieto metódy automaticky generované (resp. neexistujú), pri objektovom dizajne je každá get/set metóda dôsledkom požiadavky v doméne, t.j. dôsledkom implementácie use-case-u alebo user-story. A signatúra metódy nemusí mechanicky zodpovedať typu atribútu.

Dátové štruktúry

Dátové štruktúry majú v programovaní svoje miesto. Nemá zmysel programovať každú množinu atribútov automaticky ako triedu. Príkladom používania dátových štruktúr sú tzv. „Data Transfer Object“. Ide o dátové štruktúry, ktoré sú súčasťou API pri programovaní REST-ových alebo SOAP-ových služieb a ich účelom je zabezpečiť jednoduchú konverziu dát na JSON alebo XML. Tieto dátové štruktúry je vhodné implementovať pomocou public atribútov, čiže bez get/set metód.

Používate JavaBeans?

Písanie get/set metód zaviedol JavaBeans štandard, ktorý sa dnes už veľmi nepoužíva. Práve get/set metódy je jediné, čo z tohto štandardu zostalo. Štandard definoval pravidlá, ktoré má programátor dodržiavať, ak píše JavaBean. Dnes sa takmer všetky triedy automaticky píšu ako JavaBean-y bez toho, aby programátori tušili, že to robia.
Takže tu je ten bambus …

 



Sumár

Používanie get/set metód je najrozšírenejší cargo cult v Jave. Ich použitie samo o sebe nie je zlé ani chybné, pokiaľ programátori rozumejú jeho účelu:

  • Pri triedach v zmysle objektovo-orientovaného dizajnu je každá get/set metóda dôsledkom používateľskej požiadavky. Tieto metódy preto nie je možné automaticky generovať pre všetky atribúty naraz.
  • Pri dátových štruktúrach je prípustné a vhodné mať všetky atribúty verejne prístupné.

Dobrý článok? Chceš dostávať ďalšie?

Už viac ako 6 200 ITečkárov dostáva správy e-mailom. Nemusíš sa báť, nie každé ráno. Len občasne.

Súhlasím so spracovaním mojich osobných údajov. ( Viac informácií. )

Tvoj email neposkytneme 3tím stranám. Posielame naňho len informácie z robime.it. Kedykoľvek sa môžeš odhlásiť.

DevOps Enginner / Online Bezpečnosť

Pre IT spoločnosť, ktorá sa venuje online bezpečnosti hľadáme do tímu DevOps Medior Engineer. Spoločnosť má 20-členný tím s...

Senior Java Consultant

Hľadáš, kam by si sa posunul? Možno ťa táto práca zaujme. Ide o Senior JAVA konzultanta v nadnárodnej IT...

Ruby on Rails Junior-Medior Developer / REMOTE

Je tvoj život príliš v “koľajniciach”? Urob si odbočku a skús sa pridať do tímu Ruby on Rails developerov.Ide...

JS Sencha Senior Developer / Homeoffice

Máš rád svoju slobodu, pracuješ na projektoch, ideálne remote? Pre medzinárodnú softwarovú spoločnosť hľadáme JS Sencha Developera.Po dvoch týždňoch...

Junior Angular Developer / REMOTE

Pre nášho partnera hľadáme ITčkára na pozíciu Angular Developer. Spoločnosť sa venuje vývoju platformy pre firmy na znižovanie nákladov...

Frontend Medior Developer

Medzinárodná IT spoločnosť, ktorá vyvíja finančný softvér.Práca je v Bratislave na TPP. Odmena sa pohybuje od 2000+ EUR.Tvoje úlohy...

.NET Core Medior Developer / REMOTE

Svoju budúcnosť máš vo svojich rukách, skús to teraz ako .NET Core developer. Ide o startup projekt, ktorý vyvíja...

GIRL ́S DAY 2020: Už po siedmy raz môžu stredoškoláčky nahliadnuť do IT firiem po celom Slovensku.

Občianske združenie Aj Ty v IT už siedmy rok prepája IT firmy a organizácie so stredoškoláčkami po...

DIGITALEUROPE: Na digitalizáciu pôjde z plánu obnovy až pätina prostriedkov. Ako zareaguje Slovensko?

Výzva v podobe zníženej globálnej konkurencieschopnosti, ako aj kríza spojená s pandémiou postavili Európu...

OpenSlava 2020 tentokrát online!

OpenSlava 2020 je 8. ročník úspešnej konferencie pre IT komunitu na Slovensku...

Čítaj ďalej:

Dobrý článok? Chceš dostávať ďalšie?

Už viac ako 6 200 ITečkárov dostáva správy e-mailom. Nemusíš sa báť, nie každé ráno. Len občasne.

Súhlasím so spracovaním mojich osobných údajov. ( Viac informácií. )

Tvoj email neposkytneme 3tím stranám. Posielame naňho len informácie z robime.it. Kedykoľvek sa môžeš odhlásiť.