Plusy i minusy stałych interfejsu [zamknięte]

105

Interfejsy PHP pozwalają na definiowanie stałych w interfejsie, np

interface FooBar
{
    const FOO = 1;
    const BAR = 2;
}
echo FooBar::FOO; // 1

Każda klasa implementująca będzie miała automatycznie dostępne te stałe, np

class MyFooBar implement FooBar
{
}
echo MyFooBar::FOO; // 1

Osobiście uważam, że wszystko, co Globalne, jest Złem . Ale zastanawiam się, czy to samo dotyczy stałych interfejsu. Biorąc pod uwagę, że kodowanie w oparciu o interfejs jest ogólnie uważane za dobrą praktykę, czy używanie stałych interfejsu jest jedynymi stałymi, których można używać poza kontekstem klasy?

Chociaż jestem ciekawy twojej osobistej opinii i tego, czy używasz stałych interfejsu, czy nie, szukam głównie obiektywnych powodów w twoich odpowiedziach. Nie chcę, aby było to pytanie typu ankiety. Interesuje mnie, jaki wpływ ma używanie stałych interfejsu na utrzymywalność. Sprzęganie. Lub testy jednostkowe. Jak to się ma do SOLID PHP? Czy narusza jakiekolwiek zasady kodowania uważane za dobre praktyki w PHP? Masz pomysł …

Uwaga: w przypadku Javy pojawia się podobne pytanie, w którym wymieniono całkiem dobre powody, dla których są one złymi praktykami, ale ponieważ Java nie jest PHP, uznałem za uzasadnione, aby zadać je ponownie w tagu PHP.

Gordon
źródło
1
Hmm, nigdy wcześniej nie spotkałem się z potrzebą definiowania stałych w interfejsie. Warto wiedzieć, że klasy implementujące interfejs nie mogą przesłonić stałych, podczas gdy klasy jedynie wzajemnie się rozszerzające mogą nadpisać stałe.
Charles
1
Uważam, że stałe nie są złe, ponieważ mają przewidywalne wartości, nawet jeśli chodzi o testowalność jednostkową. Zmienne globalne są złe, ponieważ każdy może je zmienić, ponieważ jest zmienną i wszystko ma swój zakres, ale stałe nigdy nie zmienią swojej wartości, dlatego termin stała.
mdprotacio

Odpowiedzi:

135

Cóż, myślę, że sprowadza się to do różnicy między dobrym a dostatecznie dobrym .

Chociaż w większości przypadków można uniknąć stosowania stałych poprzez implementację innych wzorców (strategii lub wagi muszej), jest coś do powiedzenia na temat braku potrzeby stosowania pół tuzina innych klas do reprezentowania koncepcji. Myślę, że sprowadza się to do tego, jak prawdopodobne jest zapotrzebowanie na inne stałe. Innymi słowy, czy istnieje potrzeba rozszerzenia ENUM zapewnianego przez stałe w interfejsie. Jeśli możesz przewidzieć potrzebę jego rozszerzenia, zastosuj bardziej formalny wzór. Jeśli nie, to może wystarczyć (będzie wystarczająco dobry, a zatem będzie mniej kodu do napisania i przetestowania). Oto przykład wystarczająco dobrego i złego użycia:

Zły:

interface User {
    const TYPE_ADMINISTRATOR = 1;
    const TYPE_USER          = 2;
    const TYPE_GUEST         = 3;
}

Wystarczająco dobry:

interface HTTPRequest_1_1 {
    const TYPE_CONNECT = 'connect';
    const TYPE_DELETE  = 'delete';
    const TYPE_GET     = 'get';
    const TYPE_HEAD    = 'head';
    const TYPE_OPTIONS = 'options';
    const TYPE_POST    = 'post';
    const TYPE_PUT     = 'put';

    public function getType();
}

Powód, dla którego wybrałem te przykłady, jest prosty. UserInterfejs definiuje enum typów użytkowników. Jest bardzo prawdopodobne, że z czasem będzie się to rozwijało i lepiej pasowałoby do innego wzorca. Ale HTTPRequest_1_1jest to przyzwoity przypadek użycia, ponieważ wyliczenie jest zdefiniowane przez RFC2616 i nie zmieni się przez cały okres istnienia klasy.

