07 - Altri pattern

Factory Method

link
Immaginate di avere una logica che rimane la stessa ma può lavorare con diversi oggetti che implementano un'interfaccia comune. In questo caso, in modo simile al Template Method, è possibile definire la logica comune in una classe astratta e lasciare da implementare un metodo che torna la tipologia di oggetto specifica sulla quale eseguire la logica.

Uno degli utilizzi più comuni è quello delle interfacce grafiche, immaginate un menù contestuale che deve funzionare allo stesso modo sia su mobile che su desktop. Si può definire la logica su una classe astratta e realizzare due classi che la estendono e implementano il metodo createItem. Questo metodo torna oggetti di interfaccia MenuItem, e nella classe DesktopContextMenu verrà crate creato un DesktopMenuItem, in quella web un HTMLMenuItem

interface MenuItem {
	render(): void;
	onClick(): void;
}

class DesktopMenuItem implements MenuItem {
	constructor(public title: string) { }
	
	render() {
		//visualizzazione su desktop
	}
	onClick() {
		//logica del click
	}
}

class HTMLMenuItem implements MenuItem {
	constructor(public title: string) { }
	
	render() {
		//visualizzazione html
	}
	onClick() {
		//logica del click
	}
}

abstract class ContextMenu {
	private items: MenuItem[] = [];

	addItem(title: string) {
		const item = this.createItem(title);
		this.items.push(item);
	}

	render() {
		// render base del menu e poi
		this.items.forEach(item => item.render());
	}

	abstract protected createItem(title: string): MenuItem;
}

class DesktopContextMenu extends ContextMenu {
	protected createItem(title) {
		return new DesktopMenuItem(title);
	}
}

class HTMLContextMenu extends ContextMenu {
	protected createItem(title) {
		return new HTMLMenuItem(title);
	}
}

Abstract Factory

link
Simile a Factory Method, ma serve per creare famiglie di oggetti collegati tra loro.
Immaginiamo di dover creare altri elementi grafici come nell'esempio precedente, bottoni, dialog, textinput, ecc.
In questo caso si può predisporre il codice in modo da creare gli elementi sempre tramite un oggetto di tipo UiFactory, che espone i metodi createButton, createDialog, ecc, ed avere una implementazione di UiFactory specifica per creare elementi desktop e una per creare elementi HTML.
Gli elementi condividono tra loro la stessa interfaccia, così come le factory tra di loro, in questo modo l'applicazione può lavorare usando le definizioni astratte o interfacce rimanendo all'oscuro di quale implementazione specifica si sta utilizzando.

Singleton

link
E' un pattern che assicura che esista una singola istanza della classe e che vi sia un punto d'accesso comune per tutta l'app a quell'istanza.

Questo è utile quando la classe deve avere uno state globale per l'intero codice. Un esempio classico è una classe che espone le configurazioni di avvio dell'app, oppure una che gestisce la connessione al db, in modo che venga riutilizzata in tutti i punti dell'app.

La tipica implementazione di un singleton è:

class Connection {
	protected static instance: Connection = null;

	static getConnection() {
		if (Connection.instance === null) {
			Connection.instance = new Connection();
		}
		return Connection.instance;
	}

	constructor() {
		// logica dell'instanza
	}
	
	// logica dell'instanza
}

Composite

link
E' un pattern strutturale che prevede di fare condividere sia alle foglie sia ai nodi un'interfaccia comune, in modo che ogni nodo sia responsabile per eseguire una determinata logica per il sottoalbero di sua competenza, delegando parte dell'esecuzione ai nodi e alle foglie che lo compongono, in modo ricorsivo.

Esercizi

Libro

Un libro è composto da pagine, eventualmente organizzate in sezioni. Ogni sezione può contenere sezioni (una o più) e pagine semplici. Eʼ possibile stampare una pagina singola, una sezione o lʼintero libro.

interface BookPart {
	print(): void;
}

class Section implements BookPart {
	protected children: BookPart[];

	constructor(protected title: string) {}

	addComponent(component: BookPart) {
		this.children.push(component);
	}

	print() {
		console.log(this.title);
		this.components.forEach(comp => comp.print());
	}
}

class Page implements BookPart {
	constructor(protected content: string,
				protected pageNumber: number) {}
	print() {
		console.log(this.content);
		console.log('-----------------');
		console.log(this.pageNumber);
	}
}

class Application {
	const book = new Section('Capitolo 1');
	const sub1 = new Section('Capitolo 1.1');
	const page = new Page('page content', 1);
	sub1.addComponent(page);
	book.addComponent(sub1);
	// e via così

	book.print(); // chiamerà ricorsivamente i print delle varie componenti
}


Nuova autenticazione

State eseguendo la migrazione a un nuovo provider di autenticazione perché dovete abilitare l'autenticazione a due fattori, purtroppo per voi non potevate passare tutti gli utenti automaticamente al nuovo sistema perché devono andare ad aggiornare i loro dati e configurare i dispositivi.
Quindi in questo momento avete due servizi che vi dicono se un utente è autenticato, chiamiamoli AuthProvider e LegacyProvider.
Avete già mandato una mail a tutti gli utenti dicendo che questo passaggio è obbligatorio e che se entro 6 mesi non aggiorneranno i dati l'account verrà sospeso. Però nel frattempo dovete dare la possibilità di accedere sia col nuovo Auth sia con Legacy.
Tra 6 mesi andrete a rimuovere Legacy e a bloccare gli account.

Quale design pattern usereste? Descrivere le classi coinvolte e il flusso logico delle operazioni.