Ohjelmointi

Miksi getter- ja setter-menetelmät ovat pahoja

En aikonut aloittaa "on paha" -sarjaa, mutta useat lukijat pyysivät minua selittämään, miksi mainitsin, että sinun tulisi välttää get / set-menetelmiä viime kuukauden sarakkeessa "Miksi laajentaa pahaa".

Vaikka getter / setter-menetelmät ovat tavallisia Java-ohjelmassa, ne eivät ole erityisen olio-suuntautuneita (OO). Itse asiassa ne voivat vahingoittaa koodisi ylläpidettävyyttä. Lisäksi lukuisten getter- ja setter-menetelmien läsnäolo on punainen lippu, että ohjelmaa ei välttämättä ole suunniteltu hyvin OO: n näkökulmasta.

Tässä artikkelissa selitetään, miksi sinun ei pitäisi käyttää ketjuja ja asetinlaitteita (ja milloin voit käyttää niitä) ja ehdotetaan suunnittelumenetelmää, joka auttaa sinua eroon getter / setter-mentaliteetista.

Suunnittelun luonteesta

Ennen kuin aloitan toiseen suunnitteluun liittyvään sarakkeeseen (provosoivalla otsikolla, ei vähemmän), haluan selventää muutamia asioita.

Jotkut lukijoiden kommentit hämmästyttivät minua viime kuukauden sarakkeesta "Miksi laajenee pahaa" (katso Talkback artikkelin viimeisellä sivulla). Jotkut ihmiset uskoivat, että väitin, että esineiden suuntaus on huono yksinkertaisesti siksi ulottuu on ongelmia, ikään kuin nämä kaksi käsitettä olisivat samanlaisia. Se ei todellakaan ole mitä minä ajattelin Sanoin, joten anna minun selventää joitain metakysymyksiä.

Tämä sarake ja viime kuukauden artikkeli käsittelevät suunnittelua. Suunnittelu on luonteeltaan sarja kompromisseja. Jokaisella valinnalla on hyvä ja huono puoli, ja teet valintasi välttämättömyyden määrittelemien yleisten kriteerien yhteydessä. Hyvä ja huono eivät kuitenkaan ole ehdotonta. Hyvä päätös yhdessä tilanteessa saattaa olla huono toisessa.

Jos et ymmärrä asian molempia puolia, et voi tehdä älykästä valintaa; itse asiassa, jos et ymmärrä kaikkia tekojesi seurauksia, et suunnittele lainkaan. Sinä kompastut pimeässä. Se ei ole sattuma, että jokainen luku Neljän joukossa Suunnittelumalleja kirja sisältää "Seuraus" -osion, joka kuvaa, milloin ja miksi mallin käyttäminen on sopimatonta.

Sen toteaminen, että jollakin kieliominaisuudella tai tavallisella ohjelmointikielellä (kuten lisenssilaitteilla) on ongelmia, ei ole sama asia kuin sanomalla, ettet koskaan saa käyttää niitä missään olosuhteissa. Ja se, että ominaisuutta tai idiomia käytetään yleisesti, ei tarkoita sinua pitäisi käytä sitä joko. Tietämättömät ohjelmoijat kirjoittavat monia ohjelmia, ja pelkkä Sun Microsystemsin tai Microsoftin palveluksessa oleminen ei paranna taianomaisesti jonkun ohjelmointi- tai suunnittelukykyjä. Java-paketit sisältävät paljon hienoa koodia. Mutta on myös osa koodia, olen varma, että kirjoittajat ovat hämmentyneitä myöntämään kirjoittaneensa.

Samalla tavoin markkinointi- tai poliittiset kannustimet ajavat usein muotoilua. Joskus ohjelmoijat tekevät huonoja päätöksiä, mutta yritykset haluavat mainostaa tekniikan mahdollisuuksia, joten he korostavat, että tapa, jolla teet sen, on vähemmän kuin ihanteellinen. He tekevät parhaan huonosta tilanteesta. Näin ollen teet vastuuttomasti käyttäessäsi mitään ohjelmointikäytäntöä yksinkertaisesti siksi, että "sinun on tarkoitus tehdä asioita". Monet epäonnistuneet Enterprise JavaBeans (EJB) -projektit todistavat tämän periaatteen. EJB-pohjainen tekniikka on loistavaa tekniikkaa, kun sitä käytetään asianmukaisesti, mutta se voi kirjaimellisesti kaataa yrityksen, jos sitä käytetään väärin.