Ogólnie rzecz biorąc, nie uważam problemu ze stałymi i stałymi klas za problem globalny . Postrzegam to jako problem zależności. To wąskie rozróżnienie, ale definitywne. Widzę problemy globalne jak zmienne globalne, które nie są narzucane i jako takie tworzą miękką zależność globalną. Jednak zakodowana na stałe klasa tworzy wymuszoną zależność i jako taka tworzy twardą zależność globalną. Więc obie są zależnościami. Ale uważam, że globalność jest znacznie gorsza, ponieważ nie jest wymuszana ... Dlatego nie lubię grupować zależności klas z zależnościami globalnymi pod tym samym szyldem ...

Jeśli piszesz MyClass::FOO, jesteś na stałe zakodowany w szczegółach implementacji MyClass. Tworzy to twarde sprzężenie, które sprawia, że ​​kod jest mniej elastyczny i dlatego należy go unikać. Istnieją jednak interfejsy, które umożliwiają dokładnie ten typ sprzężenia. Dlatego MyInterface::FOOnie wprowadza żadnego konkretnego połączenia. Powiedziawszy to, nie wprowadzałbym interfejsu tylko po to, aby dodać do niego stałą.

Więc jeśli używasz interfejsów, a ty jesteś bardzo pewny, że Ty (lub ktoś inny o to chodzi) nie będzie potrzebować dodatkowych wartości, to naprawdę nie zobaczyć ogromny problem ze stałych interfejs ... Najlepszym projekty nie zawierałyby żadnych stałych, warunkowych, liczb magicznych, łańcuchów znaków ani niczego zakodowanego na stałe. To jednak dodaje dodatkowy czas na rozwój, ponieważ musisz wziąć pod uwagę zastosowania. Uważam, że w większości przypadków absolutnie warto poświęcić dodatkowy czas na zbudowanie świetnego, solidnego projektu. Ale są chwile, kiedy dostatecznie dobre jest naprawdę akceptowalne (i zrozumienie różnicy wymaga doświadczonego programisty) iw takich przypadkach jest w porządku.

Ponownie, to tylko mój pogląd na to ...

ircmaxell
źródło
4
Co w tym przypadku zasugerowałbyś jako inny wzorzec dla użytkownika?
Jacob,
@Jacob: Odrzuciłbym to. W zależności od potrzeb prawdopodobnie utworzę klasę Access, która pobierze dane z tabeli bazy danych. W ten sposób dodanie nowego poziomu jest tak proste, jak wstawienie nowego wiersza. Inną opcją byłoby zbudowanie zestawu klas ENUM (gdzie masz jedną klasę dla każdej roli uprawnień). Następnie możesz w razie potrzeby rozszerzyć klasy, aby nadać odpowiednie uprawnienia. Ale są też inne metody, które będą działać
ircmaxell
3
Bardzo solidna i dobrze wyartykułowana odpowiedź! +1
Godny amator
1
klasa ze stałymi publicznymi nie powinna mieć żadnych metod. Powinna to być tylko struktura danych lub tylko obiekt - nie jedno i drugie.
OZ_
2
@FrederikKrautwald: możesz uniknąć warunków warunkowych z polimorfizmem (w większości przypadków): Sprawdź tę odpowiedź , a także obejrzyj wykład o czystym kodzie ...
ircmaxell
10

Myślę, że zwykle lepiej jest traktować stałe, specjalnie wyliczone stałe, jako oddzielny typ („klasa”) od interfejsu:

define(TYPE_CONNECT, 'connect');
define(TYPE_DELETE , 'delete');
define(TYPE_GET    , 'get');
define(TYPE_HEAD   , 'head');
define(TYPE_OPTIONS, 'options');
define(TYPE_POST   , 'post');
define(TYPE_PUT    , 'put');

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

lub, jeśli chcesz użyć klasy jako przestrzeni nazw:

class TypeHTTP_Enums
{
  const TYPE_CONNECT = 'connect';
  const TYPE_DELETE  = 'delete';
  const TYPE_GET     = 'get';
  const TYPE_HEAD    = 'head';
  const TYPE_OPTIONS = 'options';
  const TYPE_POST    = 'post';
  const TYPE_PUT     = 'put';
}

interface IFoo
{
  function /* int */ readSomething();
  function /* void */ ExecuteSomething(/* int */ param);
}

class CBar implements IFoo
{
  function /* int */ readSomething() { ...}
  function /* void */ ExecuteSomething(/* int */ param) { ... }
}

Nie chodzi o to, że używasz tylko stałych, używasz koncepcji wartości wyliczonych lub wyliczeń, które to zestaw wartości zastrzeżonych są uważane za określony typ, z określonym zastosowaniem („domena”?)

umlcat
źródło