Lo básico parte 3: Herencia

Uno de los pilares de la programación orientada a objetos, es la herencia o la capacidad de crear jerarquías de objetos que van refinando o modificando cierto comportamiento.  Por ejemplo, imaginemos una empresa que vende artículos para el hogar. Imaginemos que vende lavadoras, estufas y licuadoras. Para motivar a sus vendedores, han puesto un sistema de comisiones que funciona de la siguiente manera:

Todos los vendedores tienen una cuota de ventas. Si el 25% de las ventas son por lavadoras, el vendedor obtiene una comisión del 15% de su salario. Si logra al menos el 50% de su comisión con la venta de licuadoras, entonces recibe una comisión del 30% de su salario. Por ultimo si logra el 25% de su cuota vendiendo estufas obtiene una comisión del 20% de su salario.

Ahora, supongamos que estos vendedores están distribuidos en zonas norte, este y sur. Los vendedores de la zona sur tienen una comisión adicional de 10% si exceden la cuota de ventas.

Podríamos representarlo de la siguiente manera:

Herencia

Entonces tenemos una clase CalculadoraComisiones. Los objetos de esta clase contienen toda las reglas para calcular las comisiones de cualquier vendedor. Una forma de implementar esto podría ser algo como:

public interface Vendedor
{
   bool CumplePorcentajeVenta(double porcentaje, string producto);
}


public class CalculadoraComisiones
{
  public virtual decimal CalcularComisiones(Vendedor vendedor)
  {
	  decimal comision = 0;
	  comision = comisionPorLavadoras(vendedor);
	  comision += comisionPorLicuadoras(vendedor);
	  comision += comisionPorEstufas(vendedor);
	  
	  return comision;
  }
  ...
}

Sin embargo para los vendedores de la zona sur, tenemos una regla adicional a las de todos los demás.

public class CalculadoraComisionesZonaSur:CalculadoraComisiones
{
   override CalcularComisiones(Vendedor vendedor)
   {
	   decimal comision = base.CalcularComisiones(vendedor);
	   comision + = comisionPorSobreVenta(vendedor);
	   return comision;
   }
   ...
}

Listo. Solo hay que utilizar el objeto correcto para calcular las comisiones.

void main(){
   List<Vendedor> vendedores = obtenerVendedores("Norte");
   vendedores.AddRange(obtenerVendedores("Este"));
   
  List<Vendedor>vendedoresSur = obtenerVendedores("Sur");
 

  var calculadora = new CalculadoraComisiones();
  for each(var vendedor in vendedores)
 {
    decimal comision = calculadora.CalcularComision(vendedor);
    Console.WriteLine(comision);
 }

  calculadora = new CalculadoraComisionesZonaSur();
 for each(var vendedor in vendedoresSur)
 {
  decimal comisiones = calculadora.CalcularComision(vendedor);
  Console.WriteLine(comision);
 }
}

Sencillo. Definitivamente este código deja mucho que desear pero ya lo iremos ajustando mas adelante.

Ahora quisiera señalar que no hay ninguna propiedad en el diagrama o en el código.
Hice esto deliberadamente para hacer énfasis en un principio fundamental: Se hereda el comportamiento, no los datos. En otras palabras usamos herencia cuando tenemos un objeto que tiene un método que queremos ajustar un poco a nuestras necesidades, no cuando queremos reutilizar datos. Eso es un error muy común. Viene de la mentalidad utilizada en las bases de datos relacionales, en la que se trata de reducir la duplicación de datos. En el caso de la programación orientada a objetos, se trata de reducir la duplicación de métodos. No tengan miedo de repetir datos en diferentes objetos.

Pues eso es todo por hoy, la próxima vez veremos el significado de la frase «favorecer composición sobre herencia» y como eso puede ayudarnos a mejorar el código de este ejemplo. Hasta entonces.

 

Lo basico parte 2

Haciendo que las cosas sucedan

La cuestión principal cuando se trata de OOD es: ¿Qué hace esto? Los objetos son sobre todo de hacer las cosas. Tienen algunos datos para hacer esta cosas, pero eso es todo. He visto cantidad de códigos donde los objetos no son más que contenedores de datos. No es OOP. Puede que estén utilizando un lenguaje de programación orientada a objetos, pero no están haciendo programación orientada a objetos. Ahora, no pretendo juzgar a nadie. Solía escribir código como ese. Esta es una de las razones que estoy compartiendo esto contigo. Los objetos hacen cosas. Deben tener una razón de existir. Un papel que cumplir.

Si prestaste atención al post anterior, definí un VirtualPet en términos de lo que hace. Duerme, juega y come. No empiezo con cosas como el nombre o la edad. Seguro que ésos se pueden utilizar para identificar un objeto determinado pero ¿que es tan bueno sobre la identificación de un objeto inútil?

Al diseñar una base de datos se inicia con datos. Al diseñar un modelo de objetos hay que empezar con el comportamiento. Parece haber un montón de gente que no sabe o no entiende esta diferencia sutil, lo que conduce a un montón de código difícil de mantener. Han oído hablar de patrones de diseño y tratan de aplicarlos a su código pseudo-orientado a objetos, a menudo resultando en complejidad innecesaria. Yo mismo he pasado por esto, preguntándome qué era tan grande de estos denominados patrones. ¿Te ha sucedido?

