8. Application Exercise – [Tax Calculation] with a layered architecture
![]() |
Here we revisit the exercise described in Section 4.1. We start with the text-file version described in Section 4.3. To handle this example with objects, we will use a three-tier architecture:
![]() |
- the [DAO] (Data Access Object) layer handles data access. In the following, this data will first be found in a text file, then in a MySQL database;
- the [business] layer handles business logic, in this case tax calculation. It does not handle data. This data can come from two sources:
- the [DAO] layer for persistent data;
- the [console] layer for user-provided data.
- The [console] layer handles interactions with the user.
In what follows, the [DAO] and [business] layers will each be implemented using a class. The [console] layer will be implemented by the main program.
We will assume that all implementations of the [dao] layer provide the getData() method, which returns a tuple of three elements (limits, coeffR, coeffN)—the three data arrays required to calculate the tax. In other languages, this is called an interface. An interface defines methods (here, getData) that classes implementing this interface must have.
The [business] layer will be implemented by a class whose constructor takes a reference to the [dao] layer as a parameter, ensuring communication between the two layers.
8.1. The [DAO] layer
We will combine the various classes required for the application into a single file, impots.py. The objects in this file will then be imported into the scripts that require them.
We start with the case where the data is in a text file, as in the example in Section 4.3.
![]() |
The code for the [ ImpotsFile] class (impots.py) that implements the [DAO] layer is as follows:
# -*- coding=utf-8 -*-
import math, sys
# --------------------------------------------------------------------------
# custom exception class
class ImpotsError:
pass
# --------------------------------------------------------------------------
class Utilities:
"""utility functions class"""
def cutNewLineChar(self, line):
# Remove the end-of-line character from line if it exists
l = len(line)
while(line[l-1] == "\n" or line[l-1] == "\r"):
l -= 1
return(line[0:l])
# --------------------------------------------------------------------------
class TaxFile:
def __init__(self, IMPOTS):
# IMPOTS: the name of the file containing the data for the limits, coeffR, and coeffN tables
# open the file
data = open(IMPOTS, "r")
# a Utilities object
u = Utilities()
# Create the 3 tables—we assume that the 3 lines in IMPOTS are syntactically correct
# -- line 1
line = data.readline()
if line == '':
raise TaxError("The first line of the file {0} is missing".format(TAXES))
limits = u.cutNewLineChar(line).split(":")
for i in range(len(limits)):
limits[i] = int(limits[i])
# -- line 2
line = data.readline()
if line == '':
raise ImpotsError("The second line of the file {0} is missing".format(IMPOTS))
coeffR = u.cutNewLineChar(line).split(":")
for i in range(len(coeffR)):
coeffR[i] = float(coeffR[i])
# -- line 3
line = data.readline()
if line == '':
raise ImpotsError("The third line of the file {0} is missing".format(IMPOTS))
coeffN = u.cutNewLineChar(line).split(":")
for i in range(len(coeffN)):
coeffN[i] = float(coeffN[i])
# end
(self.limits, self.coeffR, self.coeffN) = (limits, coeffR, coeffN)
def getData(self):
return (self.limits, self.coeffR, self.coeffN)
Notes:
- lines 7-8: we define an ImpotsError class derived from the Exception class. This class adds nothing to the Exception class. We use it solely to have a custom exception class. This class could be expanded later;
- lines 11–18: a class of utility methods. Here, the cutNewLineChar method removes any line break characters from a string;
- line 25: opening the file may throw the IOError exception;
- lines 32, 39, 46: the custom ImpotsError exception is thrown;
- the code is similar to that studied in the example in Section 4.3.
8.2. The [business] layer
![]() |
The [ ImpotsMetier] class (impots.py) that implements the [business] layer is as follows:
class ImpotsMetier:
# constructor
# retrieve a pointer to the [DAO] layer
def __init__(self, dao):
self.dao = dao
# calculate the tax
# --------------------------------------------------------------------------
def calculate(self, spouse, children, salary):
# married: yes, no
# children: number of children
# salary: annual salary
# request the data needed for the calculation from the [dao] layer
(limits, coeffR, coeffN) = self.dao.getData()
# number of shares
marie = marie.lower()
if(marie=="yes"):
nbParts = float(children) / 2 + 2
else:
nbParts = float(children) / 2 + 1
# add 1/2 portion if there are at least 3 children
if children >= 3:
nbParts += 0.5
# taxable income
taxableIncome=0.72*salary
# family quotient
familyQuotient = taxableIncome / nbParts
# is placed at the end of the limits array to stop the following loop
limits[len(limits)-1]=quota
# tax calculation
i=0
while quotient > limits[i]:
i = i + 1
# Since quotient is stored at the end of the limits array, the previous loop
# cannot go beyond the limits array
# now we can calculate the tax
return math.floor(taxableIncome * (float)(coeffR[i]) - numShares * (float)(coeffN[i]))
Notes:
- lines 5-6: the class constructor receives a reference to the [dao] layer as a parameter;
- line 16: we use the getData method of the [dao] layer to retrieve the data needed to calculate the tax;
- the rest of the code is analogous to that studied in the example in Section 4.3.
8.3. The [console] layer
![]() |
The script implementing the [console] layer (impots-03) is as follows:
# -*- coding=utf-8 -*-
# Import the Impots* class module
from impots import *
# ------------------------------------------------ main
# Define constants
DATA="data.txt"
RESULTS="results.txt"
TAXES="taxes.txt"
# The data needed to calculate the tax has been placed in the TAXES file
# with one line per table in the form
# val1:val2:val3,...
# instantiation of the [business] layer
try:
business=TaxBusiness(TaxFile(IMPOTS))
except (IOError, ImpotsError) as info:
print("An error occurred: {0}".format(infos))
sys.exit()
# read data
try:
data = open(DATA, "r")
except:
print "Unable to open the data file [DATA] for reading"
sys.exit()
# Open the results file
try:
results = open(RESULTS, "w")
except:
print "Unable to create the results file [RESULTS]"
sys.exit()
# utilities
u = Utilities()
# process the current line of the data file
line = data.readline()
while(line != ''):
# remove any end-of-line characters
line = u.cutNewLineChar(line)
# We retrieve the three fields married:children:salary that make up the line
(spouse, children, salary) = row.split(",")
children = int(children)
salary = int(salary)
# calculate the tax
tax = job.calculate(spouse, children, salary)
# print the result
results.write("{0}:{1}:{2}:{3}\n".format(spouse, children, salary, tax))
# read a new line
line = data.readline()
# close the files
data.close()
results.close()
Notes:
- Line 4: We import all objects from the impots.py file, which contains the class definitions. Once this is done, we can use these objects as if they were in the same file as the script;
- Lines 17–21: We instantiate both the [dao] layer and the [business] layer with handling for potential exceptions;
- line 18: we instantiate the [dao] layer and then the [business] layer. We store the reference to the [business] layer;
- line 19: we handle the two exceptions that may occur;
- the rest of the code is similar to that studied in the example in Section 4.3.
8.4. Results
The same as those already obtained in the versions using arrays and files.
The data file 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
The data file data.txt:
The results.txt file containing the results:




