Cuando usar Interface y cuando una clase base

domingo, 10 de noviembre de 2013

¿Cuando usarías Interface y cuando una clase base? es una tipica pregunta sobre programación orientada a objetos, incluso para entrevistas de trabajo y voy a contaros mi punto de vista.

El objetivo de una interface es un contrato que las clases que lo implementan deben cumplir y una clase base tiene como objetivo re-utilización de código entre clases comunes y a su vez mediante métodos abstractos también es un contrato para sus clases derivadas, deben implementar esos métodos las clases derivadas para evitar error de compilación. También con ambas opciones se puede realizar polimorfismo.

Una clase base puede ser abstracta o no, si es abtracta no se puede instanciar.


Cuando usar una interface

Según mi experiencia, interface esta más orientado para la abstraer al consumidor de una clase de su implementación real , es decir, si tengo por ejemplo un cliente para comunicarse con servicio externo, yo me crearía un interface para la comunicación de mi app con este cliente. Desde la clase donde me comunique con el cliente del servicio externo no voy a conocer la clase con la que estoy tratando realmente, solo su contrato, esto nos da la posibilidad de cambiar el cliente del servicio en el futuro sin afectar donde se este usando, siempre que se respete el contrato del interface.

Una de las ventajas es que esto nos va a permitir usar inyección de dependencias. Por ejemplo la clase que se comunica con el cliente puede recibir dicho cliente por parámetro, lo que nos da la posibilidad de inyectar en tiempo de ejecución el cliente del servicio externo.

Una vez tenemos interface y además inyección de dependencias nos va a venir muy bien para pruebas unitarias. Puedo crearme un mock o fake object que simule ser el cliente del servicio externo y así poder probar la clase que se comunica con el cliente sin utilizar el cliente realmente, simulando determinados comportamientos que podría tener este y probar como la clase que se comunica con él reaccionaría.

Cuando usar una clase base

Una clase base esta más enfocada a re-utilización de código y una buena práctica es hacer uso del patrón de diseño Layer Supertype, que viene a decir que en una capa de clases comunes es útil crear una clase base de la que herede el resto porque siempre hay código común que se puede poner en la clase base o sino lo habrá en el futuro.

Volviendo al ejemplo del cliente de un servicio externo, si en lugar de un cliente externo hubiera varios, como por ejemplo un cliente para cada tipo de red social, seria interesante tener una clase base para código común como anotar en un log cada vez que se publica un post y se produce un error con el cliente de twitter o facebook.

Ejemplo

Imaginemos que tenemos un blog y queremos que cuando se publique un post, se comparta dicha publicación en twitter de forma automática.

Podríamos tener un diagrama como este:



Existe un servicio blog, se le pasa su dependencia con el cliente de twitter por el constructor, tiene un método publishPost que cuando es invocado, automáticamente lo notifica en twitter. El código prototipo sería este:

    public interface ITwitterClient
    {
        void PublishTweet(string message);
    }

    public class TwitterClient:ITwitterClient 
    {
        public void PublishTweet(string message)
        {
            try 
         {         
          //se publica tweet 
         }
         catch (Exception)
         {
          //se escribe en un log el error y se lanza excepción
         }
        }
    }

    public class BlogService
    {
        private ITwitterClient _twitterClient;

        public BlogService(ITwitterClient twitterClient)
        {
            _twitterClient = twitterClient;
        }

        public void PublishPost(string htmlPost)
        {
            //publica post

            //publica en twitter
            _twitterClient.PublishTweet("Nuevo post en...");
        }
    }

Esto para una aproximación esta bien, pero ahora imaginemos que también queremos que automáticamente al publicar un post, además de compartirlo en twitter se haga también en facebook.
Entonces yo lo que haría seria refactorizar el nombre del interface a IShareClient. También será necesario crear una clase FacebookClient, en caso de error al publicar en facebook también querremos anotarlo en el log y como este código sera duplicado con el de twitter, ahora puede que tenga sentido crear una clase base para eliminar el código repetido.
Despues de refactorizar este podría ser nuestro diagrama



como se puede apreciar en el diagrama, la interface se ha refactorizado con un nombre más estándar, ya que ahora no solo va a ser un contrato para un cliente de twitter, sino también de facebook. Ahora la interface es implementada por la nueva clase base y los clientes no la implementan, sino que heredan de la clase base y heredan el contrato de la clase base con el método ShareText.
La parte que ahora se centra en la clase base y de esta forma se elimina el código duplicado es el bloque try catch y escribir en el log en caso de error al publicar en la red social. El código del prototipo sería este:

   
    public interface IShareClient
    {
        void Share(string message);
    }
    public abstract class ShareClientBase:IShareClient
    {
        public void Share(string message)
        {
            try
            {
                //se comparte 
                ShareText(message);
            }
            catch (Exception)
            {
                //se escribe en un log el error y se lanza excepcion
            }
        }

        protected abstract void ShareText(string message);
    }
    public class TwitterClient : ShareClientBase
    {
        protected override void ShareText(string message)
        {
            //se comparte
        }
    }
    public class TwitterClient :ShareClientBase
    {
        protected override void ShareText(string message)
        {
            //se comparte
        }
    }
    public class BlogService
    {
        private List _clients;

        public BlogService(List clients)
        {
            _clients = clients;
        }

        public void PublishPost(string htmlPost)
        {
            //publica post

            //publica en redes sociales
            foreach (IShareClient client in _clients)
            {
                client.Share("Nuevo post en...");
            }
            
        }
    }


Ahora con esta arquitectura podemos crear un test de la siguiente forma:
    public class TestTwitterClient:IShareClient
    {
        public void Share(string message)
        {
            throw new Exception("Invalid or expired token");
        }
    }

    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            try
            {
                TestTwitterClient fakeclient = new TestTwitterClient();

                BlogService blog = new BlogService(new List() { fakeclient });

                blog.PublishPost("test");

                Assert.IsTrue(true);
            }
            catch (Exception)
            {

                Assert.Fail();
            }
        }
    }

Nos podemos crear una clase que simule ser un cliente de twitter y que directamente en el método lanzar una excepción que podría lanzar twitter y testear que el comportamiento de nuestra clase blog es correcto. De esta forma no es necesario para pruebas utilizar el cliente de real de twitter ni consumir recursos de red, con lo que los test serán más rápidos de pasar.

Código fuente del prototipo

Libros Relacionados




Comprar en Amazon.es

Comprar en Amazon.com

Comprar en Amazon.co.uk


Comprar en Amazon.es

Comprar en Amazon.com

Comprar en Amazon.co.uk

No hay comentarios:

Publicar un comentario