Wzorce programistyczne – Fabryka abstrakcyjna (Abstract Factory) w C++ i PHP

Wzorzec programistyczny typu Abstract Factory jest jednym ze wzorców służących do tworzenia obiektów. Jest to rozszerzony przypadek innego wzorca programistycznego – klasycznej Fabryki. W klasycznym wzorcu typu Fabryka, klasa fabrykująca posiadała metody, które tworzyły instancje obiektów. Możliwe było dziedziczenie z takowej Fabryki w celu nadpisywania odpowiednich metod i tym samym zmienianie poszczególnych sposobów kreacji. W przypadku Fabryki Abstrakcyjnej posuwamy się o jeden krok wyżej. Mamy kilka Fabryk udostępniających ten sam interface i w zależności od okoliczności, powołujemy do życia inną Fabrykę służącą użytkownikowi do kreacji obiektów. Użytkownik jest odseparowany i nie informowany o faktycznych typach obiektów, a jedynie posługuje się wspólnym interfacem.

Przykład w PHP

Pierwszy przykład tego wzorca będzie zaprezentowany za pomocą PHP. W przykładzie będzie chodzić o to, iż Fabryka automatycznie będzie sprawdzać, czy zapytanie HTTP mają być dokonywane poprzez cURL, czy poprzez fopen.

Najpierw zdefiniujemy prosty interface opisujący implementowane funkcjonalności.

interface Request{
  public function getFrom($url);
}

Teraz zdefiniujemy pierwszą klasę, która będzie odpowiadać zapytaniu za pomocą cURL.

class curlRequest implements Request {
  public function getFrom($url) {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $r = curl_exec($ch);
    return $r;
  }
}

Teraz zdefiniujemy drugą klasę, która będzie odpowiadać zapytaniu za pomocą fopen.

class streamRequest implements Request {
  public function getFrom($url) {
    array('http' => array(
      'method' => 'POST',
    ));
 
    $ctx = stream_context_create($params);
    $fp = fopen($url, 'rb', false, $ctx);
    $r = stream_get_contents($fp);
    return $r;
  }
}

Klasy odpowiadające za dokonywanie zapytań mamy gotowe. Teraz zastosujemy nasz wzorzec, czyli zdefiniujemy fabryki. Najpierw prosty interface, który zdefiniuje nam funkcjonalności, które ma posiadać każda fabryka.

interface requestFactory {
  public function createRequest();
}

Teraz pierwsza fabryka, która będzie tworzyć zapytanie typu cURL.

class curlFactory implements requestFactory {
    public function createRequest() {
        return new curlRequest();
    }
}

Teraz druga fabryka, która będzie tworzyć zapytanie typu fopen.

class streamFactory implements requestFactory {
    public function createRequest() {
        return new streamRequest();
    }
}

A teraz jeszcze kod, który decyduje, którą fabrykę zastosować.

class Application {
  public function getRequestFactory() {
    if (extension_loaded('curl')) {
      return new curlFactory();
    }
    else {
      return new streamFactory();
    }
  }
}

No i przykładowe wywołanie.

$a = new Application();
$b = $a->getRequestFactory();
 
$c = $b->createRequest();
 
echo $c->getFrom('http://localhost');

Przykład w C++

Teraz bardziej abstrakcyjny przykład zapisany w C++. Najpierw klasy końcowe.

class Button {
  public:
    virtual void render() = 0;
};
 
class WindowsButton: public Button {
  public:
    void render() {
      cout << "Windows Button";
    }
};
 
class LinuxButton: public Button {
  public:
    void render() {
      cout << "Linux Button";
    }
};

Teraz zdefiniujemy Fabryki.

class ButtonFactory {
  public:
    virtual Button* createButton() = 0;
};
 
class WindowsFactory: public ButtonFactory {
  public:
    Button* createButton() {
      return new WindowsButton();
    }
};
 
class LinuxFactory: public ButtonFactory {
  public:
    Button* createButton() {
      return new LinuxButton();
    }
};

I na koniec jeszcze kod wybierający Fabrykę.

class Application {
  public:
    int system;
 
    ButtonFactory* getButtonFactory() {
      if(system == 1) return new WindowsFactory();
      else if(system == 2) return new LinuxFactory();
  }
};

A teraz przykładowe wywołanie.

int main()
{
    Application* a = new Application();
    a->system = 2;
 
    ButtonFactory* b = a->getButtonFactory();
 
    Button* c = b->createButton();
    c->render();
}