1

I have the following models.

First, there is a Vector which has a circular DNA sequence.
Second, there is a LinearizedVector which could one of below classes.

LinearizedVectorBase:  
  LinearizedVectorByOneCut   
     ConcreteLinearizedVectorByOnePosition  
     ConcreteLinearizedVectorByOneEnzyme  
     ConcreteLinearizedVectorByOneEnzymeOrPosition  
  ConcreteLinearizedVectorByTwoCuts is composed of two 
    `ConcreteLinearizedVectorByOneEnzymeOrPosition`     

The base class LinearizedVectorBase has the following abstract methods:

linearize_vector()
get_feature_instances()

The current design

enter image description here

The problem with the current design is that ConcreteLinearizedVectorByOneEnzymeOrPosition has two parents with different implementations.

class ConcreteLinearizedVectorByOneEnzymeOrPosition
   def get_feature_instances(self):
       if self.restriction_enzyme is not None:
            # it should be `ConcreteLinearizedVectorByOneEnzyme().get_feature_instances()`
            return ?
       elif self.cut_position is not None:
            # it should be `ConcreteLinearizedVectorByOnePosition().get_feature_instances()`
            return ?         

I want to have two types of enzyme and position within one model so ConcreteLinearizedVectorByTwoCuts could have a composition relation of two objects of ConcreteLinearizedVectorByOneEnzymeOrPosition. Otherwise, it will grow exponentially in the numbers of models, ConcreteLinearizedVectorByTwoEnzymes,ConcreteLinearizedVectorByTwoPositions,ConcreteLinearizedVectorByEnzymeAndPosition,ConcreteLinearizedVectorByPositionAndEnzyme, which is clearly wrong.

I'm trying to minimize the number of concrete classes that exist as a result of the possible permutation of Position and Enzymes. When I used a concrete EnzymeOrPosition class and used it in the LinearizedVectorByTwoCuts by a composition relation, I faced the issue of having two parents with a different implementation.

The flow of the program:

linearized_vector = linearize_vector(vector, **kwargs)
feature_instances = linearized_vector.get_feature_instances()

What is a better way of designing this use case?

| improve this question | | | | |
  • 2
    What is the problem you are trying to solve? – BobDalgleish Mar 13 at 13:51
  • 1
    I see three things at work here: "Linearized Vectors," "Positions," and "Enzymes." I'm put-off by the fact that "One" vs. "Two" is embodied as distinct classes. But I don't know the problem-space at all, having never been a biologist. It feels to me, though, that there's one base class and several attributes having a 1:1 or at least 1:2 (maybe more than 2?) relationship. Dunno... echoing what Bob just said... "what is the problem you are trying to solve?" – Mike Robinson Mar 13 at 14:41
  • Thanks, I edited the question to include what I'm trying to solve. – Fadi Bakoura Mar 13 at 15:20
  • 1
    You are not choosing a design patterns, but you write working code first and then refactor it to the pattern you recognised in your already written code. – Fabio Mar 17 at 4:48
  • @Fabio While I agree with your sentiment not to look for design patterns prematurely, there are problems where a particular design pattern's utility is obvious quite early on. I think FadiBakoura had done enough coding/modeling to determine that his approach was untenable, and he had the wisdom and humility to identify a situation where a pattern might improve his design and ask for help. – Philip Wrage Mar 17 at 19:44
1

You may want to linearize your plasmid with one or more restriction enzymes, positional cuts, some other mechanisms (for example, based on a Sequence that is independent of restriction enzyme), or any combination of these.

However, you don't want to have to create concrete classes for each one of these possible combinations - you'll end up with a combinatorial explosion and unmaintainable code.

I think the best way to handle your problem is to use the Decorator Pattern.

First, define the Component (LinearizedVector) and ConcreteComponent (ConcreteLinearizedVector).

class LinearizedVector():
    """
    Define the interface, or contract, of the LinearizedVector
    """
    def linearize_vector(self):
        pass

    def get_feature_instances(self):
        pass


class ConcreteLinearizedVector(LinearizedVector):
    """
    Basic concrete implementation of LinearizedVector that holds circular vector
    """
    _vector = None

    def __init__(self, vector):
        self._vector = vector

    def linearize_vector(self):
        return "ConcreteLinearizedVector.linearize_vector(" + self._vector + ")"

    def get_feature_instances(self):
        return "ConcreteLinearizedVector.get_feature_instances(" + self._vector + ")"

Next, define your Decorator (LinearizedVectorDecorator). The Decorator will not only inherit from the Component class (LinearizedVector), it will also be composed of a Component class ('has-a' relationship), allowing the Decorator to 'wrap' an instance of the Component, or one of its children.

