We hebben het gebruik van een singleton-object al bekeken, maar u realiseert zich misschien nog niet dat deze strategie een ontwerppatroon is, en een van de meest gebruikte.

In feite zijn er veel van deze patronen en ze kunnen worden geclassificeerd op basis van hun specifieke doel.

Patroon classificatie

Patroon type Sollicitatie
Creatief Een type dat het probleem van het maken van objecten oplost
Structureel Patronen waarmee we een correcte en uitbreidbare klassenhiërarchie in onze architectuur kunnen bouwen
Gedragsmatig Dit cluster van patronen vergemakkelijkt een veilige en gemakkelijke interactie tussen objecten in een programma.

Typisch wordt een patroon gekenmerkt door het probleem dat het oplost. Laten we eens kijken naar enkele patronen die we het vaakst tegenkomen bij het werken met Java:

Patroon Doel
Eenling We zijn al bekend met dit patroon - we gebruiken het om een ​​klasse te maken en te openen die niet meer dan één instantie kan hebben.
Iterator Deze kennen we ook. We weten dat we met dit patroon een verzamelobject kunnen herhalen zonder de interne representatie ervan te onthullen. Het wordt gebruikt met verzamelingen.
Adapter Dit patroon verbindt incompatibele objecten zodat ze kunnen samenwerken. Ik denk dat de naam van het adapterpatroon je helpt je precies voor te stellen wat dit doet. Hier is een eenvoudig voorbeeld uit het echte leven: een USB-adapter voor een stopcontact.
Sjabloon methode

Een gedragsprogrammeerpatroon dat het integratieprobleem oplost en waarmee u algoritmische stappen kunt wijzigen zonder de structuur van een algoritme te wijzigen.

Stel je voor dat we een auto-assemblage-algoritme hebben in de vorm van een reeks montagestappen:

Chassis -> Carrosserie -> Motor -> Cabine-interieur

Als we een versterkt frame, een krachtigere motor of een interieur met extra verlichting plaatsen, hoeven we het algoritme niet te veranderen en blijft de abstracte volgorde hetzelfde.

Decorateur Dit patroon maakt wikkels voor objecten om ze nuttige functionaliteit te geven. We zullen het beschouwen als onderdeel van dit artikel.

In Java.io implementeren de volgende klassen patronen:

Patroon Waar het wordt gebruikt in java.io
Adapter
Sjabloon methode
Decorateur

Decorateur patroon

Laten we ons voorstellen dat we een model voor een huisontwerp beschrijven.

Globaal ziet de aanpak er als volgt uit:

In eerste instantie hebben we keuze uit meerdere woningtypes. De minimale configuratie is één verdieping met een dak. Vervolgens zetten we allerlei binnenhuisarchitecten in om aanvullende parameters te wijzigen, wat natuurlijk van invloed is op de prijs van het huis.

We maken een abstracte House-klasse:


public abstract class House {
	String info;
 
	public String getInfo() {
    	return info;
	}
 
	public abstract int getPrice();
}
    

Hier hebben we 2 methoden:

  • getInfo() retourneert informatie over de naam en kenmerken van ons huis;
  • getPrice() retourneert de prijs van de huidige huisconfiguratie.

We hebben ook standaard huisimplementaties — baksteen en hout:


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;
	}
}
    

Beide klassen erven de klasse House en overschrijven de prijsmethode door een aangepaste prijs in te stellen voor een standaardhuis. We zetten de naam in de constructor.

Vervolgens moeten we decorateurklassen schrijven. Deze klassen zullen ook de klasse House erven . Om dit te doen, maken we een abstracte decorateurklasse.

Dat is waar we extra logica plaatsen voor het wijzigen van een object. Aanvankelijk zal er geen extra logica zijn en zal de abstracte klasse leeg zijn.


abstract class HouseDecorator extends House {
}
    

Vervolgens maken we decorateur-implementaties. We zullen verschillende klassen maken waarmee we extra functies aan het huis kunnen toevoegen:


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";
	}
}
    
Een binnenhuisarchitect die een tweede verdieping toevoegt aan ons huis

De decorateur-bouwer accepteert een huis dat we zullen "inrichten", dwz aanpassingen toevoegen. En we overschrijven de methoden getPrice() en getInfo() en geven informatie terug over het nieuwe bijgewerkte huis op basis van het oude.


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";
	}
}
    
Een binnenhuisarchitect die een garage toevoegt aan ons huis

Nu kunnen we ons huis bijwerken met decorateurs. Om dit te doen, moeten we een huis maken:


House brickHouse = new BrickHouse();
    

Vervolgens stellen we onze inhuisvariabele gelijk aan een nieuwe binnenhuisarchitect die in ons huis langskomt:


brickHouse = new SecondFloor(brickHouse); 
    

Onshuisvariabele is nu een huis met een tweede verdieping.

Laten we eens kijken naar use cases waarbij decorateurs betrokken zijn:

Voorbeeldcode Uitgang

House brickHouse = new BrickHouse(); 

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

Bakstenen huis

20000


House brickHouse = new BrickHouse(); 

  brickHouse = new SecondFloor(brickHouse); 

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

Bakstenen huis + tweede verdieping

40000


House brickHouse = new BrickHouse();
 

  brickHouse = new SecondFloor(brickHouse);
  brickHouse = new Garage(brickHouse);

  System.out.println(brickHouse.getInfo());
  System.out.println(brickHouse.getPrice());
                    

Bakstenen huis + tweede verdieping + garage

45000


House woodenHouse = new SecondFloor(new Garage(new WoodenHouse())); 

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());
                    

Houten huis + garage + tweede verdieping

50000


House woodenHouse = new WoodenHouse(); 

  House woodenHouseWithGarage = new Garage(woodenHouse);

  System.out.println(woodenHouse.getInfo());
  System.out.println(woodenHouse.getPrice());

  System.out.println(woodenHouseWithGarage.getInfo());
  System.out.println(woodenHouseWithGarage.getPrice());
                    

Houten huis

25000

Houten huis + garage

30000

Dit voorbeeld illustreert het voordeel van het upgraden van een object met een binnenhuisarchitect. Dus we hebben het niet veranderdhouten huisobject zelf, maar creëerde in plaats daarvan een nieuw object op basis van het oude. Hier kunnen we zien dat de voordelen gepaard gaan met nadelen: we creëren elke keer een nieuw object in het geheugen, waardoor het geheugenverbruik toeneemt.

Kijk naar dit UML-diagram van ons programma:

Een decorateur heeft een supereenvoudige implementatie en verandert dynamisch objecten en upgradet ze. Decorators zijn te herkennen aan hun constructors, die als parameters objecten van hetzelfde abstracte type of interface als de huidige klasse nemen. In Java wordt dit patroon veel gebruikt in I/O-klassen.

Zoals we al hebben opgemerkt, hebben bijvoorbeeld alle subklassen van java.io.InputStream , OutputStream , Reader en Writer een constructor die objecten van dezelfde klassen accepteert.