Tai 25 metų senumo objektinio (OO) projektavimo principas, pagal kurį neturėtumėte atskleisti objekto įgyvendinimo jokioms kitoms programos klasėms. Programą yra be reikalo sunku išlaikyti, kai atskleidžiate įgyvendinimą, visų pirma todėl, kad pakeitus objektą, kuris atskleidžia jos įgyvendinimo įgaliojimus, pasikeičia visos klasės, naudojančios objektą.
Deja, „getter“ / „seterio“ idioma, kurią daugelis programuotojų laiko orientuota į objektą, kastuvuose pažeidžia šį pagrindinį OO principą. Apsvarstykite a pavyzdį Pinigai
klasė, kurioje yra a getValue ()
metodas, kuris grąžina „vertę“ doleriais. Visoje savo programoje turėsite tokį kodą:
dviguba tvarkaVisa; Pinigų suma = ...; //... orderTotal + = suma.getValue (); // orderSotal turi būti doleriais
Šio požiūrio problema yra ta, kad pirmiau pateiktas kodas daro didelę prielaidą, kaip Pinigai
klasė yra įgyvendinta (kad „vertė“ saugoma a dvigubai
). Kodas, dėl kurio įgyvendinimo prielaidos nutrūksta, kai pasikeičia įgyvendinimas. Jei, pavyzdžiui, turite internacionalizuoti savo programą, kad palaikytumėte kitas nei dolerių valiutas, tada getValue ()
negrąžina nieko prasmingo. Galite pridėti a „getCurrency“ ()
, bet tai padarytų visą kodą, supantį getValue ()
skambinkite daug sudėtingiau, ypač jei vis dar naudojate „getter“ / „seterio“ strategiją, kad gautumėte informaciją, kurios jums reikia darbui atlikti. Tipiškas (ydingas) diegimas gali atrodyti taip:
Pinigų suma = ...; //... reikšmė = suma.getValue (); valiuta = suma.getCurrency (); konversija = CurrencyTable.getConversionFactor (valiuta, USDOLLARS); viso + = vertės * perskaičiavimas; //...
Šis pakeitimas yra per daug sudėtingas, kad jį būtų galima atlikti automatizuotai pertvarkant. Be to, jūs turėtumėte atlikti tokius pakeitimus visur savo kode.
Verslo logikos lygiu išspręskite šią problemą - atlikti darbą objekte, kuriame yra darbui atlikti reikalinga informacija. Užuot ištraukę „vertę“, kad atliktumėte tam tikrą išorinę operaciją, turėtumėte turėti Pinigai
klasė atlieka visas su pinigais susijusias operacijas, įskaitant valiutos konvertavimą. Tinkamai struktūrizuotas objektas apdorotų bendrą sumą taip:
Pinigai iš viso = ...; Pinigų suma = ...; total.increaseBy (suma);
papildyti()
metodas išsiaiškins operando valiutą, atliks bet kokį būtiną valiutos konvertavimą (tai yra operacija su pinigų) ir atnaujinkite bendrą sumą. Jei pirmiausia naudojote šią objekto strategiją, kuri turi informaciją, daro darbą, tai sąvoka valiuta galėtų būti pridėta prie Pinigai
klasę be jokių pakeitimų, reikalingų naudojamame kode Pinigai
objektai. Tai reiškia, kad tik dolerių pertvarkymas tarptautiniam įgyvendinimui būtų sutelktas vienoje vietoje: Pinigai
klasė.
Problema
Daugumai programuotojų nekyla sunkumų suvokiant šią koncepciją verslo logikos lygmeniu (nors nuosekliai taip galvoti gali prireikti šiek tiek pastangų). Problemos pradeda kilti, kai vartotojo sąsaja (UI) patenka į paveikslėlį. Problema yra ne ta, kad negalite pritaikyti tokių technikų, kaip ką tik aprašiau, kuriant vartotojo sąsają, bet tai, kad daugelis programuotojų yra užrakinti „getter / setter“ mentalitetu, kai kalbama apie vartotojo sąsajas. Aš kaltinu šią problemą iš esmės procedūriniais kodų konstravimo įrankiais, tokiais kaip „Visual Basic“ ir jo klonais (įskaitant „Java“ vartotojo sąsajos kūrėjus), kurie priverčia jus laikytis šio procedūrinio, „geresnio / nustatomojo“ mąstymo.
(Nukrypimas: kai kurie iš jūsų atsisakys ankstesnio teiginio ir rėks, kad VB remiasi pašventinta „Model-View-Controller“ (MVC) architektūra, taigi yra šventai šventa. Turėkite omenyje, kad MVC buvo sukurtas beveik prieš 30 metų. Aštuntajame dešimtmetyje didžiausias superkompiuteris buvo lygiavertis šių dienų darbalaukiams. Dauguma mašinų (pvz., DEC PDP-11) buvo 16 bitų kompiuteriai, turintys 64 KB atminties, o laikrodžio dažnis buvo matuojamas dešimtimis megahercų. Jūsų vartotojo sąsaja tikriausiai buvo šūsnis skylėtų kortelių. Jei jums pasisekė turėti vaizdo terminalą, galbūt naudojote ASCII pagrįstą konsolės įvesties / išvesties (įvesties / išvesties) sistemą. Per pastaruosius 30 metų mes daug ko išmokome. Net „Java Swing“ turėjo pakeisti MVC panašia „atskirto modelio“ architektūra, visų pirma todėl, kad grynas MVC nepakankamai izoliuoja vartotojo sąsają ir domeno modelio sluoksnius.)
Taigi trumpai apibūdinkime problemą:
Jei objektas negali atskleisti įgyvendinimo informacijos (naudodamas „get / set“ metodus ar bet kokiomis kitomis priemonėmis), tai suprantama, kad objektas turi kažkaip susikurti savo vartotojo sąsają. Tai yra, jei objekto atributų atvaizdavimo būdas yra paslėptas nuo likusios programos, tada negalėsite išskleisti tų atributų, kad sukurtumėte vartotojo sąsają.
Beje, atkreipkite dėmesį, kad neslepiate fakto, kad yra atributas. (Aš apibrėžiu atributas, čia, kaip esminė objekto savybė.) Žinote, kad an Darbuotojas
privalo turėti atlyginimą arba atlyginimo atributą, kitaip tai nebūtų Darbuotojas
. (Tai būtų a Asmuo
, a Savanoris
, a Valkata
, ar dar kažkas, kas neturi atlyginimo.) Ko nežinote ar nenorite žinoti, kaip tas atlyginimas yra pavaizduotas objekto viduje. Tai gali būti a dvigubai
, a Stygos
, mastelis ilgas
arba dvejetainiai koduojamas dešimtainis skaičius. Tai gali būti „sintetinis“ arba „išvestinis“ atributas, kuris apskaičiuojamas vykdymo metu (pavyzdžiui, iš darbo užmokesčio lygio ar pareigų arba gaunant vertę iš duomenų bazės). Nors "get" metodas iš tikrųjų gali paslėpti kai kurias šios įgyvendinimo detales, kaip matėme Pinigai
Pavyzdžiui, jis negali pakankamai paslėpti.
Taigi, kaip objektas sukuria savo vartotojo sąsają ir lieka prižiūrimas? Tik patys paprasčiausi objektai gali palaikyti kažką panašaus į a rodyti save ()
metodas. Realistiniai objektai turi:
- Rodyti save skirtingais formatais (XML, SQL, kableliais atskirtos vertės ir kt.).
- Rodyti kitokį Peržiūrų jų pačių (viename rodinyje gali būti rodomi visi atributai; kitame gali būti rodomas tik pogrupis atributų; trečiame - atributai gali būti pateikiami kitaip).
- Rodyti save skirtingose aplinkose (kliento pusėje (
JKomponentas
) ir aptarnaujamas klientui (pvz., HTML)) ir tvarko tiek įvestį, tiek išvestį abiejose aplinkose.
Kai kurie mano ankstesnio „Getter / Setter“ straipsnio skaitytojai priėjo prie išvados, kad aš pasisakiau už tai, kad objekte pridėtumėte metodus, kurie padengtų visas šias galimybes, tačiau tas „sprendimas“ yra akivaizdžiai nesąmoningas. Gautas sunkiasvoris objektas yra ne tik pernelyg sudėtingas, bet ir turėsite jį nuolat modifikuoti, kad galėtumėte patenkinti naujus vartotojo sąsajos reikalavimus. Praktiškai objektas tiesiog negali susikurti visų įmanomų vartotojo sąsajų, jei dėl jokios kitos priežasties, išskyrus daugelį tų vartotojo sąsajų, dar nebuvo sukurta kuriant klasę.
Sukurkite sprendimą
Šios problemos sprendimas yra atskirti vartotojo sąsajos kodą nuo pagrindinio verslo objekto, priskiriant jį atskirai objektų klasei. Tai reiškia, kad turėtumėte atskirti kai kurias funkcijas galėjo būti objekte visiškai į atskirą objektą.
Šis objekto metodų išsišakojimas pasireiškia keliais dizaino modeliais. Greičiausiai esate susipažinę su strategija, kuri naudojama kartu su įvairiomis java.awt. Konteineris
klasės daryti maketą. Išdėstymo problemą galite išspręsti naudodami išvesties sprendimą: „FlowLayoutPanel“
, „GridLayoutPanel“
, „BorderLayoutPanel“
ir t. t., tačiau tai įpareigoja per daug klases ir daug besikartojančių tų klasių kodų. Vienas sunkiasvorės klasės sprendimas (metodų pridėjimas prie Konteineris
Kaip layOutAsGrid ()
, layOutAsFlow ()
ir kt.) taip pat yra nepraktiška, nes negalite modifikuoti šaltinio kodo Konteineris
vien todėl, kad jums reikia nepalaikomo išdėstymo. Pagal strategijos modelį sukuriate Strategija
sąsaja („LayoutManager“
) įgyvendino keli Konkreti strategija
klasės („FlowLayout“
, „GridLayout“
ir pan.). Tada jūs sakote a Kontekstas
objektas (a Konteineris
) kaip ką nors padaryti jį perduodant a Strategija
objektas. (Praeini a Konteineris
a „LayoutManager“
tai apibrėžia maketo strategiją.)
„Builder“ modelis yra panašus į „Strategy“. Pagrindinis skirtumas yra tas, kad Statybininkas
klasė įgyvendina kažko konstravimo strategiją (pvz., a JKomponentas
arba XML srautas, vaizduojantis objekto būseną). Statybininkas
objektai paprastai kuria savo produktus naudodami ir daugiapakopį procesą. Tai yra raginimai naudoti įvairius metodus Statybininkas
reikalaujama užbaigti statybos procesą, ir Statybininkas
paprastai nežino skambučių atlikimo tvarkos ar vieno iš jo būdų skambučių skaičiaus. Svarbiausia statybininko savybė yra ta, kad verslo objektas (vadinamas Kontekstas
) tiksliai nežino, kas Statybininkas
objektas statomas. Šablonas izoliuoja verslo objektą nuo jo vaizdavimo.
Geriausias būdas pamatyti, kaip veikia paprastas statybininkas, yra pažvelgti į jį. Pirmiausia pažvelkime į Kontekstas
, verslo objektas, kuriam reikia atskleisti vartotojo sąsają. 1 sąrašas rodo supaprastintą Darbuotojas
klasė. Darbuotojas
turi vardas
, id
ir atlyginimas
atributus. (Šių klasių šakos yra sąrašo apačioje, tačiau šios šaknys yra tikros vietos žymekliai. Tikiuosi, jūs galite lengvai įsivaizduoti, kaip šios klasės veiks.)
Šis konkretus Kontekstas
naudojasi mano manymu kaip dvikryptis statybininkas. Klasikinė keturių statybininkų gauja eina viena kryptimi (išvestis), bet aš taip pat pridėjau a Statybininkas
kad an Darbuotojas
objektas gali naudoti pats inicializuoti. Du Statybininkas
reikalingos sąsajos. Darbuotojas. Eksportuotojas
sąsaja (1 sąrašas, 8 eilutė) tvarko išvesties kryptį. Tai apibrėžia sąsają su Statybininkas
objektas, kuris sukuria dabartinio objekto vaizdą. Darbuotojas
deleguoja tikrąją NS sąsają Statybininkas
viduje konors eksportas ()
metodas (31 eilutėje). Statybininkas
neperduodamas tikrasis laukas, o naudojamas Stygos
s perduoti tų laukų reprezentaciją.
Sąrašas 1. Darbuotojas: „Builder“ kontekstas
1 importuoti java.util.Locale; 2 3 viešoji klasė 4 darbuotojas {privatus vardas, pavardė; 5 privatus „EmployeeId“ ID; 6 privatus Pinigų atlyginimas; 7 8 viešoji sąsaja eksportuotojas 9 {void addName (eilutės pavadinimas); 10 negaliojantis addID (eilutės ID); 11 niekinis addSalary (String alga); 12} 13 14 viešoji sąsaja Importuotojas 15 {String suteiktiName (); 16 eilutė suteiktiID (); 17 Stygos teiktiSalary (); 18 tuštuma atidaryta (); 19 niekinis uždarymas (); 20} 21 22 valstybinis darbuotojas (importuotojas statybininkas) 23 {statybininkas.atvaryti (); 24 this.name = naujas vardas (statybininkas.provideName ()); 25 this.id = new EmployeeId (statybininkas.provideID ()); 26 this.salary = nauji pinigai (builder.provideSalary (), 27 nauji lokalės ("en", "US")); 28 statybininkas.uždaryti (); 29} 30 31 viešas negaliojantis eksportas (eksportuotojo kūrėjas) 32 {statybininkas.addName (vardas.toString ()); 33 statybininkas.addID (id.toString ()); 34 statybininkas.addSalary (alga.toString ()); 35} 36 37 //... 38 } 39 //---------------------------------------------------------------------- 40 // Vieneto bandymo dalykai 41 // 42 klasės pavadinimas 43 {private String value; 44 public Name (String value) 45 {this.value = value; 46} 47 public String toString () {grąžinimo vertė; }; 48} 49 50 klasės „EmployeeId 51“ {private String value; 52 public EmployeeId (String value) 53 {this.value = value; 54} 55 public String toString () {return value; } 56} 57 58 klasės pinigai 59 {privati Styginių vertė; 60 viešųjų pinigų (eilutės vertė, lokalės vieta) 61 {this.value = value; 62} 63 public String toString () {return value; } 64}
Pažvelkime į pavyzdį. Šis kodas sukuria 1 paveikslo vartotojo sąsają:
Darbuotojo wilma = ...; JComponentExporter uiBuilder = naujas JComponentExporter (); // Sukurkite statybininką wilma.export (uiBuilder); // Sukurkite vartotojo sąsają JComponent userInterface = uiBuilder.getJComponent (); //... someContainer.add (vartotojo sąsaja);
2 sąraše rodomas „JComponentExporter“
. Kaip matote, visas su vartotojo sąsaja susijęs kodas yra sutelktas Betono gamintojas
( „JComponentExporter“
), ir Kontekstas
( Darbuotojas
) lemia kūrimo procesą tiksliai nežinodamas, ką jis kuria.
Sąrašas 2. Eksportavimas į kliento vartotojo sąsają
1 importo javax.swing. *; 2 importuoti java.awt. *; 3 importuoti java.awt.event. *; 4 5 klasės „JComponentExporter“ įgyvendina „Employee.Exporter 6“ {privatus eilutės pavadinimas, ID, atlyginimas; 7 8 public void addName (String name) {this.name = vardas; } 9 public void addID (eilutės ID) {this.id = id; } 10 public void addSalary (String atlyginimas) {this.salary = atlyginimas; } 11 12 JComponent getJComponent () 13 {JComponent panel = new JPanel (); 14 panel.setLayout (naujas „GridLayout“ (3,2)); 15 panel.add (nauja „JLabel“ („Vardas:“)); 16 panel.add (nauja „JLabel“ (vardas)); 17 panel.add (nauja „JLabel“ („Darbuotojo ID:“)); 18 panel.add (nauja „JLabel“ (id)); 19 panel.add (nauja „JLabel“ („Atlyginimas:“)); 20 panel.add (nauja JLabel (atlyginimas)); 21 grąžinimo skydelis; 22} 23}