Zašto su getter i setter metode zle

Nisam namjeravao pokrenuti seriju "zlo je", ali nekoliko čitatelja zatražilo je od mene da objasnim zašto sam spomenuo kako biste trebali izbjegavati metode get / set u prošlomjesečnoj kolumni "Zašto se proširuje zlo?"

Iako su metode dobivanja / postavljanja uobičajene u Javi, one nisu posebno objektno orijentirane (OO). Zapravo mogu oštetiti održivost vašeg koda. Štoviše, prisutnost brojnih metoda dobivanja i postavljanja crvena je zastava da program nije nužno dobro dizajniran iz perspektive OO.

Ovaj članak objašnjava zašto ne biste trebali koristiti getere i postavljače (i kada ih možete koristiti) i predlaže metodologiju dizajna koja će vam pomoći da se izvučete iz mentaliteta getera / postavljača.

O prirodi dizajna

Prije nego što krenem u drugi stupac vezan za dizajn (s provokativnim naslovom, ni manje ni više), želim pojasniti nekoliko stvari.

Iznenadili su me neki komentari čitatelja koji su proizašli iz prošlomjesečne kolumne "Zašto se širi zlo?" (Pogledajte Talkback na posljednjoj stranici članka). Neki su ljudi vjerovali da tvrdim da je objektna orijentacija loša samo zato što extendsima problema, kao da su dva pojma jednaka. To zasigurno nije ono što sam mislio da kažem, pa dopustite mi da pojasnim neke meta-probleme.

Ova je kolumna i prošlomjesečni članak o dizajnu. Dizajn je po svojoj prirodi niz kompromisa. Svaki izbor ima dobru i lošu stranu, a vi birate u kontekstu općih kriterija definiranih potrebom. Međutim, dobro i zlo nisu apsolutni. Dobra odluka u jednom kontekstu može biti loša u drugom.

Ako ne razumijete obje strane problema, ne možete pametno odabrati; zapravo, ako ne razumijete sve posljedice svojih postupaka, uopće ne dizajnirate. Spotičeš se u mraku. Nije slučajno što svako poglavlje knjige Dizajn uzora Gang of Four uključuje odjeljak "Posljedice" koji opisuje kada i zašto je korištenje uzorka neprimjereno.

Navoditi da neka jezična značajka ili uobičajeni programski idiom (poput pristupa) ima problema nije isto što i reći da ih ni u kojem slučaju ne biste trebali koristiti. A to što se značajka ili idiom često koristi ne znači da biste je trebali koristiti. Neupućeni programeri pišu mnoge programe, a jednostavno zapošljavanje u tvrtki Sun Microsystems ili Microsoft ne magično poboljšava nečije sposobnosti programiranja ili dizajniranja. Java paketi sadrže puno izvrsnih kodova. Ali postoje i dijelovi tog koda. Siguran sam da je autorima neugodno priznati da su napisali.

U isto vrijeme, marketinški ili politički poticaji često guraju dizajnerske idiome. Ponekad programeri donose loše odluke, ali tvrtke žele promovirati ono što tehnologija može učiniti, pa de-naglašavaju da je način na koji to radite manje od idealnog. Oni najbolje iskorištavaju lošu situaciju. Slijedom toga, ponašate se neodgovorno kada usvojite bilo koju programsku praksu jednostavno zato što "to treba učiniti". Mnogi neuspjeli projekti Enterprise JavaBeans (EJB) dokazuju ovaj princip. Tehnologija zasnovana na EJB izvrsna je tehnologija kada se koristi na odgovarajući način, ali doslovno može srušiti tvrtku ako se koristi neprimjereno.

Moja poanta je da ne biste trebali programirati slijepo. Morate razumjeti pustoš koju mogu stvoriti značajke ili idiomi. Pritom ste u puno boljoj poziciji da odlučite želite li koristiti tu značajku ili idiom. Vaš izbor trebao bi biti informiran i pragmatičan. Svrha ovih članaka je da vam pomognu da pristupite svom programu otvorenih očiju.

Apstrakcija podataka

Temeljna zapovijed OO sustava je da objekt ne smije izlagati nijedan detalj njegove implementacije. Na taj način možete promijeniti implementaciju bez mijenjanja koda koji koristi objekt. Iz toga slijedi da u OO sustavima trebate izbjegavati getter i setter funkcije jer one uglavnom pružaju pristup detaljima implementacije.

Da biste vidjeli zašto, uzmite u obzir da getX()u vašem programu može biti 1.000 poziva metode, a svaki poziv pretpostavlja da je povratna vrijednost određene vrste. Možete getX(), na primjer, pohraniti povratnu vrijednost u lokalnu varijablu, a ta vrsta varijable mora odgovarati tipu povratne vrijednosti. Ako trebate promijeniti način na koji je objekt implementiran na takav način da se promijeni tip X, u dubokoj ste nevolji.

