Urkel42
Urkel42

Reputation: 11

Design-pattern/solution for logically similar functions without code dupplication

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

Answers (1)

Open AI - Opting Out
Open AI - Opting Out

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

Related Questions