Patrones Estructurales (II): Patrón Facade


Objetivo:

«Proporcionar una interfaz unificada a otro conjunto de interfaces de un subsistema. Façade define una interfaz de más alto nivel que hace que el subsistema más fácil de usar.»

Design Patterns: Elements of Reusable Object-Oriented Software

Cuando vimos el patrón Adapter dijimos que el patrón Facade (fachada) estaba muy relacionado con él. Si bien ambos patrones se basan en el mismo principio (encapsular la interfaz de otros elementos), es el motivo y el uso que se le va a dar lo que las diferencia:

  • El patrón Adapterconvierte una interfaz en otra, haciendo que la clase cliente pueda utilizar los métodos de una clase para cuya interfaz no estaba originalmente preparado.
  • El patrón Facade, sin embargo, no realiza ninguna transformación, sino que se limita a simplificar y centralizar un conjunto de invocaciones en un único punto, a la vez que desacopla al cliente del subsistema (siendo la clase Facade la que quedaría acoplada). Es importante tener en cuenta que el patrón Facade no realiza operaciones de transformación, ya que las clases del subsistema, pese a ser envueltas con este elemento, pueden seguir siendo accesibles de forma normal en caso de que fuese necesario, al contrario de lo que ocurre con el patrón Adapter. Un Facade es una forma de simplificar las cosas: es decisión del diseñador si las clases del subsistema son accedidas de forma directa o a través de la fachada.

Resumiendo, Adapter transforma mientras que Facade simplifica y desacopla.

Cambio manual vs. Cambio automático

Seguiremos hablando de vehículos para ilustrar nuestros patrones. Un ejemplo de este patrón aplicado a la vida real sería la diferencia entre un sistema de cambio manual y un sistema automático. En el primer caso, el conductor debe interactuar con el embrague, el acelerador y la palanca de cambios, mientras que en el segundo caso, el conductor únicamente tiene que preocuparse de acelerar o decelerar, puesto que será el propio vehículo el que se encargará de realizar todo el proceso.

Veamos cómo funcionará el sistema antes de aplicar el patrón Facade:

Tal y como hemos explicado, el conductor debe interactuar con tres elementos del subsistema para realizar un cambio de marcha. Codificaremos las interfaces del subsistema con las que interactúa el conductor para comprobar cuál sería su uso normal.

IPalancaCambios:


    public interface IPalancaCambios
    {
        void InsertarVelocidad(int velocidad);
        void PuntoMuerto();
        int GetVelocidadActual();
    }

IEmbrague:


    public interface IEmbrague
    {
        void PresionarEmbrague();
        void SoltarEmbrague();
    }

IAcelerador:


    public interface IAcelerador
    {
        void PresionarAcelerador();
        void SoltarAcelerador();
    }

A continuación veremos cuál será el proceso que sigue un cambio de marcha manual. Proporcionaremos, por lo tanto, un conjunto de clases que implementen estas interfaces.

PalancaCambios


    public class PalancaCambios : IPalancaCambios
    {
        private int _velocidadActual;

        #region IPalancaCambios Members

        public void InsertarVelocidad(int velocidad)
        {
            Console.WriteLine("Introduciendo marcha " + velocidad);
            this._velocidadActual = velocidad;
        }

        public void PuntoMuerto()
        {
           Console.WriteLine("Sacando velocidad " + this._velocidadActual);
           this._velocidadActual = 0;
        }

        public int GetVelocidadActual()
        {
            return _velocidadActual;
        }

        #endregion
    }

Embrague


    public class Embrague : IEmbrague
    {
        #region IEmbrague Members

        public void PresionarEmbrague()
        {
            Console.WriteLine("Embrague presionado");
        }

        public void SoltarEmbrague()
        {
            Console.WriteLine("Embrague suelto");
        }

        #endregion
    }

Acelerador


    public class Acelerador : IAcelerador
    {
        #region IAcelerador Members

        public void PresionarAcelerador()
        {
            Console.WriteLine("Acelerador presionado");
        }

        public void SoltarAcelerador()
        {
            Console.WriteLine("Acelerador levantado");
        }

        #endregion
    }

