Skip to content

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

Retomamos aquí el ejercicio descrito en el apartado 4.1. Partimos de version con archivos de texto, descrito en el apartado 4.3. Para tratar este ejemplo con objetos, utilizaremos una arquitectura de tres capas:

  • la capa [dao] (Data Access Object) se encarga del acceso a los datos. A continuación, estos datos se encontrarán primero en un archivo de texto y, después, en una base de datos MySQL;
  • la capa [metier] se encarga de los aspectos específicos del negocio, en este caso el cálculo de impuestos. No se ocupa de los datos. Estos pueden tener dos orígenes:
    • 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] 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 el cálculo del 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]

Vamos a reunir las diferentes clases necesarias para la aplicación en un mismo archivo impots.py. A continuación, los objetos de este archivo se importarán a los scripts que los necesiten.

Comenzamos con el caso en el que los datos se encuentran en un archivo de texto, como en el ejemplo del apartado 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 carácter 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 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 añade nada a la clase Exception. Se utiliza únicamente para disponer de 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 carácter de fin de línea que pueda haber en 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 estudiado en el ejemplo del apartado 4.3.

8.2. La capa [metier]

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


class ImpotsMetier:

    # constructor
    # se recupera 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
        # una media participación más si hay al menos 3 hijos
        if enfants>=3:
            nbParts+=0.5
        # renta imponible
        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
        # dado que se ha colocado el coeficiente al final de la tabla de límites, el bucle anterior
        # no puede sobrepasar la tabla de límites
        # 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 el cálculo del impuesto;
  • el resto del código es análogo al estudiado en el ejemplo del apartado 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 3 campos «casado:hijos:salario» que forman 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 introduce 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 gestión de posibles excepciones;
  • línea 18: se instancian la capa [dao] y, a continuación, la capa [metier]. Se almacena la referencia en la capa [metier];
  • línea 19: se gestionan las dos excepciones que pueden producirse;
  • el resto del código es análogo al estudiado en el ejemplo del apartado 4.3.

8.4. Resultados

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 de 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