Ohjelmointi

Lisää gettereistä ja settereistä

Se on 25 vuotta vanha olio (OO) -suunnittelun periaate, jonka mukaan sinun ei tule paljastaa objektin toteutusta muille ohjelman luokille. Ohjelmaa on tarpeettoman vaikea ylläpitää, kun paljastat toteutuksen, lähinnä siksi, että objektin muuttaminen, joka paljastaa sen toteuttamismääräykset, muuttuu kaikille objektia käyttäville luokille.

Valitettavasti getter / setter idioma, jonka monet ohjelmoijat ajattelevat olio orientoituneeksi, rikkoo tätä OO: n perusperiaatetta patoissa. Tarkastellaan esimerkkiä a Raha luokka, jolla on getValue () menetelmä, joka palauttaa "arvon" dollareina. Sinulla on koko ohjelmassasi seuraavanlainen koodi:

kaksinkertainen järjestysYhteensä; Rahamäärä = ...; //... orderTotal + = määrä.getValue (); // orderTotal on oltava dollareissa

Tämän lähestymistavan ongelmana on, että edellinen koodi tekee suuren oletuksen siitä, miten Raha luokka on toteutettu (että "arvo" on tallennettu a kaksinkertainen). Koodi, joka tekee toteutusolettamuksista katkenneita, kun toteutus muuttuu. Jos esimerkiksi sinun on kansainvälistettävä sovelluksesi tukemaan muita valuuttoja kuin dollareita, niin getValue () ei palauta mitään mielekästä. Voit lisätä a getCurrency (), mutta se tekisi kaiken koodin, joka ympäröi getValue () soita paljon monimutkaisemmaksi, varsinkin jos jatkat getter / setter-strategian käyttämistä saadaksesi työn tekemiseen tarvittavat tiedot. Tyypillinen (puutteellinen) toteutus saattaa näyttää tältä:

Rahasumma = ...; //... arvo = määrä.getValue (); valuutta = summa.getCurrency (); muuntaminen = CurrencyTable.getConversionFactor (valuutta, USDOLLARS); yhteensä + = arvo * muunnos; //...

Tämä muutos on liian monimutkainen käsiteltäväksi automaattisella uudelleenrakentamisella. Lisäksi sinun on tehtävä tällaisia ​​muutoksia kaikkialla koodissasi.

Liikelogiikkatason ratkaisu tähän ongelmaan on tehdä työ objektissa, jolla on työn tekemiseen tarvittavat tiedot. Sen sijaan, että purat "arvon" jonkin ulkoisen toiminnan suorittamiseksi, sinulla pitäisi olla Raha luokka suorittaa kaikki rahaan liittyvät toiminnot, mukaan lukien valuutan muuntaminen. Oikein jäsennelty objekti käsittelisi kokonaismäärää näin:

Rahat yhteensä = ...; Rahasumma = ...; total.increaseBy (määrä); 

lisätä() menetelmä selvittäisi operandin valuutan, tekisi tarvittavat valuuttamuunnokset (mikä on oikein operaatio raha) ja päivitä kokonaismäärä. Jos käytit aluksi tätä objektia, jolla on tieto-tekee-työ-strategiaa, käsite valuutta voidaan lisätä Raha luokka ilman muutoksia, joita vaaditaan käytetyssä koodissa Raha esineitä. Toisin sanoen, vain dollarin uudelleenrakentaminen kansainväliseen toteutukseen keskittyisi yhteen paikkaan: Raha luokassa.

Ongelma

Suurimmalla osalla ohjelmoijista ei ole vaikeuksia ymmärtää tätä käsitettä liiketoimintalogiikan tasolla (vaikka se voi kestää jonkin verran ajattelemaan näin). Ongelmia alkaa ilmetä kuitenkin, kun käyttöliittymä (UI) tulee kuvaan. Ongelmana ei ole, että et voi käyttää tekniikoita, joita juuri kuvasin, käyttöliittymän rakentamiseen, vaan että monet ohjelmoijat ovat lukittu käyttöliittymien suhteen getter / setter-mentaliteettiin. Syytän tätä ongelmaa periaatteessa menettelytapojen koodinrakennustyökaluista, kuten Visual Basic ja sen kloonit (mukaan lukien Java UI -rakentajat), jotka pakottavat sinut tähän menettelytapaan, getter / setter-ajattelutapaan.

