Skip to content

8. Ejercicio práctico: [IMPOTS] con una arquitec e en capas

Aquí retomamos el ejercicio descrito en el párrafo 4.1. Partimos de la versión con archivos de texto descrita en el párrafo 4.3. Para procesar este ejemplo con objetos, utilizaremos una arquitectura de tres capas:

  • la capa [dao] (Objeto de Acceso a Datos) se encarga del acceso a los datos. En lo sucesivo, estos datos se encontrarán primero en un archivo de texto y luego en una base de datos MySQL;
  • la capa [metier] se encarga de los aspectos de negocio, en este caso el cálculo del impuesto. No se ocupa de los datos. Estos pueden provenir de dos fuentes:
    • la capa [dao] para los datos persistentes;
    • la capa [console] para los datos proporcionados por el usuario.
  • la capa [console] se encarga de las interacciones con el usuario.

A continuación, las capas [dao] y [metier] se implementarán cada una mediante una clase. La capa [console], por su parte, será implementada por el programa principal.

Supondremos que todas las implementaciones de la capa [dao] ofrecen el método getData(), que devuelve una tupla de tres elementos (límites, coeffR, coeffN), que son las tres tablas de datos necesarias para calcular el impuesto. En otros lenguajes, a esto se le llama una interfaz. Una interfaz define métodos (en este caso, getData) que deben tener las clases que implementan dicha interfaz.

La capa [metier] será implementada por una clase cuyo constructor tendrá como parámetro una referencia a la capa [dao], lo que garantizará la comunicación entre ambas capas.

8.1. La capa [dao]

Reuniremos las diferentes clases necesarias para la aplicación en un mismo archivo impots.py. Los objetos de este archivo se importarán posteriormente a los scripts que los requieran.

Comenzamos con el caso en el que los datos se encuentran en un archivo de texto, como en el ejemplo del párrafo 4.3.

El código de la clase [ImpotsFile] (impots.py), que implementa la capa [dao], es el siguiente:


# -*- coding=utf-8 -*-

import math, sys

# --------------------------------------------------------------------------
# clase de excepción propietaria
class ImpotsError:
    pass

# --------------------------------------------------------------------------
class Utilitaires:
    """classe de fonctions utilitaires"""
    def cutNewLineChar(self,ligne):
        # se elimina el marcador de fin de línea si existe
        l=len(ligne)
        while(ligne[l-1]=="\n" or ligne[l-1]=="\r"):
            l-=1
        return(ligne[0:l])

# --------------------------------------------------------------------------
class ImpotsFile:
    def __init__(self,IMPOTS):
        # IMPOTS: el nombre del archivo que contiene los datos de las tablas límite, coeffR, coeffN
        # se abre el archivo
        data=open(IMPOTS,"r")
        #: un objeto de Utilidades
        u=Utilitaires()
        # creación de las 3 tablas; se supone que las 3 líneas de IMPOTS son sintácticamente correctas
        # -- línea 1
        ligne=data.readline()
        if ligne== '':
            raise ImpotsError("La premiere ligne du fichier {0} est absente".format(IMPOTS))
        limites=u.cutNewLineChar(ligne).split(":")
        for i in range(len(limites)):
            limites[i]=int(limites[i])
        # -- línea 2
        ligne=data.readline()
        if ligne== '':
            raise ImpotsError("La deuxieme ligne du fichier {0} est absente".format(IMPOTS))
        coeffR=u.cutNewLineChar(ligne).split(":")
        for i in range(len(coeffR)):
            coeffR[i]=float(coeffR[i])
        # -- línea 3
        ligne=data.readline()
        if ligne== '':
            raise ImpotsError("La troisieme ligne du fichier {0} est absente".format(IMPOTS))
        coeffN=u.cutNewLineChar(ligne).split(":")
        for i in range(len(coeffN)):
            coeffN[i]=float(coeffN[i])
        # fin
        (self.limites,self.coeffR,self.coeffN)=(limites,coeffR,coeffN)

    def getData(self):
        return (self.limites, self.coeffR, self.coeffN)

Notas:

  • líneas 7-8: se define una clase ImpotsError derivada de la clase Exception. Esta clase no agrega nada a la clase Exception. Se utiliza únicamente para contar con una clase de excepción propia. Posteriormente, esta clase podría ampliarse;
  • líneas 11-18: una clase de métodos de utilidad. Aquí, el método cutNewLineChar elimina cualquier marcador de fin de línea de una cadena de caracteres;
  • línea 25: al abrir el archivo puede lanzarse la excepción IOError;
  • líneas 32, 39, 46: se lanza la excepción propia ImpotsError;
  • el código es similar al analizado en el ejemplo del párrafo 4.3.

8.2. La capa [metier]

La clase [ImpotsMetier] (impots.py), que implementa la capa [metier], es la siguiente:


