Vi har allerede gjennomgått bruken av et singleton-objekt, men du er kanskje ikke klar over at denne strategien er et designmønster, og en av de mest brukte.
Faktisk er det mange av disse mønstrene, og de kan klassifiseres i henhold til deres spesifikke formål.
Mønsterklassifisering
Mønstertype | applikasjon |
---|---|
Kreativt | En type som løser problemet med objektoppretting |
Strukturell | Mønstre som lar oss bygge et korrekt og utvidbart klassehierarki i arkitekturen vår |
Atferdsmessig | Denne klyngen av mønstre forenkler sikker og praktisk interaksjon mellom objekter i et program. |
Vanligvis er et mønster preget av problemet det løser. La oss ta en titt på noen mønstre som vi møter oftest når vi jobber med Java:
Mønster | Hensikt |
---|---|
Singleton | Vi er allerede kjent med dette mønsteret - vi bruker det til å opprette og få tilgang til en klasse som ikke kan ha mer enn én forekomst. |
Iterator | Vi er også kjent med denne. Vi vet at dette mønsteret lar oss iterere over et samlingsobjekt uten å avsløre dens interne representasjon. Den brukes med samlinger. |
Adapter | Dette mønsteret kobler sammen inkompatible objekter slik at de kan fungere sammen. Jeg tror navnet på adaptermønsteret hjelper deg å forestille deg nøyaktig hva dette gjør. Her er et enkelt eksempel fra det virkelige liv: en USB-adapter for en stikkontakt. |
Malmetode |
Et atferdsprogrammeringsmønster som løser integrasjonsproblemet og lar deg endre algoritmiske trinn uten å endre strukturen til en algoritme. Tenk deg at vi har en bilmonteringsalgoritme i form av en sekvens av monteringstrinn: Chassis -> Karosseri -> Motor -> Hytteinteriør Hvis vi setter inn en forsterket ramme, en kraftigere motor eller et interiør med ekstra belysning, trenger vi ikke å endre algoritmen, og den abstrakte sekvensen forblir den samme. |
Dekoratør | Dette mønsteret lager innpakninger for objekter for å gi dem nyttig funksjonalitet. Vi vil vurdere det som en del av denne artikkelen. |
I Java.io implementerer følgende klasser mønstre:
Mønster | Hvor det brukes i java.io |
---|---|
Adapter |
|
Malmetode | |
Dekoratør |
Dekorasjonsmønster
La oss forestille oss at vi beskriver en modell for et hjemdesign.
Generelt ser tilnærmingen slik ut:
I første omgang har vi valg mellom flere typer hus. Minimumskonfigurasjonen er én etasje med tak. Da bruker vi alle slags dekoratører for å endre tilleggsparametre, noe som naturligvis påvirker prisen på huset.
Vi lager en abstrakt husklasse:
public abstract class House {
String info;
public String getInfo() {
return info;
}
public abstract int getPrice();
}
Her har vi 2 metoder:
- getInfo() returnerer informasjon om navnet og funksjonene til huset vårt;
- getPrice() returnerer prisen på gjeldende huskonfigurasjon.
Vi har også standard husimplementeringer - murstein og tre:
public class BrickHouse extends House {
public BrickHouse() {
info = "Brick House";
}
@Override
public int getPrice() {
return 20_000;
}
}
public class WoodenHouse extends House {
public WoodenHouse() {
info = "Wooden House";
}
@Override
public int getPrice() {
return 25_000;
}
}
Begge klassene arver House -klassen og overstyrer dens prismetode, og setter en egendefinert pris for et standardhus. Vi setter navnet i konstruktøren.
Deretter må vi skrive dekoratørklasser. Disse klassene vil også arve Husklassen . For å gjøre dette lager vi en abstrakt dekoratørklasse.
Det er der vi legger inn ytterligere logikk for å endre et objekt. I utgangspunktet vil det ikke være noen ekstra logikk og abstraktklassen vil være tom.
abstract class HouseDecorator extends House {
}
Deretter lager vi dekorasjonsimplementeringer. Vi vil lage flere klasser som lar oss legge til flere funksjoner til huset:
public class SecondFloor extends HouseDecorator {
House house;
public SecondFloor(House house) {
this.house = house;
}
@Override
public int getPrice() {
return house.getPrice() + 20_000;
}
@Override
public String getInfo() {
return house.getInfo() + " + second floor";
}
}
En dekoratør som legger til en andre etasje til huset vårt |
Dekoratørkonstruktøren aksepterer et hus som vi skal "pynte", dvs. legge til modifikasjoner. Og vi overstyrer metodene getPrice() og getInfo() , og returnerer informasjon om det nye oppdaterte huset basert på det gamle.
public class Garage extends HouseDecorator {
House house;
public Garage(House house) {
this.house = house;
}
@Override
public int getPrice() {
return house.getPrice() + 5_000;
}
@Override
public String getInfo() {
return house.getInfo() + " + garage";
}
}
En dekoratør som legger til en garasje til huset vårt |
Nå kan vi oppdatere huset vårt med dekoratører. For å gjøre dette må vi lage et hus:
House brickHouse = new BrickHouse();
Deretter setter vi vårhusvariabel lik en ny dekoratør, passerer i huset vårt:
brickHouse = new SecondFloor(brickHouse);
Vårhusvariabel er nå et hus med en andre etasje.
La oss se på brukstilfeller som involverer dekoratører:
Eksempelkode | Produksjon |
---|---|
|
Mursteinhus 20 000 |
|
Mursteinhus + andre etasje 40 000 |
|
Mursteinshus + andre etasje + garasje 45 000 |
|
Trehus + garasje + andre etasje 50 000 |
|
Trehus 25 000 Trehus + garasje 30 000 |
Dette eksemplet illustrerer fordelen med å oppgradere et objekt med en dekoratør. Så vi endret ikketrehusobjektet selv, men laget i stedet et nytt objekt basert på det gamle. Her kan vi se at fordelene kommer med ulemper: vi lager et nytt objekt i minnet hver gang, noe som øker minneforbruket.
Se på dette UML-diagrammet av programmet vårt:
En dekoratør har en superenkel implementering og endrer objekter dynamisk ved å oppgradere dem. Dekoratører kan gjenkjennes av konstruktørene deres, som tar objekter av samme abstrakte type eller grensesnitt som parametere som den gjeldende klassen. I Java er dette mønsteret mye brukt i I/O-klasser.
For eksempel, som vi allerede har bemerket, har alle underklasser av java.io.InputStream , OutputStream , Reader og Writer en konstruktør som godtar objekter av de samme klassene.