This permits the Decorator (and its ConcreteDecorator children) to be substituted anywhere in your client code where a Component (LinearizedVector) is expected.

The Decorator simply delegates calls to the methods it inherits from Component to the same methods on the Component that it wraps.

class LinearizedVectorDecorator(LinearizedVector):
    """
    Decorator wraps and inherits from LinearizedVector.
    This way it can be used anywhere a LinearizedVector can be used,
    and it simply delegates operations to the component it wraps.
    This will be the base decorator that concrete decorator implementations inherit from.
    """
    _linearized_vector = None
    _cut_positions = []

    def __init__(self, linearized_vector):
        self._linearized_vector = linearized_vector

    def linearize_vector(self):
        """ 
        delegate to the method on wrapped LinearizedVector
        """
        return self._linearized_vector.linearize_vector()

    def get_feature_instances(self):
        """ 
        delegate to the method on wrapped LinearizedVector
        """
        return self._linearized_vector.get_feature_instances()

    def get_cut_positions(self):
        return self._cut_positions

    def set_cut_positions(self, cut_positions):
        self._cut_positions = cut_positions

Finally, define ConcreteDecorator classes (RestrictionEnzymeLinearizedVectorDecorator, PositionallyLinearizedVectorDecorator) that inherit from the Decorator (LinearizedVectorDecorator).

These ConcreteDecorator classes can now add state and/or behavior to the Component. The ConcreteDecorator classes still delegate to the 'wrapped' Component instance, but by wrapping that Component they can now inject new behavior before and/or after the delegated method calls.

class RestrictionEnzymeLinearizedVectorDecorator(LinearizedVectorDecorator):
    """
    Delegate to the wrapped LinearizedVector and alter method results
    with restriction enzyme cut specific behaviors.
    """

    _restriction_enzyme = None

    def __init__(self, linearized_vector, restriction_enzyme):
        super().__init__( linearized_vector )
        self._restriction_enzyme = restriction_enzyme

    def linearize_vector(self):
        return "RestrictionEnzymeLinearizedVectorDecorator[ enzyme: " + self._restriction_enzyme + ", " + super().linearize_vector() + "]"

    def get_feature_instances(self):
        return "RestrictionEnzymeLinearizedVectorDecorator[ enzyme: " + self._restriction_enzyme + ", " + super().get_feature_instances() + "]"


class PositionallyLinearizedVectorDecorator(LinearizedVectorDecorator):
    """
    Delegate to the wrapped LinearizedVector and alter method results
    with positionally cut specific behaviors.
    """

    _cut_position = None

    def __init__(self, linearized_vector, cut_position):
        super().__init__( linearized_vector )
        self._cut_position = cut_position
        self.set_cut_positions( self.get_cut_positions().append( cut_position ) )

    def linearize_vector(self):
        return "PositionallyLinearizedVectorDecorator[ pos: " + str(self._cut_position) + ", " + super().linearize_vector() + "]"

    def get_feature_instances(self):
        return "PositionallyLinearizedVectorDecorator[ pos: " + str(self._cut_position) + ", " + super().get_feature_instances() + "]"

Now, to use your new code, simply instantiate your ConcreteComponent and pass it to the constructor of one of your ConcreteDecorators. You can do this in separate steps, or all at one time.

You can also nest your ConcreteDecorators multiple layers deep, but if you write them correctly, they will adhere to the base contract established by Component, and the added behavior will be transparent.

vector = RestrictionEnzymeLinearizedVectorDecorator( RestrictionEnzymeLinearizedVectorDecorator( ConcreteLinearizedVector( "ATGCAGTTACCGTGAGAATTCTTAG" ), "BamHI" ), "EcoRI" )

vector.linearize_vector()

anotherVector = RestrictionEnzymeLinearizedVectorDecorator( PositionallyLinearizedVectorDecorator( ConcreteLinearizedVector( "ATGCAGTTACCGTGAGAATTCTTAG" ), 3 ), "EcoRI" )

anotherVector.linearize_vector()

Example Output:

'RestrictionEnzymeLinearizedVectorDecorator[ enzyme: EcoRI, RestrictionEnzymeLinearizedVectorDecorator[ enzyme: BamHI, ConcreteLinearizedVector.linearize_vector(ATGCAGTTACCGTGAGAATTCTTAG)]]'

'RestrictionEnzymeLinearizedVectorDecorator[ enzyme: EcoRI, PositionallyLinearizedVectorDecorator[ pos: 3, ConcreteLinearizedVector.linearize_vector(ATGCAGTTACCGTGAGAATTCTTAG)]]'
| improve this answer | | | | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.