Prós e contras das constantes de interface [fechado]

105

As interfaces PHP permitem a definição de constantes em uma interface, por exemplo

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

Qualquer classe de implementação terá automaticamente essas constantes disponíveis, por exemplo

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

Minha opinião sobre isso é que qualquer coisa global é maligna . Mas eu me pergunto se o mesmo se aplica às Constantes de Interface. Dado que a codificação em relação a uma interface é considerada uma boa prática em geral, o uso de Constantes de Interface é as únicas constantes aceitáveis ​​para uso fora de um contexto de classe?

Embora eu esteja curioso para ouvir sua opinião pessoal e se você usa constantes de interface ou não, estou procurando principalmente por razões objetivas em suas respostas. Eu não quero que esta seja uma pergunta do tipo pesquisa. Estou interessado no efeito do uso de constantes de interface na capacidade de manutenção. Acoplamento. Ou teste de unidade. Como isso se relaciona com SOLID PHP? Isso viola algum princípio de codificação considerado uma boa prática em PHP? Você entendeu a ideia …

Observação: há uma pergunta semelhante para o Java que listou alguns bons motivos pelos quais eles são uma má prática, mas, como o Java não é PHP, achei justificável perguntar novamente na tag PHP.

Gordon
fonte
1
Hmm, eu nunca encontrei a necessidade de definir constantes em uma interface antes. Vale a pena saber que as classes que implementam a interface não podem substituir as constantes, enquanto as classes que apenas estendem umas às outras podem substituir as constantes.
Charles,
1
Acredito que as constantes não são ruins, pois têm valores previsíveis, mesmo quando estamos preocupados com a testabilidade da unidade. As variáveis ​​globais são ruins, pois qualquer um pode alterá-las, pois é uma variável e tudo tem escopo nela, mas constantes nunca irão alterar seu valor, portanto, o termo constante.
mdprotacio 01 de

Respostas:

135

Bem, acho que tudo se resume à diferença entre bom e bom o suficiente .

Embora na maioria dos casos você possa evitar o uso de constantes implementando outros padrões (estratégia ou talvez flyweight), há algo a ser dito para não precisar de meia dúzia de outras classes para representar um conceito. Acho que tudo se resume a quão provável é a necessidade de outras constantes. Em outras palavras, é necessário estender o ENUM fornecido pelas constantes na interface. Se você pode prever a necessidade de expandi-lo, use um padrão mais formal. Caso contrário, pode ser suficiente (será bom o suficiente e, portanto, haverá menos código para escrever e testar). Aqui está um exemplo de uso bom e ruim:

Ruim:

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

Bom o bastante:

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

Agora, a razão pela qual escolhi esses exemplos é simples. A Userinterface está definindo um enum de tipos de usuário. É muito provável que isso se expanda com o tempo e seria mais adequado por outro padrão. Mas HTTPRequest_1_1é um caso de uso decente, uma vez que o enum é definido pelo RFC2616 e não mudará durante o tempo de vida da classe.

Em geral, não vejo o problema com constantes e constantes de classe como sendo um problema global . Eu vejo isso como um problema de dependência. É uma distinção estreita, mas definitiva. Vejo problemas globais como em variáveis ​​globais que não são aplicadas e, como tal, criam uma dependência global flexível. Mas uma classe codificada cria uma dependência forçada e, como tal, cria uma dependência global rígida. Portanto, ambos são dependências. Mas considero o global muito pior, já que não é obrigatório ... É por isso que não gosto de agrupar dependências de classe com dependências globais sob o mesmo banner ...

Se você escrever MyClass::FOO, estará codificado com os detalhes de implementação de MyClass. Isso cria um acoplamento rígido, o que torna seu código menos flexível e, como tal, deve ser evitado. No entanto, existem interfaces que permitem exatamente esse tipo de acoplamento. Portanto MyInterface::FOO, não introduz nenhum acoplamento concreto. Dito isso, eu não apresentaria uma interface apenas para adicionar uma constante a ela.

Então, se você estiver usando interfaces, e você está muito certo de que você (ou qualquer outra pessoa para essa matéria) não vai precisar de valores adicionais, então eu realmente não vejo um enorme problema com as constantes de interface ... O melhor os designs não incluiriam quaisquer constantes ou condicionais ou números mágicos ou strings mágicas ou qualquer coisa codificada. No entanto, isso adiciona mais tempo ao desenvolvimento, pois você deve considerar os usos. Minha opinião é que na maioria das vezes vale a pena dedicar um tempo adicional para construir um design sólido e excelente. Mas há momentos em que bom o suficiente é realmente aceitável (e é preciso um desenvolvedor experiente para entender a diferença) e, nesses casos, tudo bem.

Novamente, essa é apenas minha opinião sobre isso ...

ircmaxell
fonte
4
O que você sugeriria como um padrão diferente para o usuário neste caso?
Jacob,
@Jacob: Eu abstrairia isso. Dependendo de suas necessidades, provavelmente construiria uma classe do Access que obteria seus dados de uma tabela de banco de dados. Dessa forma, adicionar um novo nível é tão fácil quanto inserir uma nova linha. Outra opção seria construir um conjunto de classes ENUM (onde você tem uma classe para cada função de permissão). Em seguida, você pode estender as classes onde necessário para fornecer as permissões apropriadas. Mas existem outros métodos que também funcionam
ircmaxell
3
Resposta muito sólida e bem articulada! +1
Decent Dabbler
1
classe com constantes públicas não deve ter nenhum método. Deve ser apenas estrutura de dados ou apenas objeto - não ambos.
OZ_
2
@FrederikKrautwald: você pode evitar condicionais com polimorfismo (na maioria dos casos): Verifique esta resposta e também assista a esta palestra sobre Código Limpo ...
ircmaxell
10

Acho que geralmente é melhor lidar com constantes, especialmente constantes enumeradas, como um tipo separado ("classe") de sua interface:

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

ou, se você quiser usar uma classe como um namespace:

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

Não é que você esteja usando apenas constantes, você está usando o conceito de valores enumerados ou enumerações, que um conjunto de valores restritos são considerados um tipo específico, com um uso específico ("domínio"?)

Umlcat
fonte