Ce este un model de design?

Modelele de proiectare sunt soluții la nivel de proiectare pentru probleme recurente pe care noi inginerii de software le întâlnim des. Nu este cod – repet, COD. Este ca o descriere a modului de abordare a acestor probleme și de proiectare a unei soluții.

Utilizarea acestor tipare este considerată o bună practică, deoarece proiectarea soluției este destul de încercată și testată, rezultând o lizibilitate mai mare a codului final. Modelele de design sunt deseori create și utilizate de limbajele OOP, cum ar fi Java, în care vor fi scrise cele mai multe exemple de aici.

Tipuri de modele de proiectare

Există aproximativ 26 de modele descoperite în prezent (cu greu cred că le voi face pe toate …).

Aceste 26 pot fi clasificate în 3 tipuri:

1. Creațional: Aceste modele sunt concepute pentru instanțierea clasei. Ele pot fi fie modele de creare de clase, fie modele de creare de obiecte.

ad-banner

2. Structural: Aceste modele sunt concepute în funcție de structura și compoziția unei clase. Scopul principal al majorității acestor modele este de a crește funcționalitatea clasei implicate, fără a schimba o mare parte din compoziția sa.

3. Comportamentale: Aceste modele sunt concepute în funcție de modul în care o clasă comunică cu ceilalți.

În această postare, vom trece printr-un model de proiectare de bază pentru fiecare tip clasificat.

Tipul 1: Creațional – Modelul de proiectare Singleton

Modelul de proiectare Singleton este un model de creație, al cărui obiectiv este să creeze o singură instanță a unei clase și să ofere un singur punct de acces global la acel obiect. Un exemplu utilizat în mod obișnuit al unei astfel de clase în Java este Calendar, unde nu puteți crea o instanță a acelei clase. De asemenea, își folosește propriul getInstance()metoda pentru a obține obiectul care urmează să fie utilizat.

O clasă care folosește modelul de design single va include,

Cele 3 tipuri de modele de design pe care toti
Diagrama clasei Singleton
  1. O variabilă statică privată, care deține singura instanță a clasei.
  2. Un constructor privat, deci nu poate fi instanțiat nicăieri altundeva.
  3. O metodă statică publică, pentru a returna singura instanță a clasei.

Există multe implementări diferite ale designului singleton. Astăzi, voi trece prin implementările;

1. Instanțare dornică

2. Instanțarea leneșă

3. Instantierea sigură a firului

Castor dornic

public class EagerSingleton {
	// create an instance of the class.
	private static EagerSingleton instance = new EagerSingleton();

	// private constructor, so it cannot be instantiated outside this class.
	private EagerSingleton() {  }

	// get the only instance of the object created.
	public static EagerSingleton getInstance() {
		return instance;
	}
}

Acest tip de instanțiere se întâmplă în timpul încărcării clasei, deoarece instanțierea instanței variabile se întâmplă în afara oricărei metode. Acest lucru prezintă un dezavantaj puternic dacă această clasă nu este utilizată deloc de aplicația client. Planul de urgență, dacă această clasă nu este utilizată, este Instanțarea leneșă.

Zile leneșe

Nu există prea multe diferențe față de implementarea de mai sus. Principalele diferențe constau în faptul că variabila statică este inițial declarată nulă și este instanțiată numai în cadrul getInstance() metoda if – și numai dacă – variabila de instanță rămâne nulă în momentul verificării.

public class LazySingleton {
	// initialize the instance as null.
	private static LazySingleton instance = null;

	// private constructor, so it cannot be instantiated outside this class.
	private LazySingleton() {  }

	// check if the instance is null, and if so, create the object.
	public static LazySingleton getInstance() {
		if (instance == null) {
			instance = new LazySingleton();
		}
		return instance;
	}
}

Aceasta rezolvă o problemă, dar încă există o alta. Ce se întâmplă dacă doi clienți diferiți accesează clasa Singleton în același timp, chiar în milisecundă? Ei bine, vor verifica dacă instanța este nulă în același timp și o vor găsi adevărată, astfel încât vor crea două instanțe ale clasei pentru fiecare cerere de către cei doi clienți. Pentru a remedia acest lucru, urmează să fie implementată instanțierea Thread Safe.

(Fir) Siguranța este cheia

În Java, cuvântul cheie sincronizat este utilizat pe metode sau obiecte pentru a implementa siguranța firelor, astfel încât doar un singur fir va accesa o anumită resursă simultan. Instanțierea clasei este plasată într-un bloc sincronizat, astfel încât metoda să poată fi accesată doar de un singur client la un moment dat.

