poniedziałek, 12 marca 2012

Jak programować obiektowo cz. 7 - const

na wstępie słów kilka

Znowu dłuższa przerwa w publikowaniu czegokolwiek. Na szczęście dobiegła końca i mam nadzieję, że w ciągu najbliższych kilku-kilkunastu tygodni uda mi się dokończyć wszystkie zaległe wpisy, na które pomysły dojrzewały, lecz z braku czasu nie mogły się doczekać realizacji:(
O czym dzisiaj? O stałych. A czym są te stałe? Kiedy ich używać, a kiedy nie? O tym dalej:)

let's talk about const

Wartości stałe klasy są to wartości, do których mamy dostęp zawsze i zewsząd. Nie potrzebujemy tworzyć instancji klasy, aby się do nich odwoływać. Jednak najważniejszą rzeczą dotyczących tego typu wartości jest fakt, że nie mogą one zostać zmodyfikowane. Ani przez obiekty zewnętrzne, ani przez instancje klasy, w której zostały zadeklarowane. I trzeba przyznać, że stałe czasami potrafią ułatwić życie:)

kiedy używać?

Zawsze wtedy, gdy zauważamy (na etapie projektowania lub też już w trakcie implementacji kodu), że w pracy z obiektami danej klasy często odwołujemy się do jakiejś wartości. Wiemy, że ta wartość jest niezmienna, bo w jakiś logiczny sposób jest powiązana z klasą (bądź jej reprezentacją w rzeczywistości). Warto wtedy rozważyć utworzenie stałej w danej klasie, aby nie mieć rozrzuconej po całym kodzie wartości, która dzisiaj nam coś mówi, bo akurat ten kod piszemy, ale za dwa - trzy miesiące, jej znaczenie będzie dla nas zagadką. Jeżeli jednak znajdzie się ona w klasie, to na pierwszy rzut oka zauważymy z czym jest ona powiązana, a dodatkowo zrozumienie jej sensu będzie prostsze.

kiedy to nie jest stała?

W pracy z kodem często spotykam się z deklaracjami stałych, które są niezmienne i są wymagane do działania klasy, np.
<?php
class SpaceFilter
{
  const SEPARATOR = '_';

  public function filter($value) 
  { 
    return str_replace(' ', self::SEPARATOR, $value);
  }
}
W powyższym, mamy klasę, która zamienia nam spację na znak podkreślenia. Czy rzeczywiście jest sens tworzyć stałą SEPARATOR? Zakładamy, że nigdy (czyli w rzeczywistości 'nie w najbliższym czasie':) się nie zmieni. Co daje nam istnienie takiej wartości stałej? Często słyszę, że takie działanie pozwala nam łatwiej odnaleźć ją, jeżeli będzie trzeba wprowadzić zmiany (np. zmiana '_' na '.'). Z tym się nie można nie zgodzić, ale czy to jest wystarczający powód, aby stworzyć stałą? Nie będzie ona nigdy używana poza klasą, nie dostarcza nam żadnych dodatkowych informacji, a nawet je duplikuje, bo zakładam, że w komentarzu do metody filter() znajdzie się informacja o tym, że zamienia ona spacje na znaki podkreślenia.

Osobiście uważam, że w takich przypadkach tworzenie stałej jest nadużywaniem tej konstrukcji. Co gorsza argument, że 'ułatwi przyszłe zmiany' jest również błędny, bo taki zabieg tak naprawdę komplikuje ten proces. Dlaczego? Jeżeli jest to stała to może zostać użyta gdziekolwiek indziej, a więc nie mogę zmienić jedynie definicji klasy, muszę również sprawdzić w całym projekcie, czy nie została ona gdzieś wykorzystana, bo teoretycznie jest taka możliwość.

Sam, w takich sytuacjach, doradzam stworzenie metody prywatnej, jeżeli koniecznie programista chce, aby wartość ta była odseparowana:
<?php
class SpaceFilter
{
  public function filter($value) 
  { 
    return str_replace(' ', $this->_getSeparator(), $value);
  }

  public function _getSeparator()
  { 
    return '_';
  }
}
Dzięki temu i wilk syty (brak stałych) i owca cała (przyszłe modyfikacje mogą zostać wprowadzone bardzo szybko). Dodatkowym atutem takiej konstrukcji jest to, że (w przeciwieństwie do zastosowania stałej) nie musimy się przejmować, że ktoś przez przypadek użył jej w kodzie.

Przykład jest dość prosty i rzeczywiste kody są zazwyczaj bardziej skomplikowane, a decyzja o (nie)stosowaniu stałej trudniejsza, ale ogólna zasada pozostaje - jeżeli wartość nie będzie wykorzystywana na zewnątrz, to nie powinna być ona stała.

przykład