Haluaisin sanoa, että sinun ei pitäisi ohjelmoida sokeasti. Sinun on ymmärrettävä, minkä tuhon ominaisuus tai idiomi voi aiheuttaa. Tällöin sinulla on paljon parempi asema päättää, pitäisikö sinun käyttää kyseistä ominaisuutta vai idioomaa. Valintojesi tulisi olla sekä tietoon perustuvia että käytännöllisiä. Näiden artikkeleiden tarkoituksena on auttaa sinua lähestymään ohjelmointiasi avoimilla silmillä.

Tiedonotto

OO-järjestelmien perusedellytys on, että objekti ei saa paljastaa mitään sen toteutuksen yksityiskohtia. Tällä tavoin voit muuttaa toteutusta muuttamatta objektia käyttävää koodia. Tästä seuraa, että OO-järjestelmissä sinun tulisi välttää parempia ja parempia toimintoja, koska ne tarjoavat pääsyn pääsyyn toteutustietoihin.

Jos haluat nähdä miksi, ota huomioon, että a getX () menetelmä, ja jokainen puhelu olettaa, että paluuarvo on tietyntyyppinen. Voit tallentaa getX ()palautusarvon esimerkiksi paikallisessa muuttujassa, ja kyseisen muuttujatyypin on vastattava palautusarvotyyppiä. Jos sinun on muutettava objektin toteutustapaa siten, että X: n tyyppi muuttuu, olet syvissä vaikeuksissa.

Jos X olisi int, mutta nyt sen on oltava a pitkä, saat 1000 kääntövirhettä. Jos korjaat ongelman väärin heittämällä palautusarvon kohtaan int, koodi kääntyy siististi, mutta se ei toimi. (Palautusarvo saattaa olla katkaistu.) Muutosta kompensoidaksesi sinun on muokattava kutakin näistä 1000 puhelusta ympäröivää koodia. En todellakaan halua tehdä niin paljon työtä.

Yksi OO-järjestelmien perusperiaate on tiedonotto. Sinun tulisi piilottaa kokonaan tapa, jolla objekti toteuttaa viestinkäsittelijän, muusta ohjelmasta. Se on yksi syy, miksi kaikkien instanssimuuttujiesi (luokan ei-vakio-kentät) pitäisi olla yksityinen.

Jos teet instanssimuuttujan julkinen, niin et voi muuttaa kenttää luokan kehittyessä ajan myötä, koska rikkisit kenttää käyttävän ulkoisen koodin. Et halua hakea luokan 1000 käyttötarkoitusta yksinkertaisesti siksi, että muutat luokkaa.

Tämä toteutuksen piiloperiaate johtaa hyvään happotestiin OO-järjestelmän laadusta: Voitko tehdä mittavia muutoksia luokan määritelmään - jopa heittää koko asia pois ja korvata sen täysin erilaisella toteutuksella - vaikuttamatta mihinkään sitä käyttävään koodiin luokan kohteet? Tällainen modularisointi on olennaisen keskeinen lähtökohta kohteelle ja helpottaa ylläpitoa. Ilman toteutuksen piilottamista ei ole mitään järkeä käyttää muita OO-ominaisuuksia.

Getter- ja setter-menetelmät (tunnetaan myös nimellä accessors) ovat vaarallisia samasta syystä julkinen kentät ovat vaarallisia: Ne tarjoavat ulkoisen pääsyn toteutuksen yksityiskohtiin. Entä jos sinun on vaihdettava käytetyn kentän tyyppi? Sinun on myös vaihdettava pääsyn palautustyyppi. Käytät tätä palautusarvoa monissa paikoissa, joten sinun on myös muutettava kaikki koodi. Haluan rajoittaa muutoksen vaikutukset yhteen luokan määritelmään. En halua heidän rippautuvan koko ohjelmaan.

Koska käyttöoikeudet rikkovat kapselointiperiaatetta, voit perustellusti väittää, että järjestelmä, joka käyttää voimakkaasti tai epäasianmukaisesti käyttöoikeuksia, ei yksinkertaisesti ole olio-suuntautunut. Jos käydään läpi suunnitteluprosessi, toisin kuin vain koodaus, ohjelmastasi löytyy tuskin yhtään pääsyohjelmaa. Prosessi on tärkeä. Minulla on enemmän sanottavaa tästä aiheesta artikkelin lopussa.