Encontrar al hombre adecuado para el trabajo

Una buena parte de la vida de un desarrollador se va en entender y aprender conceptos. Esto es porque muy a menudo los objetos representan conceptos de la vida real. Más aún, el mismo concepto puede tener un significado diferente en un contexto diferente. Un número de teléfono de casa puede ser un dato opcional en un sistema de gestión de clientes pero puede ser una pieza fundamental en un sistema de entrega de pizza;).

Crear un sistema flexible usando OOP consiste en gran parte en la identificación de los conceptos clave relacionados con un dominio y las responsabilidades relacionadas con ellos en un contexto determinado.

Así que vamos a hacer un pequeño ejercicio.

Supongamos que estamos haciendo un sistema bancario. Estamos asignados al siguiente caso:

Cobrar un cheque.
El Banco recibe un cheque de un cliente. 
El cliente puede solicitar retirar el efectivo o depositarlo a otra cuenta. 

Depositar en una cuenta
El cliente deposita una cantidad a una cuenta.

El enfoque orientado a datos

Así que aquí lo tenemos. ¿Vamos a pensarlo por un segundo sería? ¿por dónde empezamos? ¿Cuáles son los principales conceptos aquí? vamos a ver tiene que haber un cliente ¿correcto? quiero decir el cliente siempre comienza todo, trae el cheque, depositar el dinero, debemos tener uno ¿verdad? ¿verdad? Debe tener un nombre, tal vez una dirección, ¡seguramente un número de cuenta! Puede parecer algo así como:

imagen

Validar el modelo

Muy bien ¿qué sigue? bueno lo siguiente es preguntar: ¿Qué hace esto? ¿Qué hace el cliente? ¿Cuál es la Raison d’être? adelante tomate tu tiempo. Piensa en ello.

Así que imagínate que estoy en la misma habitación con el autor del diseño anterior, aquí esta cómo seria esta conversación (probablemente):

-así que ¿qué hace al cliente?
-bien obviamente cada cuenta tiene un dueño. Sería el cliente.
-sí, pero ¿qué hace?
-Mmm... ¿Qué dices? (frunciendo el ceño mientras piensa) eso sería... ayudar a encontrar una cuenta.
-Siempre podría utilizar el número de cuenta
-sí, pero no todo el mundo lo sabe. Caramba yo no sé mi código postal!
-muy bien, te entiendo. Así que el cliente ayuda a encontrar la cuenta correcta.
-Así es.
¿-Pero entonces, no tendría sentido añadir el cliente como un atributo de la cuenta?
-No creo que sea una buena idea.
-¿por qué no?
-porque son cosas diferentes. Diferentes conceptos.
-Así que estás diciendo que cada vez que quiero encontrar una cuenta ¿primero tengo que buscar el objeto de cliente?
-sí...
- y entonces supongo que yo llamaría un método llamado GetAccount()
-no necesariamente. Sólo tienes que ir a la propiedad de CustomerAccount
-por lo que el cliente no hace nada. Es solo un marcador.
-si hace! te permite acceder a la cuenta!
-… Pensando en sonrisa

El significado de «hacer»

Como se puede ver en nuestro debate ficticio, el problema radica en el significado de probablemente una de las palabras más usadas. Para ayudarnos a entender lo que «hacer» significa en este contexto permitanme reformular la pregunta: ¿Cuál es la misión de este objeto?

¿Qué hace un cartero? ¿Cuál es su misión? sería entregar correo. Él no te da su bolsa para que busques su correo. Y no importa si utiliza una bicicleta o un elefante mientras tu recibas tu correo. Así que «hacer» en este sentido define el propósito final del objeto.  En el argot OOP llamamos a esto la responsabilidad del objeto.

OK, ahora que aclaramos lo que queremos decir con la pregunta volvamos a nuestro pequeño ejercicio. ¿en que estabamos? ¡ Ah! ¿Qué hace el cliente? la respuesta corta es: nada. No entraré en la respuesta largaGuiño de sonrisa.

Continuemos con el objeto cheque (check en el diagrama). ¿Qué hace el cheque? por lo que hemos leído anteriormente recibe dinero de una cuenta. Regresaremos a él más adelante.

OK, el siguiente es el objeto cuenta. ¿Qué hace la cuenta? ¿mmm… podríamos decir que mantiene un saldo? ¿tiene sentido para ti?

Hasta el momento tenemos lo siguiente:

Objeto ¿Qué hace?
cliente nada
cheque obtener dinero de una cuenta
cuenta mantiene un saldo

La lente a través de la que vemos

El tipo de análisis «¿Qué hace esto?» no es nuevo. Está implícita en la técnica de tarjetas CRC de Kent Beck, así como en TDD. Lo importante aquí es que tenemos objetos que hacen cosas. Ahora manos a la obra Guiño de sonrisa

