Interfaces en PHP

Escribiendo código mantenible y desacoplado

Escrito por Matías Navarro Carter hace 2 semanas
En: PHP / POO
Lectura de 6 minuto(s).

"Debes codificar a una interfaz", "no debes depender de una implementación" o "debes desacoplar tus clases" son frases que programadores con experiencia en POO suelen repetir mucho a los padawans que les ha tocado guíar por el hermoso camino de desarrollar software de calidad y mantenible. Si has escuchado estas frases antes, quizás quieras seguir leyendo.

El Paradigma Moderno de Arquitectura de Software

El paradigma de arquitectura de software imperante el día de hoy puede resumirse fácilmente en una palabra: modular. Los buenos software architects buscan desarrollar pequeños componentes o módulos que puedan conectarse fácilmente para hacerlos trabajar juntos dentro de una aplicación mayor. Quizás uno de los frameworks de PHP que mejor representa este paradigma es Symfony, en donde el framework mismo consiste en varios componentes pegados entre sí, pero diseñados de tal forma que no necesitan tener conocimiento de los otros componentes para hacer su trabajo.

Por ejemplo, quizás necesitas un componente para hablar a tu base de datos, y otro para escribir archivos de log, o quizás otro para adminsitrar la seguridad en tu sitio web. No importa de la pieza de funcionalidad que sea: buenas aplicaciones siempre separan su funcionalidad en componentes que pueden ser reutilizados fácilmente.

Pero la pregunta es ¿cómo puedo diseñar estos componentes para interactuar entre sí sin que tengan algo de conocimiento de otros componentes? Al fin y al cabo, ese es el secreto.

Las Bondades de las Interfaces

Cada lenguaje Orientado a Objetos posee una funcionalidad llamada Interfaces. Las Interfaces son una especie de clases que no contienen nada de lógica, pero contienen definiciones de métodos. A diferencia de las clases normales o abstractas, las interfaces no se extienden, sino que se implementan.

Si todo lo anterior te sonó peor que chino, está bien. Quizás un buen ejemplo para entender esta funcionalidad es pensar en los objetos que usamos a diario en nuestra vida. Al fin y al cabo, la POO es eso: una abstracción en código de cómo entendemos el mundo real.

En el mundo real, diferentes objetos interáctuan entre sí por medio de interfaces. Por ejemplo, todos los aparatos que consumen electricidad tienen algún tipo de enchufe con el cual pueden recibir corriente.

Imagina que hubiese un encuhfe diferente para cada producto que ocupe electricidad. ¡Sería un caos! Gracias a Dios, un grupo de personas muy inteligentes dijo una vez: "Pongámonos de acuerdo en cómo vamos a funcionar. Definamos una interfaz común para que todos los objetos que usan electricidad puedan utilizarla." Y así nacieron los enchufes.

El enchufe es la interfaz, que está conectada a una aplicación más grande (la corriente alterna). Es un set de requerimientos mínimos para utilizar la electricidad (dos patitas metálicas). La interfaz le da a un objeto particular lo que necesita para funcionar (en el caso del mundo real, electricidad; en el caso de nuestras aplicaciones, información). Cualquier objeto que use o implemente esa interfaz (lámpara, celular, refrigerador, etcétera) puede obtener los beneficios que vienen con esa interfaz.

Bien podríamos tener una interfaz así en nuestro código:

<?php

interface Enchufable {

    public function setElectricidad(Electricidad $electricidad);

    public function encender();    
}

class Lampara implements Enchufable {

    private $electricidad;

    private $ampolleta;

    public function setElectricidad(Electricidad $electricidad)
    {
        $this->electricidad = $electricidad;
    }

    public function encender()
    {
        return $this->ampolleta->iluminar($electricidad);
    }
}

Abstración, no Implementación

El secreto de entender cómo funcionan las interfaces es pensar no en las clases en concreto que necesita nuestra aplicación para funcionar, sino en las clases en abstracto. Primero pensamos en el diseño, y luego hacemos una implementación de lo que tenemos diseñado. Por eso las interfaces no contienen lógica: son sólo una abstracción de métodos. La lógica la tendrá una implementación concreta.

Quizás sea tiempo de un ejemplo. Supongamos que en una aplicación tienes un requerimiento básico: quieres extraer un Token de autenticación de un query param. Si eres un programador ingenuo, vas a hacer algo como esto:

<?php

public function doSomething(Request $request)
{
    $token = $request->query->get('token');
    //...
}

Supongamos que repetiste esa lógica en cada método de cada controlador que necesitaba extraer el token.

Pero ¿qué pasa si luego, como requerimiento del proyecto, tu jefe dice que el token necesita ser extraído de un Authorization header? Vas a tener que cambiar cada línea de código donde usaste el ejemplo anterior. Eso te ocurrió porque no fuiste un buen diseñador de software. (1) No pudiste anticiparte a los requerimientos, y (2) dependiste de una implementación, i.e. que el token viniera en un query param. Eso hizo tu código más caro de mantener.

¿Que hubiese pasado si hubieras tenido algo como esto?

<?php

private $tokenExtractor;

public function __construct(TokenExtractorInterface $tokenExtractor)
{
    $this->tokenExtractor = $tokenExtractor
}

public function doSomething(Request $request)
{
    $token = $this->tokenExtractor->extract($request);
    //...
}

Ahora la clase que utiliza tu token extractor depende de una interfaz, no de una implementación concreta. Lo único que importa es que esa interfaz tenga el método extract() y que reciba la request. La lógica la hará el objeto que implemente la interfaz. Así, puedes hacer un QueryParamExtractor o un AuthorizationHeaderExtractor implementando esa interfaz y utilizarlos arbitrariamente sin modificar tu código. Incluso puedes crear una interfaz que utilice ambos al mismo tiempo. Todo es posible.

Llegando más lejos, puedes separar la lógica de tu controlador, y utlizar tus extractors en otro lugar de tu código, como en un sistema de autenticación. ¡Todo es más fácil cuando programas a una interfaz!

El Objetivo Principal: Desacoplar

Quizás estés pensando "Pero el segundo ejemplo es más complicado, y tiene más líneas de código". Es cierto, y eso es bueno. Un buen programador no se mide en términos de escribir menos código para obtener el mismo resultado; un buen programador se mide en términos de la calidad arquitectónica de las aplicaciones que desarrolla. En tal calidad, si hay algo importante es mantener las clases desacopladas.

Desacoplar tus clases te permitirá una mayor reutilización de tu código a través de las diferentes aplicaciones que desarrolles, además de reemplazar una implementación por otra cuando la necesidad así lo determine. Sin duda es una de aquellas cosas que debes dominar si quieres convertirte en un buen programador.