Плюсы и минусы интерфейсных констант [закрыто]

105

Интерфейсы PHP позволяют определять константы в интерфейсе, например

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

Любой реализующий класс будет автоматически иметь эти константы, например

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

Я считаю, что все Глобальное - зло . Но мне интересно, применимо ли то же самое к константам интерфейса. Учитывая, что кодирование на основе интерфейса в целом считается хорошей практикой, является ли использование констант интерфейса единственными константами, которые можно использовать вне контекста класса?

Хотя мне любопытно услышать ваше личное мнение и используете ли вы константы интерфейса или нет, я в основном ищу в ваших ответах объективные причины. Я не хочу, чтобы это был вопрос типа опроса. Меня интересует, как использование констант интерфейса влияет на ремонтопригодность. Связь. Или модульное тестирование. Как это связано с SOLID PHP? Нарушает ли это какие-либо принципы кодирования, которые считаются хорошей практикой в ​​PHP? Вы уловили идею ...

Примечание: есть аналогичный вопрос для Java, в котором перечислены некоторые довольно веские причины, почему они являются плохой практикой, но, поскольку Java не является PHP, я счел оправданным задать его снова в теге PHP.

Гордон
источник
1
Хм, я никогда раньше не сталкивался с необходимостью определять константы в интерфейсе. Стоит знать, что классы, реализующие интерфейс, не могут переопределять константы, в то время как классы, просто расширяющие друг друга, могут переопределять константы.
Charles
1
Я считаю, что константы - это неплохо, поскольку они имеют предсказуемые значения, даже когда мы озабочены возможностью модульного тестирования. Глобальные переменные - зло, поскольку любой может изменить их, поскольку это переменная, и все имеет область видимости, но константы никогда не изменят свое значение, следовательно, термин константа.
mdprotacio 01

Ответы:

135

Что ж, я думаю, что все сводится к разнице между хорошим и достаточно хорошим .

Хотя в большинстве случаев вы можете избежать использования констант, реализовав другие шаблоны (стратегию или, возможно, легковес), есть кое-что, что нужно сказать о том, что для представления концепции не требуется полдюжины других классов. Я думаю, что все сводится к тому, насколько вероятно, что нужны другие константы. Другими словами, есть ли необходимость в расширении ENUM, предоставляемого константами в интерфейсе. Если вы можете предвидеть необходимость его расширения, выберите более формальный образец. Если нет, то этого может быть достаточно (это будет достаточно хорошо, и, следовательно, будет меньше кода для написания и тестирования). Вот пример хорошего и плохого использования:

Плохой:

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

Достаточно хорошо:

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();
}

Причина, по которой я выбрал эти примеры, проста. UserИнтерфейс является определяющим перечисление типов пользователей. Очень вероятно, что со временем он расширится, и ему лучше подходит другой шаблон. Но HTTPRequest_1_1это достойный вариант использования, поскольку перечисление определено RFC2616 и не будет изменяться в течение всего времени существования класса.

В общем, я не считаю проблему с константами и константами классов глобальной проблемой. Я вижу в этом проблему зависимости. Это узкое различие, но определенное. Я вижу глобальные проблемы, например, в глобальных переменных, которые не применяются принудительно, и как таковые создают мягкую глобальную зависимость. Но жестко запрограммированный класс создает принудительную зависимость и, таким образом, создает жесткую глобальную зависимость. Итак, оба являются зависимостями. Но я считаю, что глобальное намного хуже, поскольку оно не применяется ... Вот почему я не люблю объединять зависимости классов с глобальными зависимостями под одним и тем же баннером ...

Если вы пишете MyClass::FOO, вы жестко запрограммированы на детали реализации MyClass. Это создает жесткую связь, которая делает ваш код менее гибким, и поэтому его следует избегать. Однако существуют интерфейсы, позволяющие осуществлять именно такой тип связи. Поэтому MyInterface::FOOне вводит никакой конкретной связи. С учетом сказанного, я бы не стал вводить интерфейс просто для добавления к нему константы.

Так что, если вы используете интерфейс, и вы очень уверены , что вы (или кто -либо еще в этом отношении) не нужны дополнительные значения, то я не вижу большой проблемы с константами интерфейса ... Лучший конструкции не будут содержать никаких констант, условных операторов, магических чисел, магических строк или чего-либо жестко запрограммированного. Однако это добавляет дополнительное время для разработки, так как вы должны учитывать варианты использования. Я считаю, что в большинстве случаев абсолютно необходимо потратить дополнительное время на создание отличного надежного дизайна. Но бывают случаи, когда достаточно хорошее действительно приемлемо (и требуется опытный разработчик, чтобы понять разницу), и в таких случаях это нормально.

Опять же, это только мой взгляд на это ...

ircmaxell
источник
4
Что бы вы посоветовали пользователю в этом случае в качестве другого шаблона?
Джейкоб
@ Джейкоб: Я бы отвлекся от этого. В зависимости от ваших потребностей, я бы, вероятно, построил класс Access, который бы получал данные из таблицы базы данных. Таким образом, добавить новый уровень так же просто, как вставить новую строку. Другой вариант - создать набор классов ENUM (где у вас есть один класс для каждой роли доступа). Затем вы можете расширять классы там, где это необходимо, чтобы предоставить соответствующие разрешения. Но есть и другие методы, которые тоже будут работать
ircmaxell
3
Очень солидный и четко сформулированный ответ! +1
Decent Dabbler
1
класс с общедоступными константами не должен иметь никаких методов. Это должна быть только структура данных или только объект, но не то и другое одновременно.
OZ_
2
@FrederikKrautwald: вы можете избежать условных выражений с помощью полиморфизма (в большинстве случаев): проверьте этот ответ , а также посмотрите этот доклад о чистом коде ...
ircmaxell
10

Я думаю, что обычно лучше обрабатывать константы, специально перечисленные константы, как отдельный тип («класс») от вашего интерфейса:

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) { ... }
}

или, если вы хотите использовать класс в качестве пространства имен:

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) { ... }
}

Дело не в том, что вы используете только константы, вы используете концепцию перечисляемых значений или перечислений, которые представляют собой набор ограниченных значений, которые считаются определенным типом с определенным использованием («домен»?)

umlcat
источник