Ako je X bio int, ali sada mora biti long, dobit ćete 1.000 pogrešaka u kompajliranju. Ako problem pogrešno riješite lijevanjem povratne vrijednosti int, kod će se čisto sastaviti, ali neće raditi. (Vrijednost povrata može biti skraćena.) Morate izmijeniti kôd koji okružuje svaki od tih 1.000 poziva kako biste nadoknadili promjenu. Sigurno ne želim raditi toliko posla.

Jedno od osnovnih načela OO sustava je apstrakcija podataka . Trebali biste u potpunosti sakriti način na koji objekt implementira rukovatelj porukama od ostatka programa. To je jedan od razloga zašto bi trebale biti sve vaše varijable instance (nestalna polja klase) private.

Ako napravite varijablu instance public, tada ne možete promijeniti polje kako se klasa razvija s vremenom jer biste razbili vanjski kôd koji koristi polje. Ne želite pretraživati ​​1.000 upotreba klase samo zato što je promijenite.

Ovaj princip skrivanja implementacije dovodi do dobrog ispitivanja kvalitete OO sustava: možete li napraviti velike promjene u definiciji klase - čak i izbaciti cijelu stvar i zamijeniti je potpuno drugačijom implementacijom - bez utjecaja na bilo koji kôd koji to koristi predmeti klase? Ovakva modularizacija središnja je pretpostavka orijentacije objekta i znatno olakšava održavanje. Bez skrivanja implementacije, nema smisla koristiti ostale OO značajke.

Metode getera i postavljača (poznate i kao pristupnici) opasne su iz istog razloga kao što su i publicpolja opasna: pružaju vanjski pristup detaljima implementacije. Što ako trebate promijeniti vrstu pristupnog polja? Također morate promijeniti tip povrata pristupnika. Ovu povratnu vrijednost koristite na brojnim mjestima, tako da također morate promijeniti sav taj kôd. Želim ograničiti učinke promjene na jednu definiciju klase. Ne želim da se uvuku u cijeli program.

Budući da pristupnici krše načelo enkapsulacije, možete s razlogom tvrditi da sustav koji intenzivno ili neprimjereno koristi pristupnike jednostavno nije objektno orijentiran. Ako prođete kroz postupak dizajniranja, za razliku od pukog kodiranja, u programu ćete teško naći pristupnike. Proces je važan. Moram reći više o ovom pitanju na kraju članka.

Nedostatak metoda dobivanja / postavljanja ne znači da neki podaci ne prolaze kroz sustav. Bez obzira na to, najbolje je što je više moguće smanjiti kretanje podataka. Moje je iskustvo da je održivost obrnuto proporcionalna količini podataka koja se kreće između objekata. Iako još možda ne vidite kako, zapravo možete eliminirati većinu ovog kretanja podataka.

Pažljivim dizajniranjem i fokusiranjem na ono što morate učiniti, a ne na to kako ćete to učiniti, eliminirate veliku većinu getter / setter metoda u svom programu. Ne tražite podatke potrebne za obavljanje posla; zamolite objekt koji ima informacije da obavi posao umjesto vas.Većina pristupnika pronalazi svoj put u kodu jer dizajneri nisu razmišljali o dinamičkom modelu: objektima vremena izvođenja i porukama koje jedni drugima šalju kako bi obavili posao. Oni započinju (pogrešno) dizajniranjem hijerarhije klasa, a zatim pokušavaju te klase ubaciti u dinamički model. Ovaj pristup nikad ne djeluje. Da biste izgradili statički model, morate otkriti odnose između klasa, a ti odnosi točno odgovaraju protoku poruka. Asocijacija postoji između dvije klase samo kada objekti jedne klase šalju poruke objektima druge. Glavna svrha statičnog modela je hvatanje ovih podataka o pridruživanju dok dinamički modelirate.

Bez jasno definiranog dinamičkog modela, samo nagađate kako ćete koristiti objekte klase. Slijedom toga, metode pristupa često završavaju u modelu jer morate pružiti što više pristupa jer ne možete predvidjeti hoće li vam trebati. Ovakva strategija dizajniranja-pogađanja u najboljem je slučaju neučinkovita. Gubite vrijeme na pisanje beskorisnih metoda (ili dodavanja nepotrebnih mogućnosti nastavi).

Pristupnici također snagom navike završe u dizajnu. Kad proceduralni programeri usvoje Javu, oni obično počnu graditi poznati kod. Proceduralni jezici nemaju nastavu, ali imaju C struct(mislite: klasa bez metoda). Stoga se čini prirodnim oponašati structizgradnju gradeći definicije klasa gotovo bez ikakvih metoda i samo publicpolja. Ovi proceduralni programeri negdje su pročitali da polja trebaju biti private, pa izrađuju polja privatei opskrbljuju publicmetode pristupa. Ali oni su samo zakomplicirali pristup javnosti. Sigurno nisu sustav učinili objektno orijentiranim.

