Search code examples
pythoninheritanceabstract-classfactory-patterngeneralization

Appropriate place for factory class specific generic functions (abstract class?)


I haven't quite found an answer to this specific question on Stack Overflow, so I'm posting it here.

I have a factory class which generates a Database handling object from an abstract class, depending on which database you need (see code).

My question is this; I have some generic methods which are ONLY applicable to the database handler...so I would think it NOT appropriate to put them into their own module...but I'm not sure where the appropriate place to put them is.

Putting them in the abstract class certainly works, but I don't know if thats the accepted place for them.

abstract class

class DBHandlerAbstract(object): # ABSTRACT CLASS ---

    __metaclass__ = abc.ABCMeta

    # I HAVE TO BE OVERRIDDEN
    @abc.abstractmethod
    def open(self):
        raise NotImplementedError

    # I HAVE TO BE OVERRIDDEN
    @abc.abstractmethod
    def close(self):
        raise NotImplementedError

    # SHOULD THIS GF GO HERE OR ELSEWHERE???
    def _check_host(self):
        print 'this will be the same for all dbhandler objects'

factory class

class DBHandler(object): # FACTORY CLASS ---
    """
    This is a factory class that will call and return a subclass.

    It is NOT an abstract class.

    The objects classes that are instantiated by this factory will be 
    subclasses of DBHandler  
    """
    @staticmethod
    def handler(service, *args, **kwargs):
        # Microsoft SQL (mssql)
        if      re.match("^(\s*)ms(\s*)sql.*$", str(service.lower())):
            return DBHandler_MSSQL(*args, **kwargs)

        # MySQL
        elif    re.match("^(\s*)my(\s*)sql.*$", str(service.lower())):
            return DBHandler_MYSQL(*args, **kwargs)

        else:
            log.error(MSG.DBHandlerNotProvided())
            raise TypeError('DBHandler service not provided.')

functional class

class DBHandler_MSSQL(DBHandlerAbstract): # FUNCTIONAL CLASS ---
    def __init__(self, *args, **kwargs):

        self.args       = args
        self.kwargs     = kwargs

        self._check_host()

        ...stuff and things...

gethandler.py

class test(object):

    def __init__(self):
        app_name   = 'dbhandler_test'
        logfile    = 'system'
        log_level  = 10
        screendump = True

        DBO = DBHandler.handler('mssql')

        ...stuff and things...

Solution

  • Let's approach that design issue by excluding alternatives:

    • if you would not implement the common protected function in the abstract base class you would have to repeat its implementation in the concrete descendants, which would violate DRY principle
    • if you would have the products of the covariant handler() factory aggregate the _check_host implementation mixin-style, the concrete DBHandler_XXXX not fulfill their very own ctors requirements, thus being implicitly abstract themselves. So would have valid conrete product instances, but not valid product classes, which would not just be bad to maintain also water down the factory pattern.

    It's obvious that your design is superior to those contortionisms.

    What you could however consider

    • if that is feasible for all DBHandler_XXXX descendants (!) -

    is calling _check_host in the abstract base classes' ctor and call that ctor explicitly from the DBHandler_XXXX at the appropriate point.