Getter / setter-menetelmien puute ei tarkoita sitä, että osa tiedoista ei virtaa järjestelmän läpi. On kuitenkin parasta minimoida tiedonsiirto mahdollisimman paljon. Kokemukseni mukaan ylläpidettävyys on kääntäen verrannollinen objektien välillä liikkuvan datan määrään. Vaikka et ehkä vielä näe, kuinka vielä, voit itse poistaa suurimman osan tästä dataliikkeestä.

Suunnittelemalla huolellisesti ja keskittymällä siihen, mitä sinun on tehtävä sen sijaan, miten teet sen, poistat valtaosan getter / setter-menetelmistä ohjelmasi. Älä kysy tietoja, joita tarvitset työn tekemiseen; pyydä esineitä, joilla on tietoja, tekemään työn puolestasi. Suurin osa käyttäjistä löytää tiensä koodiin, koska suunnittelijat eivät ajatelleet dynaamista mallia: ajonaikaisia ​​objekteja ja viestejä, jotka he lähettävät toisilleen työn tekemistä varten. He aloittavat (väärin) suunnittelemalla luokkahierarkian ja yrittävät sitten tuoda nämä luokat dynaamiseen malliin. Tämä lähestymistapa ei koskaan toimi. Staattisen mallin rakentamiseksi sinun on löydettävä luokkien väliset suhteet, ja nämä suhteet vastaavat tarkalleen viestivirtaa. Kahden luokan välillä on yhteys vain, kun yhden luokan objektit lähettävät viestejä toisen objektille. Staattisen mallin päätarkoitus on kaapata nämä assosiaatiotiedot mallinnettaessa dynaamisesti.

Ilman selkeästi määriteltyä dynaamista mallia olet vain arvaamassa, miten aiot käyttää luokan objekteja. Tästä syystä käyttöoikeusmenetelmät sulkeutuvat usein malliin, koska sinun on tarjottava mahdollisimman paljon käyttöoikeuksia, koska et voi ennustaa tarvitsetko sitä vai ei. Tällainen suunnittelu-arvaus -strategia on parhaimmillaan tehotonta. Tuhlaat aikaa hyödyttömien menetelmien kirjoittamiseen (tai tarpeettomien ominaisuuksien lisäämiseen luokkiin).

Accessors päätyvät myös malleihin tapana. Kun menettelyohjelmoijat ottavat Java-palvelimen käyttöön, heillä on tapana aloittaa rakentamalla tuttu koodi. Menettelykielillä ei ole luokkia, mutta niillä on C rakenne (ajatella: luokka ilman menetelmiä). Vaikuttaa siis luonnolliselta jäljitellä a rakenne rakentamalla luokan määritelmiä käytännössä ilman menetelmiä eikä mitään muuta julkinen kentät. Nämä menettelyohjelmoijat lukivat jossain, että kenttien pitäisi olla yksityinen, mutta he tekevät pellot yksityinen ja toimitus julkinen lisävarustemenetelmät. Mutta ne ovat vain monimutkaistaneet yleisön saatavuutta. He eivät todellakaan ole tehneet järjestelmästä olio-suuntautuneita.

Piirrä itseäsi

Yksi täyden kentän kapseloinnin seuraus on käyttöliittymän (UI) rakentamisessa. Jos et voi käyttää lisäosia, et voi antaa käyttöliittymän rakennustyökaluluokalle kutsua getAttribute () menetelmä. Sen sijaan luokissa on elementtejä, kuten Piirrä itsesi(...) menetelmiä.

A getIdentity () menetelmä voi tietysti toimia myös, jos se palauttaa objektin, joka toteuttaa Identiteetti käyttöliittymä. Tämän käyttöliittymän on sisällettävä a Piirrä itsesi() (tai anna-anna-a-JKomponentti- joka edustaa identiteettiäsi) -menetelmä. Vaikka getIdentity alkaa "get", se ei ole käyttöoikeus, koska se ei vain palauta kenttää. Se palauttaa monimutkaisen objektin, jolla on kohtuullinen käyttäytyminen. Silloinkin kun minulla on Identiteetti esine, minulla ei vieläkään ole aavistustakaan kuinka identiteettiä edustetaan sisäisesti.

