poniedziałek, 23 stycznia 2012

Jak programować obiektowo cz. 6 - static

wstęp

Część z Was zapewne zauważyła, że w poprzednich wpisach pominąłem kilka istotnych rzeczy dotyczących atrybutów klas, ich metod, czy też samych klas. W kolejnych wpisach postaram się nadrobić tą zaległość.

Na pierwszy ogień idzie słowo kluczowe static. Z tym, że pominę kwestię używania tego słowa jako referencji do klasy, o czym pisałem tutaj.
Co oznacza użycie statycznej metody bądź atrybutu? Jest to równoznaczne z tym, że do jej/jego używania nie potrzebujemy tworzyć instancji danej klasy.

statycznie, tylko czy logicznie?

Poniżej przedstawiam dwa użycia atrybutów statycznych, które widziałem (w mniej lub bardziej zmienionej formie) na paru blogach, tutorialach, jako przykład ich zastosowania.

Przykład pierwszy, czyli zliczanie instancji danej klasy:
<?php
class Customer
{
  static private $_customerCounter = 0;
  public function __construct() 
  { 
    self::$_customerCounter++;
  }
}
Czy jest to poprawne użycie? Jeżeli rzeczywiście stając się klientem nabywamy informację (bo każda instancja klasy ma dostęp do swoich statycznych atrybutów) nt. tego, którym klientem tego sklepu/usługi/etc., to jak najbardziej. Z tym, że tak nie jest. Takie informacje powinny gromadzić inne obiekty, które są odpowiedzialne za wszelkiego rodzaju dane statystyczne.

Drugim, często powtarzanym przykładem, jest tworzenie obiektu, dla którego musimy mieć unikalny identyfikator:
<?php
class Employee
{
  static private $_nextId = 1;
  private $_id;

  public function __construct() 
  { 
    $this->_id = self::$_nextId;
    self::$_nextId++;
  }
}
I tutaj również sposób użycia statycznego atrybutu trochę kłóci się z logiką, bo generowanie unikalnego identyfikatora (nawet jeżeli miałaby to być inkrementowana liczba całkowita) powinno zostać przerzucone na inny obiekt. Zostając pracownikiem danej firmy otrzymujemy identyfikator (gdzieś musi on zostać wyprodukowany/wygenerowany), pracownik nie ma (i nie powinien posiadać) żadnej wiedzy na temat tego, w jaki sposób ten identyfikator jest tworzony.

singleton

Jednym z przykładów poprawnego zastosowania atrybutów i metod statycznych jest wzorzec Singleton.
Mamy tam wyraźne uzasadnienie tego, z jakich powodów używamy statycznego atrybutu i metody - musimy zapewnić stworzenie tylko jednej instancji danej klasy i możliwość odwoływania się do niej. Oczywiście, aby implementacja singletona była pełna musimy uczynić konstruktor i metodę służącą do klonowania prywatnymi:
<?php
class Singleton
{
  static private $_instance = null;

  public static function getInsance() 
  { 
    if (self::$_instance === null)
      self::$_instance = new self;

    return self::$_instance;
  }

  private function __construct() {/*...*/}

  private function __clone() {/*...*/}
}
To czy powinno się używać tego wzorca, to już całkowicie inna historia. Wydaje mi się, że w przypadku dobrze przemyślanych powiązań między instancjami klas nie ma miejsca na tego typu twory (przynajmniej mi osobiście nigdy nie były potrzebne:).

Zdaję sobie sprawę, że jednym z powodów nadużywania tego wzorca przez programistów php (i pewnie nie tylko) jest jego częste użycie w różnego rodzaju frameworkach, orm'ach czy też bibliotekach. Nie świadczy to (jego wykorzystywanie) jednak o tym, że problem, w którym powinno się go stosować występuje często. Jest to spowodowane raczej wygodą programistów implementujących daną funkcjonalność, ponieważ zazwyczaj można singleton zastąpić odpowiednimi powiązaniami bądź zależnościami pomiędzy obiektami.

statycznie, czyli dla wygody

A gdzie miejsce statycznych metod? Wydaje mi się, że najczęściej są tworzone one dla wygody, a nie z opartego na wymaganiach, powodu.
Często metody klas, które nie posiadają żadnych atrybutów, a dostarczają pewną funkcjonalność są tworzone jako statyczne:
<?php
class Formatter
{
  public static function format($value) 
  { 
    return 'formatted: ' . $value;
  }
}


W kodach niektórych aplikacji można spotkać statyczne konstruktory:
<?php
class A
{
  public static function create() 
  { 
    return new self;
  }
}


Oba powyższe rozwiązania nie są uzasadnione jakimikolwiek wymaganiami. Najczęściej są tworzone one po to, aby nie było potrzebne przypisywanie instancji klasy do żadnej zmiennej, tylko bezpośrednie wywołanie danej metody.

teoria, a praktyka

Tak więc używanie atrybutów bądź metod statycznych jest naprawdę bardzo rzadko spowodowane rzeczywistymi wymaganiami nałożonymi na aplikację. Nie zmienia to jednak faktu, że są to konstrukcje dość często używane w kodach, które piszemy:)
Nie będę namawiał do wykreślenia słowa static ze swojego słownika programisty, bo z pewnością są sytuacje, które wymagają zastosowania go. Zanim jednak zdecydujecie się na jego użycie warto wziąć pod uwagę kilka rzeczy:
  • Czy użycie atrybutu statycznego rzeczywiście ma sens i nie kłóci się z logiką aplikacji. Może dana funkcjonalność powinna zostać przeniesiona do innej klasy?
  • Czy to naprawdę powinien być singelton? Czy nie istnieje żadna sytuacja, w której powinny (mogą?) istnieć dwie instacje danej klasy?
  • Czy zastosowanie singletona ma na celu jedynie przenoszenie danych pomiędzy klasami? W takim wypadku warto przeprojektować powiązania pomiędzy obiektami.
  • Czy zastosowanie metody statycznej 'dla wygody' nie spowoduje problemów w przyszłości, jeżeli będzie trzeba rozszerzyć (zmodyfikować?) działanie danej klasy?