Nacrtaj sebe

Jedna od posljedica pune enkapsulacije polja je u konstrukciji korisničkog sučelja (UI). Ako ne možete koristiti pristupnike, ne možete imati klasu graditelja korisničkog sučelja koja poziva getAttribute()metodu. Umjesto toga, klase imaju elemente poput drawYourself(...)metoda.

getIdentity()Metoda također može raditi, naravno, pod uvjetom da se vraća objekt koji implementira Identitysučelje. Ovo sučelje mora sadržavati metodu drawYourself()(ili daj mi- JComponentto-predstavlja-tvoj-identitet). Iako getIdentityzapočinje s "get", to nije dodatak jer ne vraća samo polje. Vraća složeni objekt koji ima razumno ponašanje. Čak i kad imam Identitypredmet, još uvijek nemam pojma kako je identitet predstavljen iznutra.

Naravno, drawYourself()strategija znači da sam (dah!) Stavio UI kod u poslovnu logiku. Razmotrite što se događa kada se zahtjevi korisničkog sučelja promijene. Recimo da želim atribut predstaviti na potpuno drugačiji način. Danas je "identitet" ime; sutra je to ime i matični broj; dan nakon toga to je ime, ID broj i slika. Ograničavam opseg ovih promjena na jedno mjesto u kodu. Ako imam JComponentklasu daj mi- to-predstavlja-tvoj-identitet, tada sam izolirao način na koji su identiteti predstavljeni od ostatka sustava.

Imajte na umu da zapravo nisam stavio nijedan UI kôd u poslovnu logiku. Sloj korisničkog sučelja napisao sam u smislu AWT (Abstract Window Toolkit) ili Swing, koji su obojica slojevi apstrakcije. Stvarni UI kod nalazi se u implementaciji AWT / Swing. To je cijela poanta apstraktnog sloja - da izolirate svoju poslovnu logiku od mehanike podsustava. Lako se mogu prebaciti u drugo grafičko okruženje bez promjene koda, tako da je jedini problem malo nereda. Tu nered možete lako ukloniti premještanjem svih UI kodova u unutarnju klasu (ili pomoću uzorka dizajna fasade).

JavaBeans

Mogli biste prigovoriti rekavši: "Ali što je s JavaBeansom?" Što s njima? Svakako možete graditi JavaBeans bez getera i postavljača. BeanCustomizer, BeanInfoI BeanDescriptorklase sve postoje za točno tu svrhu. Dizajneri specifikacija JavaBean bacili su u sliku idiom getter / setter jer su smatrali da će to biti jednostavan način za brzo stvaranje graha - nešto što možete učiniti dok učite kako to ispravno raditi. Nažalost, to nitko nije učinio.

Pristupnici su stvoreni samo kao način označavanja određenih svojstava kako bi ih program za izgradnju korisničkog sučelja ili ekvivalent mogao identificirati. Ne biste trebali sami pozivati ​​ove metode. Postoje za upotrebu automatiziranog alata. Ovaj alat koristi API-je introspekcije u Classklasi kako bi pronašao metode i ekstrapolirao postojanje određenih svojstava iz naziva metoda. U praksi ovaj idiom zasnovan na introspekciji nije uspio. To je kod učinilo previše složenim i proceduralnim. Programeri koji ne razumiju apstrakciju podataka zapravo pozivaju pristupnike i kao posljedicu toga, kod je manje održiv. Iz tog će razloga značajka metapodataka biti ugrađena u Javu 1.5 (trebao bi biti postavljen sredinom 2004.). Dakle, umjesto:

privatno int vlasništvo; javni int getProperty () {return svojstvo; } javna void setProperty (int vrijednost} {svojstvo = vrijednost;}

Moći ćete koristiti nešto poput:

private @property int svojstvo; 

Alat za izradu korisničkog sučelja ili ekvivalent upotrijebit će API-je introspekcije za pronalaženje svojstava, umjesto da ispituje nazive metoda i iz imena zaključuje postojanje svojstva. Stoga niti jedan runtime accessor ne oštećuje vaš kôd.

Kada je dodatak u redu?

Prvo, kao što sam ranije razgovarao, u redu je da metoda vrati objekt u smislu sučelja koje objekt implementira jer vas to sučelje izolira od promjena u izvedbenoj klasi. Ova vrsta metode (koja vraća referencu sučelja) zapravo nije "getter" u smislu metode koja samo pruža pristup polju. Ako promijenite internu implementaciju davatelja usluge, samo mijenjate definiciju vraćenog objekta kako bi se prilagodila promjenama. I dalje zaštitite vanjski kôd koji koristi objekt putem njegovog sučelja.