Reputation: 11
I am looking for an elegant solution for the following (generic) problem. I want to implement several protocols to access files. Let's say ftp and tftp. More may be added in the future. Right now i'm doing:
get_file(filename):
# some generic code for opening the file etc.
if mode == 'ftp':
get_file_ftp(filename)
else:
get_file_tftp(filename)
# some generic code for closing the file etc.
get_file_ftp(filename):
# ftp-specific code
get_file_tftp(filename):
# tftp-specific code
Same for put_file()
, ls()
and a dozen more funtions. As code grows, this is starting to get ugly. So my question is: Is there a more clever way to get the job done? Is thera a design pattern that makes sense for this use case? If so, how would i accomplish this in Python?
Upvotes: 1
Views: 42
Reputation: 24133
Two patterns which you can refactor towards are Strategy and Decorator.
The Decorator Pattern is often implemented in Python using context managers. There is a library to make them easier to implement, contextlib
:
from contextlib import contextmanager
@contextmanager
def get_file(filename):
# some generic code for opening the file etc.
yield
# some generic code for closing the file etc.
The yield
statement enables you to inject whatever you want while you're in the with
block:
with get_file(filename) as resource:
get_file_ftp(resource)
or:
with get_file(filename) as resource:
get_file_tftp(resource)
The with
block will ensure the statements after the yield
are executed, even in the presence of exceptions.
Another way of implementing decorators is with Python's decorator syntax. I suggested a context manager because I thought you wanted before and after statements whatever happened in between. Python's decorator syntax would make you have to implement the exception handling yourself. The decorator modifies the function
def get_file(function):
def generic_code(filename):
... # some generic code for opening the file etc.
function(filename)
... # some generic code for closing the file etc.
return generic_code
Usage:
@get_file
def get_file_ftp(filename):
... # ftp-specific code
Alternatively, with the Strategy Pattern, you'd pass the strategy for handling different file sources into the get_file
function:
def get_file(filename, file_strategy):
# some generic code for opening the file etc.
file_strategy(filename)
# some generic code for closing the file etc.
Then use it like:
get_file(filename, get_file_ftp)
or:
get_file(filename, get_file_tftp)
Upvotes: 1