A questo punto, probabilmente hai già incontrato modelli di progettazione. Ad esempio, singolo .

Ricordiamo cosa sono i modelli, perché sono necessari e quali sono i modelli di creazione (singleton è un esempio). Studieremo anche un nuovo modello: il metodo factory.

Nello sviluppo del software, un modello di progettazione è un costrutto architettonico ripetibile che rappresenta una soluzione a un problema di progettazione all'interno di un contesto ricorrente.

In genere, un modello non è una soluzione finale che può essere convertita direttamente in codice. È solo una soluzione modello a un problema che può essere utilizzata in varie situazioni.

I modelli di creazione sono modelli di progettazione che si occupano del processo di creazione di oggetti. Consentono di creare un sistema indipendente dal metodo utilizzato per creare, comporre e presentare gli oggetti.

Un metodo factory è un modello di progettazione creazionale che definisce un'interfaccia comune per la creazione di oggetti in una classe genitore, dando ai suoi discendenti la possibilità di creare questi oggetti. Al momento della creazione, i discendenti possono determinare quale classe creare.

Quale problema risolve il modello?

Immagina di decidere di creare un programma di consegna. Inizialmente, assumerai corrieri con auto e utilizzerai aAutoobiettare per rappresentare un veicolo di consegna nel programma. I corrieri consegnano i pacchi dal punto A al punto B e così via. Vai tranquillo.

Il programma sta guadagnando popolarità. La tua attività è in crescita e vuoi espanderti in nuovi mercati. Ad esempio, potresti iniziare anche a consegnare cibo e spedire merci. In questo caso, il cibo può essere consegnato dai corrieri a piedi, in scooter e in bicicletta, ma per il trasporto sono necessari i camion.

Ora devi tenere traccia di diverse cose (quando, a chi, cosa e quanto verrà consegnato), incluso quanto può trasportare ogni corriere. Le nuove modalità di trasporto hanno velocità e capacità diverse. Quindi noti che la maggior parte delle entità nel tuo programma sono fortemente legate alAutoclasse. Ti rendi conto che per far funzionare il tuo programma con altri metodi di consegna, dovrai riscrivere il codice base esistente e farlo di nuovo ogni volta che aggiungi un nuovo veicolo.

Il risultato è un codice orrendo pieno di istruzioni condizionali che eseguono azioni diverse a seconda del tipo di trasporto.

La soluzione

Il pattern del metodo factory suggerisce di creare oggetti chiamando uno speciale metodo factory invece di utilizzare direttamente l' operatore new . Le sottoclassi della classe che ha il metodo factory possono modificare gli oggetti creati dei veicoli specifici. A prima vista, questo può sembrare inutile: abbiamo semplicemente spostato la chiamata del costruttore da un punto all'altro del programma. Ma ora puoi sovrascrivere il metodo factory in una sottoclasse per modificare il tipo di trasporto creato.

Diamo un'occhiata al diagramma delle classi per questo approccio:

Affinché questo sistema funzioni, tutti gli oggetti restituiti devono avere un'interfaccia comune. Le sottoclassi saranno in grado di produrre oggetti di classi diverse che implementano questa interfaccia.

Ad esempio, le classi Truck e Car implementano l' interfaccia CourierTransport con un metodo deliver . Ognuna di queste classi implementa il metodo in modo diverso: i camion consegnano merci, mentre le auto consegnano cibo, pacchi e così via. Il metodo factory nella classe TruckCreator restituisce un oggetto camion e la classe CarCreator restituisce un oggetto auto.

Per il client del metodo factory, non c'è differenza tra questi oggetti, poiché li tratterà come una sorta di CourierTransport astratto . Il cliente si preoccuperà profondamente che l'oggetto abbia un metodo per la consegna, ma non è importante come funzioni esattamente quel metodo.

Implementazione 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();
	    }
	}
    

Se vogliamo creare un nuovo oggetto di consegna, il programma crea automaticamente un oggetto di trasporto appropriato.

Quando dovremmo applicare questo modello?

1. Quando non si conoscono in anticipo i tipi e le dipendenze degli oggetti con cui il codice deve lavorare.

Il metodo factory separa il codice per la produzione di forme di trasporto dal codice che utilizza il trasporto. Di conseguenza, il codice per la creazione di oggetti può essere esteso senza toccare il resto del codice.

Ad esempio, per aggiungere il supporto per un nuovo tipo di trasporto, è necessario creare una nuova sottoclasse e definire in essa un metodo factory che restituisca un'istanza del nuovo trasporto.

2. Quando si desidera risparmiare risorse di sistema riutilizzando oggetti esistenti invece di crearne di nuovi.

Questo problema si verifica in genere quando si lavora con oggetti ad alta intensità di risorse, come connessioni a database, file system, ecc.

Pensa ai passaggi che devi compiere per riutilizzare gli oggetti esistenti:

  1. Innanzitutto, devi creare un repository condiviso per archiviare tutti gli oggetti che crei.

  2. Quando si richiede un nuovo oggetto, è necessario cercare nel repository e verificare se contiene un oggetto disponibile.

  3. Restituire l'oggetto al codice client.

  4. Ma se non ci sono oggetti disponibili, creane uno nuovo e aggiungilo al repository.

Tutto questo codice deve essere messo in un posto che non ingombri il codice client. Il posto più conveniente sarebbe il costruttore, poiché abbiamo bisogno di tutti questi controlli solo durante la creazione di oggetti. Purtroppo, un costruttore crea sempre un nuovo oggetto, non può restituire un oggetto esistente.

Ciò significa che è necessario un altro metodo in grado di restituire sia oggetti esistenti che nuovi. Questo sarà il metodo di fabbrica.

3. Quando si desidera consentire agli utenti di estendere parti del framework o della libreria.

Gli utenti possono estendere le classi del framework tramite ereditarietà. Ma come si fa a fare in modo che il framework crei oggetti di queste nuove classi piuttosto che quelli standard?

La soluzione è consentire agli utenti di estendere non solo i componenti, ma anche le classi che creano tali componenti. E per questo, le classi di creazione devono avere metodi di creazione specifici che possono essere definiti.

Vantaggi

  • Separa una classe da classi di trasporto specifiche.
  • Mantiene il codice per la creazione di forme di trasporto in un'unica posizione, semplificando la gestione del codice.
  • Semplifica l'aggiunta di nuove modalità di trasporto al programma.
  • Implementa il principio aperto-chiuso.

Svantaggi

Può portare a grandi gerarchie di classi parallele, poiché ogni classe di prodotto deve avere la propria sottoclasse di creatori.

Riassumiamo

Hai imparato a conoscere il modello del metodo di fabbrica e hai visto una possibile implementazione. Questo modello viene spesso utilizzato in varie librerie che forniscono oggetti per la creazione di altri oggetti.

Usa il pattern del metodo factory quando vuoi aggiungere facilmente nuovi oggetti di sottoclassi di classi esistenti per interagire con la tua logica di business principale e non gonfiare il tuo codice a causa di contesti diversi.