Tietysti a Piirrä itsesi() strategia tarkoittaa, että laitan käyttöliittymäkoodin liiketoimintalogiikkaan (ahne!) Mieti, mitä tapahtuu, kun käyttöliittymän vaatimukset muuttuvat. Oletetaan, että haluan edustaa attribuuttia täysin eri tavalla. Nykyään "identiteetti" on nimi; huomenna se on nimi ja henkilötunnus; seuraavana päivänä se on nimi, henkilötunnus ja kuva. Rajoitan näiden muutosten laajuuden yhteen paikkaan koodissa. Jos minulla on antaa minulle-a-JKomponentti- joka edustaa identiteettisi luokkaa, olen eristänyt identiteettien edustustavan muusta järjestelmästä.

Muista, että en ole itse asiassa laittanut mitään käyttöliittymäkoodia liiketoimintalogiikkaan. Olen kirjoittanut käyttöliittymäkerroksen AWT (Abstract Window Toolkit) tai Swing-muodossa, jotka ovat molemmat abstraktiokerroksia. Varsinainen käyttöliittymäkoodi on AWT / Swing-toteutuksessa. Se on abstraktiokerroksen koko piste - eristääksesi liiketoimintalogiikkasi osajärjestelmän mekaniikasta. Pystyn helposti siirtämään toiseen graafiseen ympäristöön muuttamatta koodia, joten ainoa ongelma on pieni sotku. Voit poistaa tämän sekaannuksen helposti siirtämällä kaiken käyttöliittymäkoodin sisempään luokkaan (tai käyttämällä julkisivukuviota).

Java-pavut

Voit vastustaa sanomalla "Mutta entä JavaBeans?" Mitä niistä? Voit varmasti rakentaa JavaBean-laitteita ilman katkaisijoita ja asettimia. BeanCustomizer, BeanInfoja BeanDescriptor kaikki luokat ovat olemassa juuri tähän tarkoitukseen. JavaBean-suunnittelijat heittivät getter / setter-idion kuvaan, koska heidän mielestään se olisi helppo tapa tehdä nopeasti papu - jotain, mitä voit tehdä, kun opit tekemään sen oikein. Valitettavasti kukaan ei tehnyt niin.

Lisälaitteet luotiin vain tapana merkitä tiettyjä ominaisuuksia, jotta UI-rakennusohjelma tai vastaava voisi tunnistaa ne. Sinun ei pitäisi kutsua näitä menetelmiä itse. Ne ovat olemassa käytettäväksi automatisoidulle työkalulle. Tämä työkalu käyttää sisäisen tarkastelun sovellusliittymiä Luokka luokan löytää menetelmät ja ekstrapoloida tiettyjen ominaisuuksien olemassaolo menetelmien nimistä. Käytännössä tämä itsetutkiskeluun perustuva idiomi ei ole onnistunut. Se on tehnyt koodista aivan liian monimutkaisen ja menettelyllisen. Ohjelmoijat, jotka eivät ymmärrä tiedonkeruua, todella kutsuvat käyttöoikeuksien haltijoita, minkä seurauksena koodi on vähemmän ylläpidettävä. Tästä syystä metatieto-ominaisuus sisällytetään Java 1.5: een (erääntyy vuoden 2004 puolivälissä). Joten sen sijaan, että:

yksityinen omaisuus; public int getProperty () {return property; } public void setProperty (int arvo} {ominaisuus = arvo;} 

Voit käyttää jotain seuraavista:

yksityinen @omaisuuden omaisuus; 

Käyttöliittymän rakennustyökalu tai vastaava käyttää introspektio-sovellusliittymiä ominaisuuksien löytämiseen sen sijaan, että tutkisi menetelmien nimiä ja johtaisi ominaisuuden olemassaolon nimen perusteella. Siksi mikään ajonaikainen pääsy ei vahingoita koodiasi.

Milloin lisävaruste on kunnossa?

Ensinnäkin, kuten aiemmin keskustelin, on hyvä tapa palauttaa objekti objektin toteuttaman käyttöliittymän muodossa, koska kyseinen käyttöliittymä eristää sinut toteutusluokan muutoksista. Tällainen menetelmä (joka palauttaa rajapintaviitteen) ei todellakaan ole "getter" siinä mielessä, että menetelmä tarjoaa vain pääsyn kentälle. Jos muutat palveluntarjoajan sisäistä toteutusta, muutat vain palautetun objektin määritelmää muutosten huomioon ottamiseksi. Suojaat edelleen objektia käyttävän ulkoisen koodin käyttöliittymän kautta.