¿Una Interfaz solo con propiedades? – Refactoring

e40f2722-bccf-45d5-8bbc-4ba2dff9c54cEl otro día pude compartir una hora hablando de clean code y algunas best practices con el grande de @elBruno. La verdad es que pasé un rato bastante agradable y ya espero la próxima 😉

Unos días después de esa charla me encuentro con una de esas situaciones en que preguntando y preguntando, la mayoría de las veces basta con preguntarse uno mismo, te llevan a una refactorización que hace más limpio tú código y deja alguna que otra conclusión chula.

Resulta que en un proyecto X tengo una interfaz IX que tiene 2 propiedades y ya está, dos propiedades con un get y esa es toda mi interfaz. Una interfaz, y seguro esto ya lo saben todos, define un contrato que hay que cumplir e implementar, por lo tanto, mi interfaz IX obligaba a cada clase que la implementa, a tener esas dos propiedades.

Con la primera clase todo bien, siempre hay alguna herramienta en Visual Studio que ayuda a generar las implementaciones. Cuando escribí la segunda clase, me acordé que el mismo código lo tenía en la clase anterior. Cuando iba a escribir la tercera clase, tuve una visión de futuro ¿Llegaré a escribir la décima clase que implementa esta interfaz?

Cuando llegamos a este punto, y antes de continuar soportando ver el mismo código una y otra vez, lo mejor es parar y buscar la base del problema.

Conversando con yo mismo

  • ¿Por qué en cada clase se repite exactamente el mismo código?
  • Porque la interfaz que implementa te obliga
  • ¿Qué hago si no quisiera repetir lo mismo en cada clase?
  • No deberías usar una interfaz. ¿Quizás una clase abstracta?
  • Pero por algo usé una interfaz
  • Para obligar a cada clase a cumplir un contrato e implementar un comportamiento
  • Mmm Estoy cumpliendo un contrato ¿y el comportamiento?
  • Clic! Aquí algo no me cierra. Estoy usando una interfaz para hacer algo que no hago.

Refactoring

  • Mi interfaz ha dejado de ser una interfaz para convertirse en una clase abstracta.
  • Las dos propiedades se implementan dentro de la clase abstracta con un get público y un set protected.
  • Cada clase que hereda, hace uso de esas propiedades.
  • Al estar en una clase padre y ser public/protected, cada clase hija tiene acceso a las propiedades sin necesidad de declararlas.

Hasta aquí “el problema” ya estaba resuelto, pero me quedaba una pregunta que me hice una vez que terminé el refactoring y fue el motivo por el cual me decidí a escribir el post.

  • ¿Tenía sentido tener una interfaz solo con propiedades?

Aún sigo buscando algún ejemplo que aplique, de momento la respuesta es NO. ¿Por qué no? Porque una interfaz tiene sentido cuando queremos obligar a que se haga algo sin saber cómo se hará. El qué y el cómo en este caso definen el comportamiento de un objeto.

En POO, las propiedades definen el estado de un objeto y no su comportamiento. Sabemos que un objeto se comporta según su estado, y aquí recordarán el clásico ejemplo de, “si un avión está en pista (estado), no podemos hacer que aterrice (comportamiento)“.  Por lo tanto, LQQD, si definimos solo propiedades dentro de una clase, no tendríamos comportamiento, y si no hay comportamiento, no necesitamos interfaz.

🙂

Esta entrada fue publicada en Clean Code, POO, Refactoring. Guarda el enlace permanente.

