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?

2 komentarze:

  1. //Singleton
    static private $_instance = null;

    Zmienna zadeklarowana ma wartość początkową null, więc przypisanie nulla, nic nie zmienia i wprowadza zamieszanie. Jestem przeciwnikem pisania takiego kodu w przykładach, ma to zły wpływ edukacyjny :)


    //metody statyczne
    Meody statyczne często są nadużywane w miejscach gdzie powinny być deklarowane jako metody instancji obiektu, ale... Metody statyczne są bardzo przydatne np przy porównywaniu obiektu. Mało kto wie, że dostep prywatnych pól instancji danej klasy ma inna instancja (egzemplarz) tej klasy. Bardzo łatwo więc napisać statyczną metodę, np porównywującą dwa obiekty z ominięciem setterów i getterów.

    Metody statyczne bardzo ładnie się sprawdzają jako realizacja operacji matematycznych. Dodawanie macierzy można zaimplementować jako metodę statyczną, przyjmującą jako argument 2 instancje macierzy, wynikiem czego jest kolejna instancja macierzy.

    Tzw "statyczny konstruktor" jest bardzo użyteczny. Czasem potrzeba nam utworzyć obiekt z "dziwnej" postaci danych, np z tablicy, json'a, xml'a itp. Statycznie implementujemu sobie odpowiednią metodę: createFromArray, createFromJson, createFromXml. W takiej metodzie statycznej tworzymy nową instancję, wywołujemy tam 15 setterów (zakładam, że wiemy, że w setterach można umieścić walidację danych) i jest kawałek ładnego kodu.

    OdpowiedzUsuń
    Odpowiedzi
    1. //Singleton
      Programiści, z którymi pracuję są po Twojej stronie:) Jest to pozostałość po projektowaniu, tam mimo wszystko zawsze definiuje wartość domyślną i to przyzwyczajenie przekłada się u mnie na kod.
      Mimo wszystko masz rację, jest to niepotrzebne.

      //metody statyczne
      W ostatnim akapicie 'teoria, a praktyka' stwierdzam, że są sytuacje, kiedy metody statyczne się przydają. Po prostu nie podałem żadnych przykładów.
      Ogólnie wychodzę z założenia, że nie istnieje taka konstrukcja językowa, której bezwzględnie trzeba unikać. Jedna po prostu wymagają więcej uwagi przy wykorzystywaniu niż inne:)

      Usuń