(Poikkeama: Jotkut teistä epäilevät edellistä lausuntoa ja huutavat, että VB perustuu pyhitettyyn Model-View-Controller (MVC) -arkkitehtuuriin, joten se on pyhä. Muista, että MVC kehitettiin melkein 30 vuotta sitten. Alussa 1970-luvulla suurin supertietokone oli samanlainen kuin nykyiset työpöydät. Useimmat koneet (kuten DEC PDP-11) olivat 16-bittisiä tietokoneita, 64 kt: n muistia ja kellotaajuudet mitattuna kymmenissä megahertseissä. pino rei'itettyjä kortteja. Jos sinulla on onni saada videopääte, olet ehkä käyttänyt ASCII-pohjaista konsolin tulo- / lähtöjärjestelmää (I / O). Olemme oppineet paljon viimeisten 30 vuoden aikana. Jopa Java Swingin oli korvattava MVC samanlaisella "erotettavissa olevan mallin" arkkitehtuurilla, pääasiassa siksi, että puhdas MVC ei eristää riittävästi käyttöliittymän ja verkkotunnuksen mallikerroksia.)

Joten määritellään ongelma pähkinänkuoressa:

Jos objekti ei välttämättä paljasta toteutustietoja (get / set -menetelmillä tai millä tahansa muulla tavalla), on järkevää, että objektin on jotenkin luotava oma käyttöliittymä. Toisin sanoen jos tapa, jolla objektin määritteet esitetään, on piilotettu muusta ohjelmasta, et voi purkaa näitä määritteitä käyttöliittymän rakentamiseksi.

Huomaa, muuten, ettet piilota sitä, että määrite on olemassa. (Olen määrittelemässä määritteen, tässä kohteen olennaisena ominaisuutena.) Tiedät, että Työntekijä on oltava palkka tai palkkamääritelmä, muuten se ei olisi Työntekijä. (Se olisi Henkilö, a Vapaaehtoinen, a Vaganttitai jokin muu, jolla ei ole palkkaa.) Mitä et tiedä - tai haluat tietää - on, kuinka tämä palkka on edustettuna kohteen sisällä. Se voisi olla kaksinkertainen, a Merkkijono, skaalattu pitkätai binäärikoodattu desimaali. Se voi olla "synteettinen" tai "johdettu" attribuutti, joka lasketaan ajon aikana (esimerkiksi palkkaluokasta tai työnimikkeestä tai hakemalla arvo tietokannasta). Vaikka get-menetelmä voi todellakin piilottaa osan tästä toteutustavasta, kuten näimme Raha Esimerkiksi se ei voi piiloutua tarpeeksi.

Joten miten esine tuottaa oman käyttöliittymän ja pysyy ylläpidettävänä? Vain yksinkertaisimmat kohteet voivat tukea jotain kuten a displayYourself () menetelmä. Realististen esineiden on:

  • Näytä itsensä eri muodoissa (XML, SQL, pilkuilla erotetut arvot jne.).
  • Näytä erilainen näkymät itsestään (yksi näkymä saattaa näyttää kaikki määritteet; toinen saattaa näyttää vain osan määritteistä; ja kolmas saattaa näyttää määritteet eri tavalla).
  • Näytä itsensä erilaisissa ympäristöissä (asiakaspuoli (JKomponentti) ja palvelimelta asiakkaalle (esimerkiksi HTML) ja käsittelevät sekä syötettä että lähtöä molemmissa ympäristöissä.

Jotkut edellisen getter / setter-artikkelin lukijoista hyppivät siihen johtopäätökseen, että kannatin, että lisäät objektiin menetelmiä kattamaan kaikki nämä mahdollisuudet, mutta tuo "ratkaisu" on tietysti järjetön. Paitsi tuloksena oleva raskas esine on liian monimutkainen, sinun on muokattava sitä jatkuvasti vastaamaan uusia käyttöliittymävaatimuksia. Käytännössä esine ei vain pysty rakentamaan kaikkia mahdollisia käyttöliittymiä itselleen, ellei mistään muusta syystä kuin monia näistä käyttöliittymistä ei edes suunniteltu luokan luomisen yhteydessä.

Rakenna ratkaisu

Tämän ongelman ratkaisu on erottaa käyttöliittymäkoodi ydinliiketoiminnasta asettamalla se erilliseen objektiluokkaan. Eli sinun tulisi erottaa osa toiminnoista voisi olla esineessä erilliseksi kohteeksi kokonaan.

Tämä kohteen menetelmien haarautuminen esiintyy useissa suunnittelumalleissa. Tunnet todennäköisesti strategian, jota käytetään erilaisten kanssa java.awt.Säiliö luokat tehdä asettelua. Voit ratkaista asetteluongelman johdannaisratkaisulla: FlowLayoutPanel, GridLayoutPanel, BorderLayoutPaneljne., mutta se edellyttää liian monta luokkaa ja paljon päällekkäistä koodia näissä luokissa. Yksi raskasluokan ratkaisu (lisäämällä menetelmiä Kontti Kuten layOutAsGrid (), layOutAsFlow ()jne.) on myös epäkäytännöllinen, koska et voi muokata lähdekoodia Kontti yksinkertaisesti siksi, että tarvitset ulkoasua, jota ei tueta. Strategiakuviossa luot a Strategia käyttöliittymä (LayoutManager), joita useat ovat toteuttaneet Konkreettinen strategia luokat (FlowLayout, Ruudukkoasettelu, jne.). Sitten kerrot a Asiayhteys esine (a Kontti) miten tehdä jotain siirtämällä se a Strategia esine. (Ohitat a Kontti a LayoutManager joka määrittelee asettelustrategian.)

Builder-malli on samanlainen kuin strategia. Tärkein ero on, että Rakentaja luokka toteuttaa strategian jonkin rakentamiseksi (kuten a JKomponentti tai XML-virta, joka edustaa objektin tilaa). Rakentaja objektit yleensä rakentavat tuotteitaan myös monivaiheisella prosessilla. Eli kutsuu eri menetelmiin Rakentaja vaaditaan rakennusprosessin loppuun saattamiseksi, ja Rakentaja tyypillisesti ei tiedä puhelun soittojärjestystä tai kuinka monta sen menetelmää kutsutaan. Rakentajan tärkein ominaisuus on, että liikeobjekti (kutsutaan Asiayhteys) ei tiedä tarkalleen mitä Rakentaja esine on rakentamassa. Kuvio eristää liikeobjektin esityksestä.

Paras tapa nähdä yksinkertaisen rakentajan toiminta on tarkastella yhtä. Tarkastellaan ensin Asiayhteys, liikeobjekti, jonka on paljastettava käyttöliittymä. Luettelossa 1 on yksinkertaistettu Työntekijä luokassa. Työntekijä on nimi, idja palkka määritteet. (Näiden luokkien tynkä on listan lopussa, mutta nämä tuput ovat vain paikkamerkkejä oikealle. Voit - toivon - kuvitella helposti, kuinka nämä luokat toimisivat.)

Tämä erityisesti Asiayhteys käyttää mielestäni kaksisuuntaisena rakentajana. Klassinen neljän rakentajan jengi menee yhteen suuntaan (lähtö), mutta olen lisännyt myös a Rakentaja että an Työntekijä objektilla voidaan aloittaa itsensä. Kaksi Rakentaja liitännät vaaditaan. Työntekijä.Viejä liitäntä (Listaus 1, rivi 8) käsittelee lähtösuunnan. Se määrittelee käyttöliittymän a Rakentaja objekti, joka muodostaa nykyisen objektin esityksen. Työntekijä delegoi käyttöliittymän varsinaisen rakentamisen Rakentaja että viedä() menetelmä (rivillä 31). Rakentaja ei läpäise todellisia kenttiä, vaan käyttää Merkkijonos läpäistä edustus näistä kentistä.

Listaus 1. Työntekijä: Builder-konteksti

 1 tuo java.util.Locale; 2 3 julkisen luokan työntekijä 4 {yksityinen nimen nimi; 5 yksityinen EmployeeId-tunnus; 6 yksityinen rahapalkka; 7 8 julkinen käyttöliittymä Viejä 9 {void addName (String name); 10 void addID (merkkijonon tunnus); 11 mitätön addSalary (jousipalkka); 12} 13 14 julkinen käyttöliittymä Maahantuoja 15 {String supplyName (); 16 merkkijono supplyID (); 17 merkkijono tarjotaSalary (); 18 mitätön auki (); 19 mitätön sulje (); 20} 21 22 julkinen työntekijä (maahantuojan rakentaja) 23 {rakentaja.open (); 24 this.name = uusi nimi (builder.provideName ()); 25 this.id = uusi EmployeeId (rakentaja.provideID ()); 26 this.salary = uusi raha (builder.provideSalary (), 27 new Locale ("en", "US")); 28 rakentaja. Sulje (); 29} 30 31 public void export (Viejärakentaja) 32 {builder.addName (name.toString ()); 33 builder.addID (id.toString ()); 34 builder.addSalary (palkka.String ()); 35} 36 37 //... 38 } 39 //---------------------------------------------------------------------- 40 // Yksikötesti 41 // 42 luokan nimi 43 {yksityinen merkkijonoarvo; 44 public name (String value) 45 {tämä.arvo = arvo; 46} 47 public String toString () {palautusarvo; }; 48} 49 50 luokan EmployeeId 51 {yksityinen merkkijonoarvo; 52 public EmployeeId (String value) 53 {this.arvo = arvo; 54} 55 public String toString () {palautusarvo; } 56} 57 58 luokan raha 59 {yksityinen merkkijonoarvo; 60 julkista rahaa (merkkijonoarvo, sijainti) 61 {this.value = value; 62} 63 public String toString () {palautusarvo; } 64} 

Katsotaanpa esimerkkiä. Seuraava koodi rakentaa kuvan 1 käyttöliittymän:

Työntekijän wilma = ...; JComponentExporter uiBuilder = uusi JComponentExporter (); // Luo rakennustyökalu wilma.export (uiBuilder); // Rakenna käyttöliittymä JComponent userInterface = uiBuilder.getJComponent (); //... someContainer.add (userInterface); 

Luettelossa 2 näkyy JComponentExporter. Kuten näette, kaikki käyttöliittymään liittyvät koodit ovat keskittyneet Betonin valmistaja ( JComponentExporter), ja Asiayhteys ( Työntekijä) ohjaa rakennusprosessia tietämättä tarkalleen, mitä se rakentaa.

Listaus 2. Vienti asiakaspuolen käyttöliittymään

 1 tuonti javax.swing. *; 2 tuo java.awt. *; 3 tuo java.awt.event. *; 4 5 luokan JComponentExporter toteuttaa työntekijä.Viejä 6 {yksityinen Merkkijono nimi, tunnus, palkka; 7 8 public void addName (Merkkijonon nimi) {this.name = nimi; } 9 public void addID (String id) {this.id = id; } 10 public void addSalary (String palkka) {tämä.palkka = palkka; } 11 12 JComponent getJComponent () 13 {JComponent -paneeli = uusi JPanel (); 14 panel.setLayout (uusi GridLayout (3,2)); 15 panel.add (uusi JLabel ("Nimi:")); 16 panel.add (uusi JLabel (nimi)); 17 panel.add (uusi JLabel ("Työntekijän tunnus:")); 18 panel.add (uusi JLabel (id)); 19 panel.add (uusi JLabel ("Palkka:")); 20 paneeli. Lisää (uusi JLabel (palkka)); 21 palautuspaneeli; 22} 23}