El cliente, por lo tanto, tendría que realizar las siguientes acciones si quisiera realizar un cambio de marcha:


            IAcelerador acelerador = new Acelerador();
            IEmbrague embrague = new Embrague();
            IPalancaCambios palancaCambios = new PalancaCambios();

            acelerador.SoltarAcelerador();
            embrague.PresionarEmbrague();
            palancaCambios.PuntoMuerto();
            palancaCambios.InsertarVelocidad(3);
            embrague.SoltarEmbrague();
            acelerador.PresionarAcelerador();

Esto hará que se muestre el siguiente comportamiento:

Veamos ahora cómo podemos implementar nuestro patrón Facade para simplificar este proceso y -sobre todo- desacoplar al máximo nuestra clase cliente del propio subsistema.

El principio de mínimo conocimiento

También conocido como Ley de Deméter, es la base de la idea de loose coupling o acoplamiento débil. Se basa en los siguientes conceptos:

  • Una clase tan sólo debe conocer aquellas otras clases con las que tenga que relacionarse de una forma estrecha.
  • Una clase únicamente debe hablar con sus «amigas», evitando a toda cosa «hablar con extraños».

La razón de ser de esta ley es fomentar uno de los principios básicos de la programación orientada a objetos: reducir al mínimo el acoplamiento. ¿Que qué es el acoplamiento? El acoplamiento no es más que el nivel de dependencia que unas clases tienen respecto a otras. Cuanto menor sea el acoplamiento, menor será el impacto en nuestro programa en el momento en el que alguno de los elementos del sistema cambie.

Siguiendo con el ejemplo, vemos que el conductor tiene que relacionarse con tres elementos: el embrague, el acelerador y la palanca de cambios. Imaginemos que nuestro coche tiene una avería y tienen que cambiarnos la caja de cambios, haciendo que la marcha atrás, en lugar de estar en la posición inferior derecha, se encuentre en la posición inferior izquierda.

El problema de que cambie este elemento implica que se verán afectados todos los demás elementos que interactúen con él. En este caso, todos los conductores que utilicen el vehículo deberán cambiar su comportamiento para adaptarse al nuevo cambio de interfaz. Cuanto mayor sea el número de conductores, mayor será el número de cambios que habrá que realizar. La idea del bajo acoplamiento es, precisamente, minimizar las relaciones entre las clases para que, cuando se dé un cambio (que se dará), las consecuencias en los elementos adyacentes sean mínimos. Cuanto menos cosas poseas, menos cosas tendrás que perder. Epicuro habría sido, sin duda, un gran ingeniero de software.

¿Qué tiene que ver el patrón Facade con todo esto? Pues que si los tres elementos (acelerador, embrague y palanca de cambios) fuesen accedidos por una interfaz más sencilla (por ejemplo, una centralita electrónica), el cambio de palanca de cambios no afectaría a ninguno de los conductores, sino que el cambio en una única clase (la centralita) haría innecesario hacer cambios en los hábitos de un número indeterminado de conductores.

Así, el patrón Facade realiza, por lo tanto, dos funciones fundamentales:

  • Simplificar el uso de las interfaces.
  • Desacoplar los clientes del subsistema.

Creemos, por tanto, nuestra clase Centralita que simplifique la interfaz:

