Implementación del ADT racional (C++).

   El Ejemplo Racional muestra una posible implementación de ADT racional definido en la entrada Tipos de Datos Abstractos (ADT).

   Las líneas 12 y 13 definen los atributos de un número racional (p / q). Los detalles relacionados con los métodos de tipo set y get (líneas 20-23), ya han sido comentados en la sección Métodos y atributos de la entrada POO Mensajes y métodos y no se repetirán aquí.
 
   Por otro lado, los constructores definidos (líneas 31-44) requieren de una ampliación en su explicación, ya que difieren un poco de lo hasta ahora comentado para los constructores.
 
   El constructor principal o base es el definido en las líneas 39-44. Observe que dicho constructor recibe dos argumentos, mismos que representan el numerador (n) y el denominador (d) del número racional que se desea inicializar. En la línea 42, el constructor realiza una verificación del denominador (cláusula de condición), de tal forma que si éste es cero, se lanza (throw) una excepción (línea 43) para indicar el error a través de la clase arithmetic_exception (línea 16). Note también que el mismo comportamiento es considerado en las líneas 52 y 53 para el método "establece_denominador". Una posible salida del programa al presentarse la situación anteriormente planteada, se muestra a continuación:
El denominador no puede ser cero.
Programa finalizado.
   En C++ es posible utilizar cualquiera de los tipos de datos existentes (definidos y predefinidos) como excepción. Una forma de comprender las excepciones es como una manera de ceder el control a "alguien" cuando no se puede emprender localmente ninguna acción con sentido. Otra forma de pensar en una excepción es "alguna parte del sistema no ha podido realizar lo que se le ha solicitado".

   El manejo de excepciones está conformado básicamente sobre tres palabras clave: try, catch y throw. El código que se desee supervisar respecto a un posible error debe colocarse dentro de un bloque try (líneas 99-109), y cachadas o atrapadas por uno o más bloques catch (línea 109).
 
    La forma general de un bloque try-catch es la siguiente:

          try { 
                    // bloque try
           } catch (tipo1 arg) { 
                    // bloque catch1
           } catch (tipo2 arg) { 
                    // bloque catch2
           } catch (tipo3 arg) { 
                    // bloque catch3
                        .
                        .
                        .
          } catch (tipoN arg) {
                     // bloque catchN 
          }

   Cuando una excepción es lanzada, debería ser atrapada por el correspondiente y primer catch que mejor corresponda con el tipo de la excepción, y "arg" recibe su valor. Si no se lanza ninguna excepción, es decir, si no ocurre ningún tipo de error dentro de una cláusula try, ninguna cláusula catch es ejecutada. throw genera y lanza la excepción especificada por su argumento (arithmetic_exception (línea 16 del ejemplo)). Si se lanza una excepción para la que no hay una cláusula catch correspondiente, normalmente se genera una terminación anormal del programa. Esto último puede cambiarse, pero los mecanismos de gestión para excepciones no manejadas quedan fuera de los alcances de este blog.

   La implementación de las operaciones de suma(+) y producto(*) aparecen en las líneas 65-72 y 74-81 respectivamente, a través del mecanismo de sobre carga de operadores de C++ (la implementación de las operaciones resta(-) y división(/) se dejan como ejercicio para el lector). Observe que en las líneas 68 y 69 (77 y 78) se accede directamente a los atributos p y q a través del operador punto (.), es decir, sin hacer uso de los métodos de acceso set y get; esto es así debido a que el objeto s (m) es de la misma clase que define al operador "+" ("*"), por lo que el acceso es concedido sin ningún tipo de inconveniente.

   La sobrecarga de operadores se realiza a través de la definición de funciones de operador, que son las que definen el comportamiento que tendrá el operador asociado. Una función de operador se crea a través de uso de la palabra reservada "operator" (líneas 65 y 74).

   Por otro lado, note también que para el objeto (this) que recibe el mensaje suma (+) (multiplica(*)), los atributos p y q de las líneas 68 y 69 (77 y 78) son accedidos sin ningún tipo de operador ni mensaje, es decir, son accedidos por contexto y ámbito, ya que el objeto que recibe el mensaje conoce cuáles son sus propios atributos y métodos, por lo que dentro del método la expresión: 
p * r.obten_denominador( );
es equivalente a la expresión:
this.p * r.obten_denominador( );
   Por último en lo que respecta a la sobre carga de operadores, es importante resaltar que ambos métodos utilizan un objeto local al método (s y m respectivamente) para almacenar el resultado que generan y poder regresarlo como valor de retorno del método, por lo que es posible deducir que si un método requiere de variables u objetos para realizar su labor, éstos pueden declarase locales al método. En este sentido, sería un error respecto al paradigma (al menos un mal diseño) el declarar dichas variables u objetos como atributos de la clase, ya que no describen alguna propiedad o característica inherente a los objetos que deriven de ella, sino que sólo son elementos necesarios para el almacenamiento del resultado y la realización del cálculo correspondiente a la responsabilidad (comportamiento) del método. Lo anterior se aplica aún cuando se utilizara una sola variable para almacenar el resultado de cualquiera de las cuatro operaciones aritméticas (dos de las cuales, se dejan como ejercicio para el lector).

   Observe que el método "to_string" (líneas 83-90) tiene como responsabilidad el regresar una representación en cadena del objeto que recibe el mensaje.

   La función main muestra una posible ejecución de prueba del ejemplo, y la salida correspondiente se muestra a continuación:
r1 = 1/3
r2 = 2/5
1/3 + 2/5 = 11/15
1/3 * 2/5 = 2/15
   Por último, aunque la función main ya debería poder explicarse por sí misma, se resaltarán los siguientes aspectos:
  1. Las líneas 100 y 101 crean los números racionales r1 y r2, donde r1 = 1 / 3 y r2 = 2 / 5.
  2. Las líneas 106 y 108 envían los mensajes suma (r1 + r2) y multiplica (r1 * r2) respectivamente. Note que el valor de retorno es también un número racional, al cual le es enviado el mensaje "to_string" para obtener la representación en cadena de los resultados correspondientes.

No hay comentarios.:

Publicar un comentario