Op dit punt bent u waarschijnlijk al ontwerppatronen tegengekomen. Bijvoorbeeld singlet .

Laten we ons eens herinneren wat patronen zijn, waarom ze nodig zijn en wat scheppingspatronen zijn (singleton is een voorbeeld). We bestuderen ook een nieuw patroon: de fabrieksmethode.

Bij softwareontwikkeling is een ontwerppatroon een herhaalbare architectonische constructie die een oplossing vertegenwoordigt voor een ontwerpprobleem binnen een terugkerende context.

Gewoonlijk is een patroon geen definitieve oplossing die direct in code kan worden omgezet. Het is slechts een modeloplossing voor een probleem dat in verschillende situaties kan worden gebruikt.

Creatiepatronen zijn ontwerppatronen die te maken hebben met het proces van het maken van objecten. Ze maken het mogelijk om een ​​systeem te creëren dat onafhankelijk is van de methode die wordt gebruikt om objecten te creëren, samen te stellen en te presenteren.

Een fabrieksmethode is een creatief ontwerppatroon dat een gemeenschappelijke interface definieert voor het maken van objecten in een bovenliggende klasse, waardoor zijn afstammelingen de mogelijkheid krijgen om deze objecten te maken. Tijdens het maken kunnen afstammelingen bepalen welke klasse ze willen maken.

Welk probleem lost het patroon op?

Stel je voor dat je besluit een bezorgprogramma aan te maken. In eerste instantie huurt u koeriers met auto's in en gebruikt u eenAutobezwaar maken om een ​​bestelwagen in het programma weer te geven. Koeriers bezorgen pakketten van punt A naar punt B enzovoort. Makkelijk.

Het programma wint aan populariteit. Uw bedrijf groeit en u wilt uitbreiden naar nieuwe markten. U kunt bijvoorbeeld ook beginnen met het bezorgen van voedsel en het verzenden van vracht. In dit geval kan voedsel door koeriers te voet, op scooters en op fietsen worden bezorgd, maar voor vracht zijn vrachtwagens nodig.

Nu moet u verschillende dingen bijhouden (wanneer, aan wie, wat en hoeveel er wordt afgeleverd), inclusief hoeveel elke koerier kan vervoeren. De nieuwe vervoerwijzen hebben verschillende snelheden en capaciteiten. Dan merk je dat de meeste entiteiten in je programma sterk gebonden zijn aan deAutoklas. U realiseert zich dat om uw programma te laten werken met andere aflevermethoden, u de bestaande codebasis moet herschrijven en dat elke keer dat u een nieuw voertuig toevoegt, opnieuw moet doen.

Het resultaat is verschrikkelijke code gevuld met voorwaardelijke verklaringen die verschillende acties uitvoeren, afhankelijk van het type transport.

De oplossing

Het fabrieksmethodepatroon suggereert het maken van objecten door een speciale fabrieksmethode aan te roepen in plaats van rechtstreeks de nieuwe operator te gebruiken. Subklassen van de klasse met de fabrieksmethode kunnen de gemaakte objecten van de specifieke voertuigen wijzigen. Op het eerste gezicht lijkt dit misschien zinloos: we hebben de constructor-oproep gewoon verplaatst van de ene plaats in het programma naar de andere. Maar nu kunt u de fabrieksmethode in een subklasse overschrijven om het type transport dat wordt gemaakt te wijzigen.

Laten we eens kijken naar het klassendiagram voor deze benadering:

Om dit systeem te laten werken, moeten alle geretourneerde objecten een gemeenschappelijke interface hebben. Subklassen kunnen objecten van verschillende klassen produceren die deze interface implementeren.

De klassen Truck en Car implementeren bijvoorbeeld de CourierTransport- interface met een aflevermethode . Elk van deze klassen implementeert de methode op een andere manier: vrachtwagens leveren vracht, terwijl auto's voedsel, pakketten, enzovoort leveren. De fabrieksmethode in de TruckCreator- klasse retourneert een vrachtwagenobject en de CarCreator- klasse retourneert een auto-object.

Voor de klant van de fabrieksmethode is er geen verschil tussen deze objecten, aangezien het ze zal behandelen als een soort abstract CourierTransport . De klant zal er veel om geven dat het object een methode heeft om te leveren, maar hoe die methode precies werkt, is niet belangrijk.

Implementatie in Java:


public interface CourierTransport {
	void deliver();
}
public class Car implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The package is being delivered by car");
	}
}
public class Truck implements CourierTransport {
	@Override
	public void deliver() {
    		System.out.println("The freight is being delivered by truck");
	}
}
public abstract class CourierTransportCreator {
	public abstract CourierTransport createTransport();
}
public class CarCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Car();
	}
}
public class TruckCreator extends CourierTransportCreator {
	@Override
	public CourierTransport createTransport() {
    		return new Truck();
	}
}
 