4 respuestas a ¿Una Interfaz solo con propiedades? – Refactoring

  1. Juanma dijo:

    Yo no creo que sea algo raro (ni malo) tener un interfaz sólo con propiedades. Como bien dices, es una forma de definir un contrato, y ese contrato puede implicar únicamente que se expone determinada información.

    Dejando de lado que utilizar herencia para implica un acoplamiento muy grande (¿Qué vas a hacer si tienes dos interfaces con distintos juegos de propiedades? ¿Pasarte a C++ para usar herencia múltiple?), creo que el argumento de la implementación es incompleto, porque partes de la idea de que toda propiedad tiene un getter y un setter, y que es la única forma de implementarlo.

    Es posible que el getter requiera algún tipo de cálculo, y en ese caso el comportamiento es distinto.

    Te pongo un ejemplo chorra (que no sé como se verá tras perder el formato ;-), con varios “documentos” que tienen una referencia y un importe asociado. Cada uno calcula eso de maneras distintas, por ejemplo, una factura a partir de sus líneas, pero un cupón a partir de un importe fijo:

    interface IDocument {
    string Reference { get; }
    decimal Amount {get; }
    }

    class Invoice: IDocument {
    // … datos de la factura
    public string Reference {
    get { return ‘INV/’ + this.number; }
    }
    public decimal Amount {
    get { return this.lines.Sum(x => x.Quantity * x.Price) – this.discount + this.shippingCost; }
    }
    }

    class Voucher: IDocument {
    // … datos del cupón
    public string Reference {
    get { return this.identifier; }
    }
    public decimal Amount {
    get { return this.amount; }
    }
    }

  2. Que bueno tenerte por aquí Juanma! Me estoy imaginando lo que se avecina a principio de octubre 😉

    Antes de entrar en el tema, no perdamos de vista que estamos hablando de clases que solo tienen propiedades (sin un solo método) 🙂

    Al tema…

    Sigo sin verlo necesario, para variar un poco nuestros debates 🙂 Me doy un salto a la vida real y sueño con encontrar a ese alguien que me haga un contrato por no hacer nada 🙂 Creo que cuando hablamos de contrato, siempre se espera que se sigan unas reglas y que exista comportamiento, o sea, que se haga algo.

    El ejemplo de propiedades con lógica no cambia, o no debería cambiar, el estado del objeto. Un objeto solo cambia, o debería cambiar, mediante acciones que se realicen sobre él.

    Siguiendo con tu mismo ejemplo, Invoice y Voucher no cambian su estado por el hecho de tener alguna lógica dentro de las propiedades Amount y Reference. Invocar a estas propiedades no implican una acción, tan solo te retornan un estado. Además, en tu ejemplo asumes que solo tendrás dos clases que implementan la interfaz, y que esa dos clases tendrán implementaciones distintas para esas propiedades. ¿Seguro que cuando aparezca la clase Transfer y Coupon no vas a repetir la misma lógica? Alguna la tendrá y sino, sigo viendo una clase abstracta con propiedades virtuales si llevan lógica, o abstractas si quiero que se redefinan en todas las clases que heredan.

    Dejé para el final el tema de la herencia múltiple porque en realidad usar interfaces para resolver este problema, es un parche 🙂 La herencia múltiple existe en la POO, que algunos lenguajes orientados a objetos no la implementen no quiere decir que la solución que encontremos para este problema, sea la forma correcta de hacerlo siempre. Ojo, encontrar un caso real donde salga herencia múltiple bien aplicada cuesta.

    Cuando heredamos, las clases hijas obtienen representaciones de estados (propiedades) y acciones (métodos) que normalmente reutilizamos o redefinimos completamente. A donde quiero llegar es que aún si me fuese a C++ y pudiera tener a mi disposición herencia múltiple, no soy capaz de encontrar un caso en que tenga que heredar de dos clases que solo contengan propiedades.

    Los DTOs por ejemplo son objetos que normalmente solo tienen propiedades. ¿Alguna vez hemos necesitado herencia múltiple para definir un DTO?

    😉

  3. Juanma dijo:

    Para mi, los DTOs no son objetos, son sacos de propiedades que, accidentalmente, se implementan como objetos en lenguajes que no tienen otro tipo de abstracción mejor 🙂

    Sobre el tema de la herencia, suponte que tienes el interfaz que describo antes para poder procesar distintos tipos de documentos para, por ejemplo, exportarlos a un CSV con su referencia y su importe total.

    Es posible que varios de ellos tuvieran la misma implementación (como Voucher y Coupon) y otros una diferente pero similar entre ellos (como Invoice, Order y DeliveryNote), ¿aplicarías herencia? ¿Cómo? ¿Entre quienes?

    Ahí el interface *sí* es un contrato (el contrato que cumplen aquellos objetos que pueden ser exportados a CSV), y el comportamiento es “calcular información que necesito”. No veo por qué un contrato tiene que implicar cambio de estado, creo que son dos cosas completamente ortogonales.

    ¿Y si luego tienes otro proceso que también requiere actuar con los documentos, pero necesita otro tipo de información, por ejemplo el usuario que los ha creado para obtener algún tipo de auditoría? ¿Mezclarías esa responsabilidad en la clase base donde están las propiedades que se usan para el generador de CSVs? ¿No sería eso una violación de SRP?

  4. Qué cruel eres, con lo bien que les quedó eso de “Data transfer object” 😀 ¡Me guardo tu definición! Algún día, con tu permiso, la usaré 😉

    A ver cómo me sale la herencia. Mañana seguro le hago refactoring 🙂 PD: Dejo una sola propiedad para abreviar

    class abstract Document
    {
    public virtual decimal Amount
    {
    get { return this.amount; }
    }
    }

    class Voucher: Document
    {
    // Reutilizo lógica de Document
    }

    class Coupon: Document
    {
    // Reutilizo lógica de Document
    }

    class PurchaseDocument: Document
    {
    public override decimal Amount
    {
    get { return // lógica común a otras clases; }
    }
    }

    class Invoice: PurchaseDocument
    {
    public override decimal Amount
    {
    get { return // base.Amount + nueva lógica de Invoice; }
    }
    }

    y así…

    Sobre si aplica o no una interfaz, lee lo que has puesto: “aquellos objetos que pueden ser exportados a CSV”. ¿Que tengas un objeto que implementa una interfaz con propiedades obliga a esos objetos a que puedan ser exportados a CSV? Esos objetos los exportas a CSV y a lo que quiera, e incluso, ni siquiera tienes por qué exportarlos porque nadie te obliga 🙂 Para que esa afirmación fuera cierta, esos objetos tendrían que saber cómo se exportan ellos mismos a CSV y eso, es una acción.

    Siguiendo con tu mismo análisis, ¿me dices que un objeto con propiedades a las que asignas valores y otras a las que accedes a un resultado para “calcular información que necesito” es comportamiento? A mi esto me huele a “un saco de propiedades que se implementan como objetos en lenguajes que…” 😛 Name, LastName y el clásico FullName (Name + LastName)

    No entendí lo del usuario para auditoria. Si solo lo necesita un nuevo objeto, es una lógica solo de ese objeto. Si es de un grupo de objetos nuevos, tendría otra clase común parecida a PurchaseDocument. ¿Dónde violas ahí el SRP? 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *