Fabrică (programare orientată pe obiecte) - Factory (object-oriented programming)

Image
Metoda fabricii în LePUS3

În programarea orientată pe obiecte (OOP) , o fabrică este un obiect pentru crearea altor obiecte - în mod formal o fabrică este o funcție sau o metodă care returnează obiecte cu un prototip sau o clasă variabilă dintr-un apel de metodă, care se presupune că este „nou”. Mai general, un subrutină care returnează un obiect „nou” poate fi denumit „fabrică”, ca în metoda fabrică sau funcția din fabrică . Acesta este un concept de bază în OOP și constituie baza pentru o serie de modele de proiectare software conexe .

Motivație

În programarea bazată pe clase , o fabrică este o abstractizare a unui constructor al unei clase, în timp ce în programarea bazată pe prototip o fabrică este o abstractizare a unui obiect prototip. Un constructor este concret prin faptul că creează obiecte ca instanțe ale unei singure clase și printr-un proces specificat (instanțierea clasei), în timp ce o fabrică poate crea obiecte prin instanțierea diferitelor clase sau prin utilizarea altor scheme de alocare, cum ar fi un pool de obiecte . Un obiect prototip este concret în care acesta este utilizat pentru a crea obiecte prin a fi clonat , în timp ce o fabrica poate crea obiecte prin clonare diverse prototipuri, sau prin alte scheme de alocare.

O fabrică poate fi implementată în diferite moduri. Cel mai adesea este implementat ca metodă, caz în care se numește metodă fabrică . Uneori este implementat ca o funcție, caz în care se numește funcție din fabrică . În unele limbi, constructorii sunt ei înșiși fabrici. Cu toate acestea, în majoritatea limbilor nu sunt, iar constructorii sunt invocați într-un mod care este idiomatic pentru limbă, cum ar fi prin utilizarea cuvântului cheie new, în timp ce o fabrică nu are un statut special și este invocată printr-o metodă obișnuită de apel sau apel de funcție. În aceste limbi, o fabrică este o abstractizare a unui constructor, dar nu strict o generalizare, deoarece constructorii nu sunt ei înșiși fabrici.

Terminologie

Terminologia diferă dacă conceptul de fabrică este el însuși un model de design - în Patterns de proiectare nu există un „model de fabrică”, ci în schimb două tipare ( model de metodă de fabrică și model de fabrică abstract ) care folosesc fabrici. Unele surse se referă la concept ca model de fabrică , în timp ce altele consideră conceptul în sine un idiom de programare , rezervând termenul „model de fabrică” sau „tipare de fabrică” unor modele mai complicate care folosesc fabrici, cel mai adesea modelul metodei fabricii; în acest context, conceptul de fabrică în sine poate fi denumit o fabrică simplă. În alte contexte, în special în limbajul Python, se folosește „fabrica” în sine, ca în acest articol. Mai general, „fabrică” poate fi aplicată nu doar unui obiect care returnează obiecte dintr-un apel de metodă, ci și unui subrutin care returnează obiecte, ca într-o funcție din fabrică (chiar dacă funcțiile nu sunt obiecte) sau metodei din fabrică. Deoarece în multe limbi fabricile sunt invocate apelând o metodă, conceptul general al unei fabrici este adesea confundat cu modelul specific de modelare a modelului de fabricație .

Utilizare

OOP oferă polimorfismul utilizării obiectelor prin expediere prin metodă , în mod formal subtip polimorfism printr-o singură expediere determinată de tipul obiectului pe care se numește metoda. Cu toate acestea, acest lucru nu funcționează pentru constructori, deoarece constructorii creează un obiect de un anumit tip, mai degrabă decât folosesc un obiect existent. Mai concret, atunci când este chemat un constructor, nu există încă niciun obiect pe care să fie trimis.

Utilizarea fabricilor în loc de constructori sau prototipuri permite utilizarea polimorfismului pentru crearea obiectelor, nu numai utilizarea obiectelor. Mai exact, utilizarea fabricilor asigură încapsularea și înseamnă că codul nu este legat de anumite clase sau obiecte și, astfel, ierarhia de clase sau prototipurile pot fi schimbate sau refacturate fără a fi nevoie să se schimbe codul care le folosește - se abstrag de la ierarhia de clase sau prototipuri.

Mai tehnic, în limbile în care fabricile generalizează constructorii, fabricile pot fi utilizate de obicei oriunde pot fi constructorii, ceea ce înseamnă că interfețele care acceptă un constructor pot accepta, de asemenea, în general o fabrică - de obicei, este nevoie doar de ceva care creează un obiect, mai degrabă decât de a specifica o clasă și instanțierea.

De exemplu, în Python, collections.defaultdictclasa are un constructor care creează un obiect de tip defaultdictale cărui valori implicite sunt produse prin invocarea unei fabrici. Fabrica este transmisă ca argument către constructor și poate fi ea însăși un constructor sau orice lucru care se comportă ca un constructor - un obiect apelabil care returnează un obiect, adică o fabrică. De exemplu, folosind listconstructorul pentru liste:

# collections.defaultdict([default_factory[, ...]])
d = defaultdict(list)

Crearea obiectelor

Fabrica de obiecte sunt folosite în situații în care menținere a unui obiect de un anumit tip este un proces mai complex decât pur și simplu crearea unui nou obiect, în special în cazul în care se dorește alocarea sau inițializarea complexe. Unele dintre procesele necesare în crearea unui obiect care includ determinarea obiect pentru a crea, gestiona durata de viață a obiectului, și de gestionare se referă la acumulărilor și-dărâma specializate ale obiectului. Obiectul din fabrică poate decide să creeze dinamic clasa obiectului (dacă este cazul), să o returneze dintr-un pool de obiecte , să facă configurații complexe pe obiect sau alte lucruri. În mod similar, folosind această definiție, un singleton implementat de modelul singleton este o fabrică formală - returnează un obiect, dar nu creează obiecte noi dincolo de instanța unică.

Exemple

Cel mai simplu exemplu de fabrică este o funcție simplă de fabrică, care invocă doar un constructor și returnează rezultatul. În Python, o funcție din fabrică fcare instanțiază o clasă Apoate fi implementată ca:

def f():
    return A()

O funcție simplă din fabrică care implementează modelul singleton este:

def f():
    if f.obj is None:
        f.obj = A()
    return f.obj

f.obj = None

Aceasta va crea un obiect la prima apelare și va reveni întotdeauna același obiect după aceea.

Sintaxă

Fabricile pot fi invocate în diferite moduri, cel mai adesea un apel de metodă (o metodă din fabrică ), uneori prin apelarea ca funcție dacă fabrica este un obiect apelabil (o funcție din fabrică ). În unele limbi constructorii și fabricile au sintaxă identică, în timp ce în altele constructorii au sintaxă specială. În limbile în care constructorii și fabricile au sintaxă identică, cum ar fi Python, Perl, Ruby, Object Pascal și F #, constructorii pot fi înlocuiți în mod transparent cu fabrici. În limbile în care acestea diferă, trebuie să le deosebim în interfețe, iar comutarea între constructori și fabrici necesită schimbarea apelurilor.

Semantică

În limbile în care obiectele sunt alocate dinamic , ca în Java sau Python, fabricile sunt echivalente semantic cu constructorii. Cu toate acestea, în limbi precum C ++ care permit alocarea statică a unor obiecte, fabricile sunt diferite de constructorii pentru clasele alocate static, deoarece aceștia din urmă pot avea alocarea de memorie determinată la momentul compilării, în timp ce alocarea valorilor returnate ale fabricilor trebuie determinată la timpul de rulare. Dacă un constructor poate fi transmis ca argument unei funcții, atunci invocarea constructorului și alocarea valorii returnate trebuie făcute dinamic în timp de rulare și, astfel, au semantică similară sau identică cu invocarea unei fabrici.

Modele de design

Fabricile sunt utilizate în diferite modele de proiectare , în special în modele de creație, cum ar fi biblioteca de obiecte de tipar de proiectare . Au fost dezvoltate rețete specifice pentru a le implementa în multe limbi. De exemplu, mai multe „ tipare GoF ”, cum ar fi „ modelul metodei Factory ”, „ Builder ” sau chiar „ Singleton ” sunt implementări ale acestui concept. „Modelul abstract al fabricii ” este în schimb o metodă de a construi colecții de fabrici.

