Strategy pattern in python
Here is an example of strategy pattern in python. The alogrithm that varies in this example is the conversion of csv file to xml, html and any other formats. The client is the CsvUtil class. As you can see we can change the conversion at run time and also we can apply all the alogrithm if we wish to which we can’t achieve when we go for the regular inheritance model. Please do note that an abstract class is equivalent to interface when there is no implementation.
Source code for all the classes related to strategy.
HTML_TEMPATE="""<html><body>%s</body></html> """ class CsvConverter: ''' Abstract class or interface for conversion of csv file''' def convert(self,csv): pass #abstract method class Csv2Html(CsvConverter): ''' Covert Csv to html table with alternativing row colors''' def convert(self,csv): ''' convert the csv file to html file ''' htmltable = '' csvfile = open(csv) dialect = Sniffer().sniff(csvfile.read(2048)) csvfile.seek(0) #rewind after sniffing csvreader = reader(csvfile,dialect) i = 0 for line in csvreader: #print line if i == 0: #header row htmltable += '<tr class="head">' elif i % 2 == 0: #even row htmltable += '<tr class="even">' else: #odd row htmltable += '<tr class="odd">' for col in line: htmltable += '<td>%s</td>'%col htmltable += '</tr>' i += 1 return HTML_TEMPLATE%htmltable def __str__(self): return 'Csv2Html' class Csv2Xml(CsvConverter): ''' Covert Csv to Xml ''' def __init__(self,linetag='row'): ''' Constructor ''' self.linetag = linetag def convert(self,csv): ''' convert the csv file to xml file ''' print 'hello' xml = '' csvfile = open(csv) csvreader = DictReader(csvfile) for row in csvreader: xml += '<%s>'%self.linetag for colname,colvalue in row.iteritems(): xml += '<%s>$%s</%s>'%(colname,colvalue,colname) xml += '</%s>'%self.linetag return xml def __str__(self): return 'Csv2Xml'
Here is the source code for the context. You can see the use of property in python here.
class CsvUtil(object): ''' A wrapper or utility class for dealing with csv files ''' def __init__(self,csv): ''' constructor ''' self.csv = csv self._numlines = None #this information is computed lazily self._numcols = None #this information is gathered lazily self._wellformed = None #this information is gathered lazily self._converter = Csv2Html() #default converter def _gatherstats(self): ''' gather basic stats on the csv ''' with open(self.csv) as csvfile: dialect = csv.Sniffer().sniff(csvfile.read(2048)) csvfile.seek(0) #rewind reader = csv.reader(csvfile,dialect) self._numlines = 0 self._wellformed = True self._numcols = 0 for line in reader: self._numlines += 1 if self._numcols != len(line): self._wellformed = False def convert(self): ''' convert the csv file to another format ''' return self._converter.convert(self.csv) def get_wellformed(self): ''' a csv is wellformed if all the lines have same number of columns ''' if not self._wellformed: self._gatherstats() return self._wellformed def get_numlines(self): ''' number of lines in the csv files ''' if not self._numlines: self._gatherstats() return self._numlines def get_numcols(self): ''' number of lines in the csv files ''' if not self._numcols: self._gatherstats() return self._numcols def get_converter(self): ''' getter for converter ''' return self._converter def set_converter(self,value): print 'set ',value self._converter = value #properties wellformed = property(get_wellformed) numlines = property(get_numlines) numcols = property(get_numcols) converter = property(get_converter,set_converter)
Finally the demonstration of using the context and strategy….
csvfile = "C:\sandbox\python\mycsv.txt"
util = CsvUtil(csvfile) # my utility class for handling csv
print util.numlines
print util.wellformed
print 'Default converter is',util.converter
print util.convert() #print using the default converter
#now changing my converter at run time :-)
util.converter = Csv2Xml()
print 'My new converter is',util.converter
print util.convert()