public class ThreadSafeSingleton {
	// initialize the instance as null.
	private static ThreadSafeSingleton instance = null;

	// private constructor, so it cannot be instantiated outside this class.
	private ThreadSafeSingleton() {  }

	// check if the instance is null, within a synchronized block. If so, create the object
	public static ThreadSafeSingleton getInstance() {
		synchronized (ThreadSafeSingleton.class) {
			if (instance == null) {
				instance = new ThreadSafeSingleton();
			}
		}
		return instance;
	}
}

Cheltuielile generale pentru metoda sincronizată sunt ridicate și reduc performanța întregii operațiuni.

De exemplu, dacă variabila de instanță a fost deja instanțiată, atunci de fiecare dată când un client accesează fișierul getInstance() metoda, synchronized se execută metoda și performanța scade. Acest lucru se întâmplă doar pentru a verifica dacă instance valoarea variabilelor este nulă. Dacă constată că este, părăsește metoda.

Pentru a reduce această cheltuială, se folosește dublă blocare. Verificarea este utilizată înainte de synchronized metoda, de asemenea, și dacă valoarea este nulă singură, face synchronized rularea metodei.

// double locking is used to reduce the overhead of the synchronized method
public static ThreadSafeSingleton getInstanceDoubleLocking() {
	if (instance == null) {
		synchronized (ThreadSafeSingleton.class) {
			if (instance == null) {
				instance = new ThreadSafeSingleton();
			}
		}
	}
	return instance;
}

Acum la următoarea clasificare.

Tipul 2: Structural – Modelul de design al decoratorului

Îți voi oferi un mic scenariu pentru a oferi un context mai bun de ce și unde ar trebui să folosești modelul decorator.

Spuneți că dețineți o cafenea și, ca orice începător, începeți doar cu două tipuri de cafea simplă, amestecul casei și friptura întunecată. În sistemul dvs. de facturare, exista o singură clasă pentru diferite amestecuri de cafea, care moștenește clasa abstractă a băuturilor. Oamenii încep de fapt să vină și să-ți ia cafeaua minunată (deși amară?). Apoi, sunt noii cafele care, Doamne ferește, vor zahăr sau lapte. O astfel de trufie pentru cafea !! ??

Acum trebuie să aveți și aceste două suplimente, atât în ​​meniu, cât și din păcate în sistemul de facturare. Inițial, persoana dvs. IT va face o subclasă pentru ambele cafele, una care include zahăr, cealaltă lapte. Apoi, din moment ce clienții au întotdeauna dreptate, se spune aceste temute cuvinte:

„Pot să iau o cafea cu lapte, cu zahăr, te rog?”

???

Acolo merge sistemul tău de facturare râzând din nou în față. Ei bine, înapoi la planșa de desen …

Persoana IT adaugă apoi cafea cu lapte cu zahăr ca o altă subclasă la fiecare clasă de cafea părinte. Restul lunii este o navigație lină, oamenii se aliniază pentru a-ți lua cafeaua, de fapt câștigi bani. ??

Dar așteaptă, mai sunt!

Lumea este împotriva ta încă o dată. Un concurent se deschide peste drum, cu nu doar 4 tipuri de cafea, ci și mai mult de 10 suplimente! ?

Cumpărați toate acestea și multe altele, pentru a vinde singuri o cafea mai bună și chiar atunci amintiți-vă că ați uitat să actualizați acel sistem de facturare dratted. Este posibil să nu puteți face numărul infinit de subclase pentru orice combinație a tuturor suplimentelor, cu noile amestecuri de cafea. Ca să nu mai vorbim, dimensiunea sistemului final. ??

Este timpul să investiți într-un sistem de facturare adecvat. Găsiți personal IT nou, care știe de fapt ce fac și spun;

„De ce, acest lucru va fi mult mai ușor și mai mic dacă va folosi modelul decorator.”

Ce naiba este asta?

Modelul de design al decoratorului se încadrează în categoria structurală, care se ocupă de structura reală a unei clase, fie prin moștenire, compoziție sau ambele. Scopul acestui design este de a modifica funcționalitatea unui obiect în timpul rulării. Acesta este unul dintre multele alte modele de proiectare care utilizează clase abstracte și interfețe cu compoziția pentru a obține rezultatul dorit.

Să-i dăm lui Math o șansă (tremură?) Să aducă totul în perspectivă;

Luați 4 amestecuri de cafea și 10 suplimente. Dacă ne-am ținut de generația de subclase pentru fiecare combinație diferită a tuturor suplimentelor pentru un tip de cafea. Asta este;

