Objektumgyár minta

3 minute read

Először is elnézést a cím miatt, de talán kis figyelmet kapok miatta a szakma jeles képviselőitől, akik nem értik, hogy mit akar ez jelenteni. A “Factory Pattern”-ről van szó, egy osztályszervezési mintáról, melyet modern programtervezéskor használunk és magas szintű nyelveken valósítunk meg (Például Java, C# és kicsit bátortalanul megemlítem a PHP-t is, merthogy most arról van szó a Zend certificate általam önképzéssel megvalósuló megszerzése céljából)

Tehát így kerül a csizma az asztalra, miután ezen túl vagyunk, csapjunk a leves közepébe. A Gang of Four: Design patterns művében legtöbbször Factory Method és Abstract Factory címszóval hivatkozik erre az objektumorientált programtervezési mintára. A továbbiakban szem előtt tartom, hogy egy szoftveren nem egyedül dolgozunk, ezért a másik fejlesztő által írt kódot, ami az én osztályaimat használja, kliens kódnak fogom nevezni. Tehát az itt tárgyalt mintának a kliens kódja szempontjából az az előnye, hogy anélkül tudja példányosítani egy belső osztályomat, hogy komolyabban kellene tanulmányoznia azt. Ezzel komoly munkaórák spórolhatók és átláthatóbb programkódot eredményez a használata.

A legegyszerűbb módja ennek, ha van egy factory osztályunk, annak pedig egy olyan eljárása, ami egy paraméter alapján “legyártja” a kívánt objektumot.

Ha például egy online fizetési módot szeretnénk megvalósítani PHP segítségével, az így fog kinézni:

class Payment {
  public $data;
  function __constructor($data) {
    $this->data = $data;
  }
  function process() {}
  function getResult() {}
}

class CreditCard extends Payment {}
class PayPal extends Payment {}

class PaymentFactory {
  const CREDIT_CARD = 1;
  const PAYPAL = 2;
  function createPayment($data) {
      switch ($data['type']) {
      case CREDIT_CARD : 
        return new CreditCard($data);;
      case PAYPAL :
        return new PayPal($data);
      default :
        return new Payment($data);
      }
  }
}

// kliens kód

$data = $_POST;
$pay = PaymentFactory::createPayment($data);
$pay->process();

Persze ez egy nagyon kigyomlált kód az érthetőség kedvéért, validálás nélkül használja a $_POST változót, de az majd egy másik történet lesz. Mint látható az, hogy melyik fizetési mód lesz példányosítva egy felhasználótól érkező form alapján dől el és ez meghatározhatja a következő lépést is, például a kártyaadatok bekérése vagy az átirányítást a PayPal fizetőoldalára. A kliens kódnak csak arról kell tudnia, hogy a form milyen értékeket adhat át az asszociatív tömb “type” részeként. Jelen esetben 1-et vagy 2-t, de tulajdonképpen ez a kód nagyon rugalmas, kibővíthető több fizetési móddal is. Továbbgondolva egyetlen hátránya, hogy a sok leaf osztály miatt nagyon megnőhet az a switch blokk és ugyan nem valósult meg kódduplikáció, de mégis szeretem kerülni a túl sok if-et és switch-et, ha objektumorientált programozásról beszélek.

Nézzük, mi van akkor, ha a kliens számára biztosítani szeretnénk, hogy a saját osztályait gyárthassa a saját feltételei alapján? Erre találták ki az Abstract Factory mintát. Az alábbi példa egy kártyát és egy virtuális fizetőeszköz implementálását várja el a kliens osztályától.

abstract class AbstractPaymentFactory {
  abstract function getCardPayment();
  abstract function getVirtualPayment();
}
interface PaymentInterface { function process(); }

class AmazonPaymentFactory extends AbstractPaymentFactory {
  function getCardPayment() {
    return new AmazonCreditCardPayment();
  }
  function getVirtualPayment() {
    return new AmazonMoneyBookersPayment();
  }
}

class EbayPaymentFactory extends AbstractPaymentFactory {
  function getCardPayment() {
    return new EbayCreditCardPayment();
  }
  function getVirtualPayment() {
    return new EbayPaypalPaypent();
  }
}

// a fizetési módok osztályai

class EbayCreditCardPayment implements PaymentInterface { function process() {} }
class EbayPaypalPaypent implements PaymentInterface { function process() {} }
class AmazonCreditCardPayment implements PaymentInterface { function process() {} }
class AmazonMoneyBookersPayment implements PaymentInterface { function process() {} }

// példányosítás

$factory = new AmazonPaymentFactory();
if($_POST['type'] == 1) {
  $pay = $factory->getCardPayment();
} else {
  $pay = $factory->getVirtualPayment();
}
$pay->process();

Ha olyan vagy mint én és szereted rendben tudni a kódodat, akkor most megszólalt a szirénád, mivel sokféle osztály esetén rengeteg kód duplikáció keletkezik fölöslegesen. A következő minta, ami megoldja ezt a problémát, a Decorator Pattern, de azt majd egy következő bejegyzésben…

Figyeljük meg, hogy mivel a PHP nem képes a visszatérési értékek type casting-jára, nem tudjuk elvárni a klienstől, hogy a getCardPayment() és getVirtualPayment() visszatérési értékei a PaymentInterface egyik megvalósítása legyen. Ez a nyelv hibája, amit az osztályunk dokumentációjával kell kompenzálnunk és megkövetelnünk, hogy minden esetben ilyen osztállyal tárjen vissza a kliens megvalósítása is.

Updated:

Comments