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 Vagantti
tai 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
, BorderLayoutPanel
jne., 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
, id
ja 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ää Merkkijono
s 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}