(10-1) ² = 9² = 81 subclase

Scădem 1 din 10, deoarece nu puteți combina un supliment cu altul de același tip, zahărul cu zahărul sună prost. Și asta pentru un singur amestec de cafea. Înmulțiți asta 81 de 4 și primești o mare 324 subclase diferite! Vorbește despre toate codurile …

Dar, cu modelul decorator va fi nevoie de doar 16 clase în acest scenariu. Vrei să pariezi?

1611750429 359 Cele 3 tipuri de modele de design pe care toti
Schema clasei modelului de proiectare a decoratorilor
1611750429 875 Cele 3 tipuri de modele de design pe care toti
Diagrama clasei conform scenariului cafenelei

Dacă ne planificăm scenariul conform schemei de clasă de mai sus, obținem 4 clase pentru cele 4 amestecuri de cafea, 10 pentru fiecare supliment și 1 pentru componenta abstractă și încă 1 pentru decoratorul abstract. Vedea! 16! Acum predați cei 100 de dolari. ?? (jk, dar nu va fi refuzat dacă este dat … doar spunând)

După cum puteți vedea de sus, la fel cum amestecurile de cafea din beton sunt subclasele clasei abstracte de băuturi, clasa abstractă AddOn își moștenește metodele de la aceasta. Suplimentele, care sunt subclasele sale, moștenesc la rândul lor orice metode noi pentru a adăuga funcționalitate obiectului de bază atunci când este necesar.

Să trecem la codificare, pentru a vedea acest model în uz.

Mai întâi să facem clasa de băuturi abstracte, din care toate diferitele amestecuri de cafea vor moșteni de la:

public abstract class Beverage {
	private String description;
    
	public Beverage(String description) {
		super();
		this.description = description;
	}
    
	public String getDescription() {
		return description;
	}
    
	public abstract double cost();
}

Apoi, pentru a adăuga ambele clase de beton de amestec de cafea.

public class HouseBlend extends Beverage {
	public HouseBlend() {
		super(“House blend”);
	}

	@Override
	public double cost() {
		return 250;
	}
}

public class DarkRoast extends Beverage {
	public DarkRoast() {
		super(“Dark roast”);
	}

	@Override
	public double cost() {
		return 300;
	}
}

Clasa abstractă AddOn moștenește și din clasa abstractă a băuturilor (mai multe despre aceasta mai jos).

public abstract class AddOn extends Beverage {
	protected Beverage beverage;

	public AddOn(String description, Beverage bev) {
		super(description);
		this.beverage = bev;
	}

	public abstract String getDescription();
}

Și acum implementările concrete ale acestei clase abstracte:

public class Sugar extends AddOn {
	public Sugar(Beverage bev) {
		super(“Sugar”, bev);
	}

	@Override
	public String getDescription() {
		return beverage.getDescription() + “ with Mocha”;
	}

	@Override
	public double cost() {
		return beverage.cost() + 50;
	}
}

public class Milk extends AddOn {
	public Milk(Beverage bev) {
		super(“Milk”, bev);
	}

	@Override
	public String getDescription() {
		return beverage.getDescription() + “ with Milk”;
	}

	@Override  public double cost() {
		return beverage.cost() + 100;
	}
}

După cum puteți vedea mai sus, putem transmite orice subclasă de băuturi către orice subclasă de AddOn și obținem costul adăugat, precum și descrierea actualizată. Și, deoarece clasa AddOn este în esență de tipul băuturii, putem trece un AddOn într-un alt AddOn. În acest fel, putem adăuga orice număr de suplimente la un anumit amestec de cafea.

Acum, să scrieți un cod pentru a testa acest lucru.

public class CoffeeShop {
	public static void main(String[] args) {
		HouseBlend houseblend = new HouseBlend();
		System.out.println(houseblend.getDescription() + “: “ + houseblend.cost());

		Milk milkAddOn = new Milk(houseblend);
		System.out.println(milkAddOn.getDescription() + “: “ + milkAddOn.cost());

		Sugar sugarAddOn = new Sugar(milkAddOn);
		System.out.println(sugarAddOn.getDescription() + “: “ + sugarAddOn.cost());
	}
}

Rezultatul final este:

Cele 3 tipuri de modele de design pe care toti
PS acest lucru este în Rupii Sri Lanka

Functioneaza! Am reușit să adăugăm mai mult de un add-on la un amestec de cafea și să-i actualizăm cu succes costul final și descrierea, fără a fi nevoie să facem subclase infinite pentru fiecare combinație de supliment pentru toate amestecurile de cafea.