Wróćmy do naszego głównego przykładu. Do tej pory stworzyliśmy klasę Contractor wraz z asocjacjami, atrybutami oraz metodami. To może dzisiaj trochę edycji? Załóżmy, że dane dotyczące kontrahentów przechowujemy w bazie danych i każdy z nich posiada jakiś unikalny identyfikator (kolumna o nazwie id :P). Jakie kroki musimy wykonać, żeby go edytować?
  • Wybranie z listy kontrahentów tego, którego dane chcemy zmienić i wyświetlenie jego obecnych danych.
  • Edycja danych.
  • Zapis nowych danych.
Na pierwszy rzut oka widać, że kroki 1 i 3 muszą najpierw pobrać kontrahenta z bazy danych, aby następnie wykonać na nim operacje. Aby to było możliwe potrzebujemy jego id. W aplikacjach klient-serwer nie można się bezpośrednio odwołać do metod obiektów na serwerze. Trzeba wysłać żądanie ze zbiorem parametrów. Taki parametr powinien mieć jednoznaczną nazwę np. 'contractorId' (nie 'id', bo tych zazwyczaj jest w projekcie dużo).

Kiedy będzie on wysyłany? Poprzez listę kontrahentów (po wybraniu tego, którym jesteśmy zainteresowani) oraz w formularzu edycji danych (musimy wiedzieć czyje dane są edytowane). Mamy już dwa miejsca, w których wystąpi parametr 'contractorId', a co z przyszłymi przypadkami np. "wyświetl faktury kontrahenta", "wystaw fakturę" itp.? Tam również będzie musiała się znaleźć.

Oczywiście możemy tą nazwę kopiować, ale w przypadku potrzeby zmiany 'contractorId' na np. 'cId'? Będziemy musieli przeszukać cały projekt, aby znaleźć wystąpienia 'contractorId'. Dodatkowo, można popełnić literówkę, a taki błąd może być czasami trudny do odnalezienia.

I to jest właśnie moment, w którym warto pomyśleć o zastosowaniu stałej! Tylko gdzie ją umieścić? Warto w takich chwilach zastanowić się, która klasa jest najbardziej powiązana z daną wartością. W naszym przypadku odpowiedź nasuwa się sama - klasa Contractor:
<?php
class Contractor
{
  const CONTRACTOR_ID = 'contractorId';
  /* ... */
}
Ale czy rzeczywiście jest to dobre miejsce? Do tej pory udało nam się stworzyć całkiem dobrą strukturę tej klasy i nie uwzględniała ona żadnej stałej? Czy coś się zmieniło?

Do czego tak naprawdę wykorzystujemy 'contracotrId'? Do pobierania danych konkretnego obiektu z bazy. Tak więc 'contractorId' nie jest czymś powiązanym z klasą Contractor, jest to pewnego rodzaju abstrakcja, która pomaga nam odnaleźć dane w bazie. Dlatego też lepszym miejscem na umiejscowienie tej stałej byłaby właśnie klasa odpowiedzialna za pobieranie danych.

W rozbudowanych aplikacjach mamy jednak kilka klas do pobierania danych np. Contractors, Invoices itp. W takim wypadku nie potrzebujemy stałej umieszczać w klasie Contractors, ponieważ jej obiekt potrzebuje wartości parametru 'contractorId', a nie jego nazwy.

No więc gdzie? Tak naprawdę nazwa ta jest wykorzystywana przy wysyłaniu żądania (obiekt klasy Request). Służy do określenia, jaki parametr jest wysyłany, czyli jeżeli na liście parametrów (np. tablica asocjacyjna) znajduje się parametr o kluczu 'contractorId', to wiemy, że jego wartość to id kontrahenta. I to miejsce jest idealne:
<?php
class Request
{
  const CONTRACTOR_ID = 'contractorId';
  /* ... */
}

3 komentarze:

  1. Jedyne sensowne użycie stałych jakie przychodzi mi na myśl, a nie przynoszące z czasem problemów, to użycie ich jako flag.

    Szczególnie że interfejsy mogą mieć stałe - więc można z powodzeniem deklarować i interfejs i zestaw obsługiwanych przez niego flag. Bez konkretnej implementacji.

    A metodę filter($value) zmienił bym na filter($value, $separator = '_') i nie trzeba grzebać.

    OdpowiedzUsuń
    Odpowiedzi
    1. O flagach nie pisałem celowo. Wydaje mi się, że bardziej dotykają aspektów implementacyjnych, a nie projektowych. Jest to (flagi) temat na tyle obszerny, że warto mu poświęcić osobny wpis (zresztą już taki był: http://blog.kowalczyk.cc/2011/04/05/php-flagi-bitowe/ :)

      Co do przesuwania ich do interfejsu, to powiem Ci, że nie spotkałem się z przypadkiem klasy stosującej flagi, która wymagałaby interfejsu.

      Jeśli chodzi o metodę filter(), to tak jak napisałem przykład jest uproszczony. Miał on zademonstrować problem:)

      Usuń
  2. Nigdy nie używałem, dobrze wiedzieć jednak że coś takiego istnieje, dzięki za fajną serię artykułów

    OdpowiedzUsuń