Nuestro patrón Facade, por tanto, se implementaría de este modo:


    public class Centralita
    {
        private IEmbrague _embrague;
        private IAcelerador _acelerador;
        private IPalancaCambios _palancaCambios;

        public Centralita(IEmbrague embrague, IAcelerador acelerador, IPalancaCambios palancaCambios)
        {
            this._embrague = embrague;
            this._acelerador = acelerador;
            this._palancaCambios = palancaCambios;
        }

        public void AumentarMarcha()
        {
            _acelerador.SoltarAcelerador();
            _embrague.PresionarEmbrague();
            _palancaCambios.PuntoMuerto();
            if (_palancaCambios.GetVelocidadActual() < 5)
                _palancaCambios.InsertarVelocidad(_palancaCambios.GetVelocidadActual() + 1);
            _embrague.SoltarEmbrague();
            _acelerador.PresionarAcelerador();
        }

        public void ReducirMarcha()
        {
            _acelerador.SoltarAcelerador();
            _embrague.PresionarEmbrague();
            _palancaCambios.PuntoMuerto();
            if (_palancaCambios.GetVelocidadActual() > 1)
                _palancaCambios.InsertarVelocidad(_palancaCambios.GetVelocidadActual() - 1);
            _embrague.SoltarEmbrague();
            _acelerador.PresionarAcelerador();
        }
    }

Si nos fijamos bien en la implementación del patrón, nos daremos cuenta de un par de detalles:

  • El patrón contiene una referencia a cada uno de los elementos que encapsula, que son inyectados a través del constructor (recordemos: la inyección de dependencias es también una buena práctica de programación orientada a objetos).
  • Los parámetros que recibe el constructor son interfaces en lugar de clases. Otro buen principio de programación orientado a objetos era el de depender de abstracciones, no de concreciones.
  • La existencia de la clase Centralita no implica que las clases no puedan utilizarse tal y como las usábamos hasta el momento: este patrón no reduce la visibilidad de los objetos que encapsula, simplemente ofrece una alternativa simplificada de su uso.
  • Al encapsular las clases dentro de Centralita estamos desacoplando las clases contenidas en ella del resto de clases cliente, centralizando los cambios en el supuesto de que alguna de las clases acopladas cambie. ¡Ojo! Si decidimos seguir utilizando las clases encapsuladas por otros medios, no estaremos reduciendo el acoplamiento.

Finalmente, realizamos la invocación a través de nuestro patrón Facade. El cliente debería implementar un código muchísimo más sencillo:


            IAcelerador acelerador = new Acelerador();
            IEmbrague embrague = new Embrague();
            IPalancaCambios palancaCambios = new PalancaCambios();

            Centralita centralita = new Centralita(embrague, acelerador, palancaCambios);
            centralita.AumentarMarcha();

El resultado sería el siguiente:

¿Cuándo utilizar este patrón? Ejemplos reales

Los escenarios más habituales para el uso de este patrón son subsistemas fuertemente acoplados en los que existan secuencias de interacciones que se repitan con frecuencia. Un ejemplo real podría darse en el cálculo del riesgo financiero de un cliente de un banco a la hora de concederle una hipoteca o un crédito. Un patrón Facade se encargaría aquí de conectar con todos los subsistemas implicados (créditos, ahorros, impagos) y realizar un cálculo que proporcione este tipo de información.

Fuentes:

9 comentarios

  1. Una pregunta sobre el metodo siguiente:

    public void AumentarMarcha()
    {
    _acelerador.SoltarAcelerador();
    _embrague.PresionarEmbrague();
    _palancaCambios.PuntoMuerto();
    if (_palancaCambios.GetVelocidadActual() < 5)
    _palancaCambios.InsertarVelocidad(_palancaCambios.GetVelocidadActual() + 1);
    _embrague.SoltarEmbrague();
    _acelerador.PresionarAcelerador();
    }

    Es posible que siempre termine devolviendo _velocidadActual = 1 ? Porque en _palancaCambios.PunttoMuerto() siempre vuelve a cero, o sea no veo como logra llegar a mas velocidades

  2. IAcelerador acelerador = new Acelerador();
    IEmbrague embrague = new Embrague();
    IPalancaCambios palancaCambios = new PalancaCambios();

    acelerador.SoltarAcelerador();
    embrague.PresionarEmbrague();
    palancaCambios.PuntoMuerto();
    palancaCambios.InsertarVelocidad(3);
    embrague.SoltarEmbrague();
    acelerador.PresionarAcelerador(); Buen post, esta parte del código seria el program.cs o cual seria

Deja un comentario