În cele din urmă, la ultima categorie.

Tipul 3: Comportamental – Modelul de proiectare a comenzilor

Un model de proiectare comportamentală se concentrează pe modul în care clasele și obiectele comunică între ele. Accentul principal al modelului de comandă este de a inculca un grad mai mare de cuplare liberă între părțile implicate (citiți: clase).

Uhhhh … Ce este asta?

Cuplarea este modul în care două (sau mai multe) clase care interacționează între ele, bine, interacționează. Scenariul ideal atunci când aceste clase interacționează este că nu depind foarte mult unul de celălalt. Este o cuplare slabă. Deci, o definiție mai bună pentru cuplarea liberă ar fi, clase care sunt interconectate, făcând cea mai mică utilizare una de cealaltă.

Necesitatea acestui tipar a apărut atunci când cererile trebuiau trimise fără a ști conștient ce solicitați sau cine este receptorul.

În acest model, clasa invocatoare este decuplată de clasa care efectuează de fapt o acțiune. Clasa invocator are numai metoda apelabilă executați, care execută comanda necesară, atunci când clientul o solicită.

Să luăm un exemplu de bază din lumea reală, comandând o masă la un restaurant elegant. Pe măsură ce curge fluxul, dați comanda (comanda) chelnerului (invocatorului), care apoi îl predă bucătarului (receptorului), astfel încât să puteți obține mâncare. S-ar putea să sune simplu … dar un pic meh la cod.

1611750429 638 Cele 3 tipuri de modele de design pe care toti

Ideea este destul de simplă, dar codarea merge în jurul nasului.

1611750429 690 Cele 3 tipuri de modele de design pe care toti
Diagrama clasei modelului de proiectare a comenzilor

Fluxul de funcționare din partea tehnică este, faceți o comandă concretă, care implementează interfața de comandă, cerând receptorului să finalizeze o acțiune și trimiteți comanda invocatorului. Invocatorul este persoana care știe când să dea această comandă. Bucătarul este singurul care știe ce să facă atunci când i se dă comanda / comanda specifică. Deci, când se execută metoda de executare a invocatorului, aceasta, la rândul său, determină executarea metodei de executare a obiectelor de comandă pe receptor, finalizând astfel acțiunile necesare.

Ceea ce trebuie să implementăm este;

  1. O comandă de interfață
  2. O comandă de clasă care implementează interfața de comandă
  3. Un ospătar de clasă (invocator)
  4. Un bucătar de clasă (receptor)

Deci, codarea merge așa:

Chef, receptorul

public class Chef {
	public void cookPasta() {
		System.out.println(“Chef is cooking Chicken Alfredo…”);
	}

	public void bakeCake() {
		System.out.println(“Chef is baking Chocolate Fudge Cake…”);
	}
}

Comandă, interfața

public interface Command {
	public abstract void execute();
}

Ordinea, comanda concretă

public class Order implements Command {
	private Chef chef;
	private String food;

	public Order(Chef chef, String food) {
		this.chef = chef;
		this.food = food;
	}

	@Override
	public void execute() {
		if (this.food.equals(“Pasta”)) {
			this.chef.cookPasta();
		} else {
			this.chef.bakeCake();
		}
	}
}

Chelner, invocatorul

public class Waiter {
	private Order order;

	public Waiter(Order ord) {
		this.order = ord;
	}

	public void execute() {
		this.order.execute();
	}
}

Tu, clientul

public class Client {
	public static void main(String[] args) {
		Chef chef = new Chef();
        
		Order order = new Order(chef, “Pasta”);
		Waiter waiter = new Waiter(order);
		waiter.execute();

		order = new Order(chef, “Cake”);
		waiter = new Waiter(order);
		waiter.execute();
	}
}

După cum puteți vedea mai sus, Clientul face o comandă și setează receptorul ca bucătar. Ordinul este trimis Chelnerului, care va ști când să execute Ordinul (adică când să-i dea bucătarului ordinul să gătească). Când invocatorul este executat, metoda de executare a comenzilor este executată pe receptor (adică bucătarului i se dă comanda fie să gătească paste? Fie să coacă tort?).

1*gwsVqEIKFmBj01M7dXsQ A
Rezultatul final al clientului

Recapitulare rapidă

În această postare am trecut prin:

  1. Ce este cu adevărat un model de design,
  2. Diferitele tipuri de modele de design și de ce sunt diferite
  3. Un model de proiectare de bază sau comun pentru fiecare tip

Sper că acest lucru a fost de ajutor.

Găsiți codul repo pentru postare, aici.