Lo primero que viene a la mente es el objeto cliente. Realmente no hace nada. Cualquier objeto que no hace nada en el modelo, sólo lo hacen más difícil de entender, así que vamos a deshacernos de ese cliente… (Es decir el objeto, no ese tipo presumido de mente cerrada de tu último proyecto! Apuesto a que estás pensando en él/ella ahora Sonrisa enojada)

Woah! ¿ningún objeto cliente? ¡pero es un modelo de banca el que estamos haciendo aquí! ¡tiene que existir un objeto cliente!

Bueno, veras, un modelo de objetos muestra sólo lo que es importante para un contexto específico. Solo pone de relieve lo que es importante para un caso de uso específico. Tal vez el siguiente ejemplo hará esto claro.

Optimizado-mundo-mapa-terreno

Tengo 2 mapas.  Se podría decir que ambos son modelos del mundo. La diferencia radica en lo que ellos están tratando de transmitir. El primero muestra las zonas horarias. El segundo pone énfasis en el terreno.

Así que como veras, un modelo de objetos es como un mapa. No muestra la realidad con todo detalle, solo lo relevante para el resolver el asunto pendiente. Llamamos a este proceso de identificación de los objetos importantes y acciones pertinentes, el proceso de abstracción. Y los objetos resultantes son llamados abstracciones, puesto que representan las cosas importantes en el dominio. OK, ahora demostrado mi punto, espero que me entiendas cuando digo que el cliente no es relevante para este modelo. Al menos no en este momento.

Cavando más profundo

Ahora que ya tenemos objetos y responsabilidades asignadas a ellos, vamos a averiguar cómo les llevan a cabo. Voy a empezar con el caso de uso «depósito en cuenta» ya que al parecer es el más sencillo (siempre empiezo con los casos de uso más sencillos). Este caso me dicen que el objeto cuenta tiene una operación de depósito que añade una cantidad a su saldo actual. Fácil.

Objeto ¿Qué hace? ¿Cómo lo hace?
cuenta mantiene un saldo Depositar(monto)

Ahora a «cobrar un cheque». Así que tenemos un método «cambiar» en el objeto cheque y necesita una cuenta de donde retirar. Pero el objeto de la cuenta no tiene una forma de retirar de sí mismo así que tenemos que crear una.

Objeto ¿Qué hace? ¿Cómo hace?
cheque obtener dinero de una cuenta Cambiar()
cuenta mantiene un saldo Depositar(monto)
Retirar(monto)

El cliente también puede depositar el cheque a otra cuenta. Efectivamente se trata de una operación de transferencia. ¿Donde debemos poner esto? ¿Quien tiene esta responsabilidad? para responder a esa pregunta tenemos que entender los efectos de esta operación. ¿Qué va a pasar después de la operación se ha completado? Si todo va bien una cuenta tendrá un decremento en su saldo mientras que en otra habrá incrementado. ¿Y quién es responsable de mantener un saldo? ¡la cuenta en si! no es un objeto ServicioCuenta o un objeto Transferencia sino que es la cuenta quien tiene esta responsabilidad. Así que vamos a crear un método transferencia en el objeto cuenta y un método CambiarEnCuenta en el objeto cheque.

Objeto ¿Qué hace? ¿Cómo hace?
Echale un vistazo obtener dinero de una cuenta Cambiar()
CambiarEnCuenta (cuenta)
cuenta mantiene un equilibrio Depositar(monto)
Retirar(monto)
Transferir(monto,cuenta)

Así que ahí lo tienen. Ambos casos con sólo 2 clases simples. Seguramente que tienes una idea bastante clara de cómo codificar esto ahora. Puede ser algo similar a:


class Cuenta
{
    decimal _saldo= 0;

    public bool depositar(decimal monto)
    {
        //reglas de negocio    
        _saldo += monto; 
        return true;
    }

    public bool Retirar (decimal monto)
    {
        //reglas de negocio   
        _saldo -= monto; 
        return true;
    }

    publiv bool Transferencia (decimal monto, cuenta aCuenta)
    {
        //reglas de negocio   
        aCuenta.Depositar(monto);
        Retirar(monto);
        return true;
    }
}

class Cheque
{
    decimal _monto= 0;
    Cuenta _cuentaOrigen;

    Control público (decimal monto, Cuenta cuenta)
    {
        _monto= cantidad;
        _cuentaOrigen= cuenta;
    }

    public bool Cambiar()
    {
        //reglas de negocio
        return _cuentaOrigen.retirar(_monto);
    }

    public bool CambiarEnCuenta(Cuenta cuenta)
    {
        //reglas de negocio
        return _cuentaOrigen.Transferir (monto: _monto, aCuenta: cuenta);
    }
}

Tarea: Volver a cualquiera de tus proyectos anteriores e identificar por lo menos 2 casos de uso, sus principales conceptos y si hay objetos que representan cada uno. Luego encontrar cuales son sus responsabilidades y, si no son claras, definirlas. Por último, identificar las operaciones necesarias para llevar responsabilidades del objeto. Compara y escribe tu comentario más abajo. Me avisas si este cambio simplifica el código actual o no (una comparacion simple es suficiente).

Nos vemos en el próximo post.