În unele modele de proiectare, un obiect din fabrică are o metodă pentru fiecare tip de obiect pe care este capabil să îl creeze. Aceste metode acceptă opțional parametrii care definesc modul în care este creat obiectul și apoi returnează obiectul creat.

Aplicații

Obiectele din fabrică sunt obișnuite în seturile de instrumente și cadre în care codul bibliotecii trebuie să creeze obiecte de tipuri care pot fi subclasate de aplicațiile care utilizează cadrul. Ele sunt, de asemenea, utilizate în dezvoltarea test-driven pentru a permite testarea claselor.

Fabricile determină tipul concret de obiect concret care trebuie creat și tocmai aici se creează obiectul. Deoarece fabrica returnează doar o interfață abstractă la obiect, codul de client nu știe - și nu este împovărat de - tipul de beton real al obiectului care tocmai a fost creat. Cu toate acestea, tipul unui obiect concret este cunoscut de fabrica abstractă. În special, aceasta înseamnă:

  • Codul clientului nu are nicio cunoștință de tipul concret , nu trebuie să includă fișiere antet sau declarații de clasă referitoare la tipul concret. Codul clientului se ocupă doar de tipul abstract. Obiectele de tip concret sunt într-adevăr create de fabrică, dar codul client accesează astfel de obiecte numai prin interfața lor abstractă .
  • Adăugarea de noi tipuri concrete se face prin modificarea codului clientului pentru a utiliza o fabrică diferită, o modificare care este de obicei o linie într-un singur fișier. Acest lucru este semnificativ mai ușor decât modificarea codului clientului pentru a crea un nou tip, care ar necesita schimbarea fiecărei locații din codul în care este creat un nou obiect.

Aplicabilitate

Fabricile pot fi utilizate atunci când:

  1. Crearea unui obiect face reutilizarea imposibilă fără o duplicare semnificativă a codului.
  2. Crearea unui obiect necesită acces la informații sau resurse care nu ar trebui să fie conținute în clasa de compunere.
  3. Gestionarea pe viață a obiectelor generate trebuie să fie centralizată pentru a asigura un comportament consecvent în cadrul aplicației.

Unitati de productie, în special metodele de fabricație, sunt comune în pachete de instrumente și cadre , în cazul în care codul de bibliotecă are nevoie pentru a crea obiecte de tipuri care pot fi subclasată de aplicații folosind cadrul.

Ierarhiile de clase paralele necesită adesea obiecte dintr-o ierarhie pentru a putea crea obiecte adecvate din alta.

Metodele din fabrică sunt folosite în dezvoltarea testată pentru a permite testarea claselor. Dacă o astfel de clasă Foocreează un alt obiect Dangerouscare nu poate fi supus testelor unitare automate (poate comunică cu o bază de date de producție care nu este întotdeauna disponibilă), atunci crearea Dangerousobiectelor este plasată în metoda fabricii virtualecreateDangerous din clasă Foo. Pentru testare, TestFoo(o subclasă de Foo) este apoi creată, cu metoda fabricii virtuale createDangeroussuprascrisă pentru a crea și a returna FakeDangerous, un obiect fals . Testele unitare sunt apoi utilizate TestFoopentru a testa funcționalitatea Foofără a provoca efectul secundar al utilizării unui Dangerousobiect real .

Beneficii și variante

Pe lângă utilizarea în modelele de proiectare, fabricile, în special metodele din fabrică, au diverse avantaje și variații.

Denumiri descriptive

O metodă fabrică are un nume distinct. În multe limbaje orientate obiect, constructorii trebuie să aibă același nume cu clasa în care se află, ceea ce poate duce la ambiguitate dacă există mai multe modalități de a crea un obiect (vezi supraîncărcarea ). Metodele din fabrică nu au o astfel de constrângere și pot avea nume descriptive; acestea sunt uneori cunoscute ca constructori alternativi . De exemplu, atunci când numerele complexe sunt create din două numere reale, numerele reale pot fi interpretate ca coordonate carteziene sau polare, dar folosind metode din fabrică, semnificația este clară, așa cum este ilustrat în următorul exemplu din C #.

    public class Complex
    {
        public double real;
        public double imaginary;

        public static Complex FromCartesian(double real, double imaginary)
        {
            return new Complex(real, imaginary);
        }

        public static Complex FromPolar(double modulus, double angle)
        {
            return new Complex(modulus * Math.Cos(angle), modulus * Math.Sin(angle));
        }

        private Complex(double real, double imaginary)
        {
            this.real = real;
            this.imaginary = imaginary;
        }
    }

Complex product = Complex.FromPolar(1, Math.PI);

Când metodele din fabrică sunt utilizate pentru dezambiguizare ca aceasta, constructorii bruti sunt adesea făcute private pentru a forța clienții să utilizeze metodele din fabrică.

Incapsularea

Metodele din fabrică încapsulează crearea de obiecte. Acest lucru poate fi util dacă procesul de creație este foarte complex; de exemplu, dacă depinde de setările din fișierele de configurare sau de introducerea utilizatorului.

Luați ca exemplu un program care citește fișiere imagine . Programul acceptă diferite formate de imagine, reprezentate de o clasă de cititor pentru fiecare format.

De fiecare dată când programul citește o imagine, trebuie să creeze un cititor de tipul adecvat pe baza unor informații din fișier. Această logică poate fi încapsulată într-o metodă din fabrică. Această abordare a fost denumită și Fabrica simplă.

Java

public class ImageReaderFactory {
    public static ImageReader createImageReader(ImageInputStreamProcessor iisp) {
        if (iisp.isGIF()) {
            return new GifReader(iisp.getInputStream());
        }
        else if (iisp.isJPEG()) {
            return new JpegReader(iisp.getInputStream());
        }
        else {
            throw new IllegalArgumentException("Unknown image type.");
        }
    }
}

PHP

class Factory
{
    public static function build($type)
    {
        $class = "Format" . $type;
        if (!class_exists($class)) {
            throw new Exception("Missing format class.");
        }
        return new $class;
    }
}

interface FormatInterface {}

class FormatString implements FormatInterface {}
class FormatNumber implements FormatInterface {}

try {
    $string = Factory::build("String");
} catch (Exception $e) {
    echo $e->getMessage();
}

try {
    $number = Factory::build("Number");
} catch (Exception $e) {
    echo $e->getMessage();
}

Limitări

Există trei limitări asociate cu utilizarea metodei din fabrică. Primul se referă la refactorizarea codului existent; celelalte două se referă la extinderea unei clase.

  • Prima limitare este că refacerea unei clase existente pentru a utiliza fabricile sparg clienții existenți. De exemplu, dacă clasa Complex ar fi o clasă standard, ar putea avea numeroși clienți cu cod precum:
    Complex c = new Complex(-1, 0);
    
Odată ce ne dăm seama că sunt necesare două fabrici diferite, schimbăm clasa (la codul prezentat mai devreme ). Dar, deoarece constructorul este acum privat, codul client existent nu se mai compilează.
  • A doua limitare este că, deoarece modelul se bazează pe utilizarea unui constructor privat, clasa nu poate fi extinsă. Orice subclasă trebuie să invoce constructorul moștenit, dar acest lucru nu se poate face dacă acel constructor este privat.
  • A treia limitare este că, dacă clasa ar fi extinsă (de exemplu, prin protejarea constructorului - acest lucru este riscant, dar fezabil), subclasa trebuie să furnizeze propria sa reimplementare a tuturor metodelor din fabrică cu exact aceleași semnături. De exemplu, dacă clasa StrangeComplexse extinde Complex, atunci dacă nu StrangeComplexfurnizează propria versiune a tuturor metodelor din fabrică, apelul
    StrangeComplex.FromPolar(1, Math.Pi);
    
    va produce o instanță a Complex(superclasei) mai degrabă decât instanța așteptată a subclasei. Caracteristicile de reflecție ale unor limbi pot evita această problemă.

Toate cele trei probleme ar putea fi atenuate prin modificarea limbajului de programare subiacent pentru a face din fabrici membrii clasei de primă clasă (vezi și clasa Virtuală ).

Note

Referințe