Pros y contras de las constantes de interfaz [cerrado]

105

Las interfaces PHP permiten la definición de constantes en una interfaz, p. Ej.

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

Cualquier clase de implementación tendrá automáticamente estas constantes disponibles, p. Ej.

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

Mi propia opinión sobre esto es que cualquier cosa global es malvada . Pero me pregunto si lo mismo se aplica a las constantes de interfaz. Dado que la codificación contra una interfaz se considera una buena práctica en general, ¿el uso de constantes de interfaz es la única constante aceptable para usar fuera del contexto de una clase?

Si bien tengo curiosidad por escuchar su opinión personal y si usa constantes de interfaz o no, principalmente busco razones objetivas en sus respuestas. No quiero que esta sea una pregunta de tipo encuesta. Estoy interesado en el efecto que tiene el uso de constantes de interfaz en el mantenimiento. Acoplamiento. O pruebas unitarias. ¿Cómo se relaciona con PHP SOLID ? ¿Viola algún principio de codificación que se considere una buena práctica en PHP? Entiendes la idea ...

Nota: hay una pregunta similar para Java que enumera algunas razones bastante buenas por las que son una mala práctica, pero como Java no es PHP, sentí que estaba justificado preguntarlo dentro de la etiqueta PHP nuevamente.

Gordon
fuente
1
Hmm, nunca antes me había encontrado con la necesidad de definir constantes en una interfaz. Vale la pena saber que las clases que implementan la interfaz no pueden anular las constantes, mientras que las clases que simplemente se extienden entre sí pueden anular las constantes.
Charles
1
Creo que las constantes no son malas, ya que tienen valores predecibles incluso cuando nos preocupa la capacidad de prueba de la unidad. Las variables globales son malas ya que cualquiera puede cambiarlas, ya que es una variable y todo tiene alcance, pero las constantes nunca cambiarán su valor, por lo tanto, el término constante.
mdprotacio

Respuestas:

135

Bueno, creo que se reduce a la diferencia entre lo bueno y lo suficientemente bueno .

Si bien en la mayoría de los casos puede evitar el uso de constantes implementando otros patrones (estrategia o tal vez peso mosca), hay algo que decir sobre no necesitar media docena de otras clases para representar un concepto. Creo que todo se reduce a la probabilidad de que se necesiten otras constantes. En otras palabras, ¿es necesario ampliar el ENUM proporcionado por las constantes en la interfaz? Si puede prever la necesidad de expandirlo, elija un patrón más formal. Si no, entonces puede ser suficiente (será lo suficientemente bueno y, por lo tanto, habrá menos código para escribir y probar). Aquí hay un ejemplo de un uso suficientemente bueno y malo:

Malo:

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

Suficientemente bueno:

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

Ahora, la razón por la que elegí esos ejemplos es simple. La Userinterfaz define una enumeración de tipos de usuario. Es muy probable que se expanda con el tiempo y sería más adecuado para otro patrón. Pero HTTPRequest_1_1es un caso de uso decente, ya que la enumeración está definida por RFC2616 y no cambiará durante la vida útil de la clase.

En general, no veo el problema con las constantes y las constantes de clase como un problema global . Lo veo como un problema de dependencia. Es una distinción estrecha, pero definida. Veo los problemas globales como variables globales que no se aplican y, como tales, crean una dependencia global suave. Pero una clase codificada crea una dependencia forzada y, como tal, crea una dependencia global dura. Entonces ambos son dependencias. Pero considero que lo global es mucho peor ya que no se aplica ... Por eso no me gusta agrupar las dependencias de clase con las dependencias globales bajo el mismo banner ...

Si escribe MyClass::FOO, está codificado en los detalles de implementación de MyClass. Esto crea un acoplamiento duro, lo que hace que su código sea menos flexible y, como tal, debe evitarse. Sin embargo, existen interfaces para permitir exactamente este tipo de acoplamiento. Por tanto MyInterface::FOO, no introduce ningún acoplamiento de hormigón. Dicho esto, no introduciría una interfaz solo para agregarle una constante.

Entonces, si está usando interfaces, y está muy seguro de que usted (o cualquier otra persona) no necesitará valores adicionales, entonces realmente no veo un gran problema con las constantes de la interfaz ... Lo mejor los diseños no incluirían constantes, condicionales, números mágicos, cadenas mágicas ni nada codificado. Sin embargo, eso agrega tiempo adicional al desarrollo, ya que debe considerar los usos. Mi opinión es que la mayoría de las veces vale la pena tomarse el tiempo adicional para construir un gran diseño sólido. Pero hay ocasiones en las que lo suficientemente bueno es realmente aceptable (y se necesita un desarrollador experimentado para comprender la diferencia), y en esos casos está bien.

Nuevamente, esa es solo mi opinión al respecto ...

ircmaxell
fuente
4
¿Qué sugeriría como patrón diferente para el usuario en este caso?
Jacob
@Jacob: Lo abstraería. Dependiendo de sus necesidades, probablemente crearía una clase de Access que obtendría sus datos de una tabla de base de datos. De esa manera, agregar un nuevo nivel es tan fácil como insertar una nueva fila. Otra opción sería construir un conjunto de clases ENUM (donde tiene una clase para cada rol de permiso). Luego, puede extender las clases donde sea necesario para proporcionar los permisos adecuados. Pero hay otros métodos que también funcionarán
ircmaxell
3
¡Respuesta muy sólida y bien articulada! +1
Decent Dabbler
1
la clase con constantes públicas no debería tener ningún método. Debe ser solo estructura de datos o solo objeto, no ambos.
OZ_
2
@FrederikKrautwald: puede evitar los condicionales con polimorfismo (en la mayoría de los casos): consulte esta respuesta y mire esta charla sobre Código limpio ...
ircmaxell
10

Creo que generalmente es mejor manejar constantes, constantes especialmente enumeradas, como un tipo separado ("clase") de su interfaz:

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

o, si desea utilizar una clase como espacio de nombres:

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

No es que esté usando solo constantes, está usando el concepto de valores enumerados o enumeraciones, que un conjunto de valores restringidos, se consideran un tipo específico, con un uso específico ("dominio"?)

umlcat
fuente