public class Delivery {
	private String address;
	private CourierTransport courierTransport;
 
	public void Delivery() {
	}
 
	public Delivery(String address, CourierTransport courierTransport) {
    	this.address = address;
    	this.courierTransport = courierTransport;
	}
 
	public CourierTransport getCourierTransport() {
    		return courierTransport;
	}
 
	public void setCourierTransport(CourierTransport courierTransport) {
    		this.courierTransport = courierTransport;
	}
 
	public String getAddress() {
    		return address;
	}
 
	public void setAddress(String address) {
    		this.address = address;
	}
}
public static void main(String[] args) {
    	// Accept a new type of order from the database (pseudocode)
    	String type = database.getTypeOfDeliver();
 
    	Delivery delivery = new Delivery();
    	
    	// Set the transport for delivery
        delivery.setCourierTransport(getCourierTransportByType(type));
    	
    	// Make the delivery
        delivery.getCourierTransport().deliver();
 
	}
 
	public static CourierTransport getCourierTransportByType(String type) {
    	switch (type) {
        	case "CarDelivery":
            	return new CarCreator().createTransport();
        	case "TruckDelivery":
            	return new TruckCreator().createTransport();
        	default:
            	throw new RuntimeException();
	    }
	}
    

Als we een nieuw leveringsobject willen maken, maakt het programma automatisch een geschikt transportobject aan.

Wanneer moeten we dit patroon toepassen?

1. Als u van tevoren niet weet met welke typen en afhankelijkheden de objecten waarmee uw code moet werken.

De fabrieksmethode scheidt de code voor het produceren van transportmiddelen van de code die het transport gebruikt. Hierdoor kan de code voor het maken van objecten worden uitgebreid zonder de rest van de code aan te raken.

Om bijvoorbeeld ondersteuning voor een nieuw type transport toe te voegen, moet u een nieuwe subklasse maken en daarin een fabrieksmethode definiëren die een exemplaar van het nieuwe transport retourneert.

2. Wanneer u systeembronnen wilt besparen door bestaande objecten te hergebruiken in plaats van nieuwe te maken.

Dit probleem treedt meestal op bij het werken met resource-intensieve objecten, zoals databaseverbindingen, bestandssystemen, enz.

Bedenk welke stappen je moet nemen om bestaande objecten te hergebruiken:

  1. Eerst moet u een gedeelde repository maken om alle objecten die u maakt op te slaan.

  2. Bij het aanvragen van een nieuw object moet u in de repository kijken of deze een beschikbaar object bevat.

  3. Retourneer het object naar de clientcode.

  4. Maar als er geen beschikbare objecten zijn, maak dan een nieuwe aan en voeg deze toe aan de repository.

Al deze code moet ergens worden geplaatst waar de clientcode niet onoverzichtelijk wordt. De handigste plaats zou de constructor zijn, aangezien we al deze controles alleen nodig hebben bij het maken van objecten. Helaas maakt een constructor altijd een nieuw object - het kan geen bestaand object retourneren.

Dat betekent dat er een andere methode nodig is die zowel bestaande als nieuwe objecten kan retourneren. Dit zal de fabrieksmethode zijn.

3. Wanneer u gebruikers wilt toestaan ​​delen van uw framework of bibliotheek uit te breiden.

Gebruikers kunnen uw framework-klassen uitbreiden door overerving. Maar hoe zorg je ervoor dat het raamwerk objecten van deze nieuwe klassen maakt in plaats van de standaardklassen?

De oplossing is om gebruikers niet alleen de componenten te laten uitbreiden, maar ook de klassen die deze componenten maken. En hiervoor moeten de makende klassen specifieke creatiemethoden hebben die kunnen worden gedefinieerd.

Voordelen

  • Ontkoppelt een klasse van specifieke transportklassen.
  • Bewaart de code voor het maken van transportmiddelen op één plek, waardoor de code gemakkelijker te onderhouden is.
  • Vereenvoudigt de toevoeging van nieuwe vervoerswijzen aan het programma.
  • Implementeert het open-gesloten principe.

Nadelen

Kan leiden tot grote parallelle klassehiërarchieën, aangezien elke productklasse zijn eigen maker-subklasse moet hebben.

Laten we samenvatten

U leerde over het patroon van de fabrieksmethode en zag een mogelijke implementatie. Dit patroon wordt vaak gebruikt in verschillende bibliotheken die objecten bieden voor het maken van andere objecten.

Gebruik het fabrieksmethodepatroon wanneer u eenvoudig nieuwe objecten of subklassen van bestaande klassen wilt toevoegen om te communiceren met uw belangrijkste bedrijfslogica en om uw code niet op te zwellen vanwege verschillende contexten.