class ImpotsMetier:

    # constructor
    # se obtiene un puntero a la capa [dao]
    def __init__(self, dao):
        self.dao=dao

    # cálculo del impuesto
    # --------------------------------------------------------------------------
    def calculer(self,marie,enfants,salaire):
        # casado: sí, no
        # hijos: número de hijos
        # salario: salario anual

        # se solicitan a la capa [dao] los datos necesarios para el cálculo
        (limites, coeffR, coeffN)=self.dao.getData()

       # número de partes
        marie=marie.lower()
        if(marie=="oui"):
            nbParts=float(enfants)/2+2
        else:
            nbParts=float(enfants)/2+1
        # media participación adicional si hay al menos tres hijos
        if enfants>=3:
            nbParts+=0.5
        # ingresos imponibles
        revenuImposable=0.72*salaire
        # coeficiente familiar
        quotient=revenuImposable/nbParts
        # se coloca al final de la tabla de límites para detener el bucle siguiente
        limites[len(limites)-1]=quotient
        # cálculo del impuesto
        i=0
        while quotient>limites[i] :
            i=i+1
        # debido a que se colocó el coeficiente familiar al final de la tabla de límites, el bucle anterior
        # no puede sobrepasar los límites de la tabla
        # ahora se puede calcular el impuesto
        return math.floor(revenuImposable*(float)(coeffR[i])-nbParts*(float)(coeffN[i]))

Notas:

  • líneas 5-6: el constructor de la clase recibe como parámetro una referencia a la capa [dao];
  • línea 16: se utiliza el método getData de la capa [dao] para recuperar los datos que permiten calcular el impuesto;
  • el resto del código es similar al analizado en el ejemplo del párrafo 4.3.

8.3. La capa [console]

El script que implementa la capa [console] (impuestos-03) es el siguiente:


# -*- coding=utf-8 -*-

# importación del módulo de clases Impuestos*
from impots import *

# ------------------------------------------------ main
# definición de las constantes
DATA="data.txt"
RESULTATS="resultats.txt"
IMPOTS="impots.txt"

# los datos necesarios para el cálculo del impuesto se han colocado en el archivo IMPOTS
# a razón de una línea por tabla en el formato
# val1:val2:val3,...

# instanciación de la capa [metier]
try:
    metier=ImpotsMetier(ImpotsFile(IMPOTS))
except (IOError, ImpotsError) as infos:
    print ("Une erreur s'est produite : {0}".format(infos))
    sys.exit()

# lectura de datos
try:
    data=open(DATA,"r")
except:
    print "Impossible d'ouvrir en lecture le fichier des donnees [DATA]"
    sys.exit()

# apertura del archivo de resultados
try:
    resultats=open(RESULTATS,"w")
except:  
    print "Impossible de creer le fichier des résultats [RESULTATS]"
    sys.exit()

# utilidades
u=Utilitaires()

# se procesa la línea actual del archivo de datos
ligne=data.readline()
while(ligne != ''):
    # se elimina el posible carácter de fin de línea
    ligne=u.cutNewLineChar(ligne)
    # se recuperan los tres campos «casado:hijos:salario» que conforman la línea
    (marie,enfants,salaire)=ligne.split(",")
    enfants=int(enfants)
    salaire=int(salaire)
    # se calcula el impuesto
    impot=metier.calculer(marie,enfants,salaire)
    # se registra el resultado
    resultats.write("{0}:{1}:{2}:{3}\n".format(marie,enfants,salaire,impot))
    # se lee una nueva línea
    ligne=data.readline()
# se cierran los archivos
data.close()
resultats.close()

Notas:

  • línea 4: se importan todos los objetos del archivo impots.py, que contiene las definiciones de las clases. Una vez hecho esto, se pueden utilizar estos objetos como si estuvieran en el mismo archivo que el script;
  • líneas 17-21: se instancian tanto la capa [dao] como la capa [metier] con manejo de posibles excepciones;
  • línea 18: se instancia la capa [dao] y luego la capa [metier]. Se almacena la referencia en la capa [metier];
  • línea 19: se manejan las dos excepciones que pueden ocurrir;
  • el resto del código es similar al analizado en el ejemplo del párrafo 4.3.

8.4. Résultats

Los ya obtenidos en las versiones con tablas y archivos.

El archivo de datos impots.txt:

12620:13190:15640:24740:31810:39970:48360:55790:92970:127860:151250:172040:195000:0
0:0.05:0.1:0.15:0.2:0.25:0.3:0.35:0.4:0.45:0.5:0.55:0.6:0.65
0:631:1290.5:2072.5:3309.5:4900:6898.5:9316.5:12106:16754.5:23147.5:30710:39312:49062

El archivo de datos data.txt:

oui,2,200000
non,2,200000
oui,3,200000
non,3,200000
oui,5,50000
non,0,3000000

Los archivos resultats.txt con los resultados obtenidos:

oui:2:200000:22504.0
non:2:200000:33388.0
oui:3:200000:16400.0
non:3:200000:22504.0
oui:5:50000:0.0
non:0:3000000:1354938.0