2017-09-24 6 views
0

Je n'arrive pas à trouver la bonne façon de modéliser ce problème. Ici je vous donne une version de mon code minimaliste:Design Pattern pour fusionner deux implémentations en une seule classe

# -*- coding: utf-8 -*- 
from abc import ABCMeta, abstractmethod 

class AreaCalculator(): 
    __metaclass__ = ABCMeta 

    def __init__(self): 
     pass 

    @abstractmethod 
    def getArea(self): 
     pass 

    def compute(self): 
     self.getArea() 


class PerimeterCalculator(): 
    __metaclass__ = ABCMeta 

    def __init__(self): 
     pass 

    @abstractmethod 
    def getPerimeter(self): 
     pass 

    def compute(self): 
     self.getPerimeter() 


class TriangleAreaCalculator(AreaCalculator): 

    def __init__(self): 
     AreaCalculator.__init__(self) 

    def getArea(self): 
     return area 

class TrianglePerimeterCalculator(PerimeterCalculator): 

    def __init__(self): 
     PerimeterCalculator.__init__(self) 

    def getPerimeter(self): 
     return perimeter 



a = TriangleAreaCalculator() 
b = TrianglePerimeterCalculator() 

Y at-il une façon élégante de fusionner les classes « TrianglePerimeterCalculator » et « TriangleAreaCalculator » en un seul, mais en gardant « PerimeterCalculator » et « AreaCalculator » séparés? Comme Kyle l'a suggéré dans les commentaires, je peux créer une nouvelle classe (appelons-la "Triangle") qui hérite de "PerimeterCalculator" et "AreaCalculator" en même temps, mais ce que je veux, c'est être capable de dire à une nouvelle instance de "Triangle" de se comporter comme "PerimeterCalculator" ou "AreaCalculator", mais pas les deux en même temps.

+0

Vous pouvez expérimenter avec la création d'une classe "Triangle" plus générique qui a une méthode de périmètre et la surface. – Kyle

+3

Vous pouvez hériter de plusieurs classes en Python. classe CustomClass (BaseClass1, BaseClass2): – Kyle

+0

Oui ...cela résout le problème, mais je voudrais qu'une nouvelle instance "Triangle" se comporte comme "PerimeterCalculator" ou "AreaCalculator" mais pas les deux en même temps – caspillaga

Répondre

1

Voici une autre réponse, après l'édition et la clarification de votre question. Il permet la création d'un uniqueTriangle instance qui peut se comporter comme un AreaCalculator ou PerimeterCalculator, selon les besoins. Ce modèle de programmation est appelé "délégation" et est utilisé lorsque la responsabilité de l'implémentation d'une opération particulière est transférée à un objet différent, dans ce cas une instance interne d'une autre classe. Une façon courante de faire cela en Python est de surcharger la méthode par défaut de la classe __getattr__().

Puisque vous n'avez jamais répondu au commentaire sous mon autre réponse à propos de ce qui contrôle exactement quel comportement est utilisé, j'ai ajouté une méthode set_behavior() pour permettre de le spécifier explicitement.

from abc import ABCMeta, abstractmethod 


class AreaCalculator: 
    __metaclass__ = ABCMeta 

    def __init__(self): 
     pass 

    @abstractmethod 
    def getArea(self): 
     pass 

    def compute(self): 
     return self.getArea() 


class PerimeterCalculator: 
    __metaclass__ = ABCMeta 

    def __init__(self): 
     pass 

    @abstractmethod 
    def getPerimeter(self): 
     pass 

    def compute(self): 
     return self.getPerimeter() 


class TriangleAreaCalculator(AreaCalculator): 

    def __init__(self): 
     super(TriangleAreaCalculator, self).__init__() 

    def getArea(self): 
     print('TriangleAreaCalculator.getArea() called') 
     area = 13 
     return area 



class TrianglePerimeterCalculator(PerimeterCalculator): 

    def __init__(self): 
     super(TrianglePerimeterCalculator, self).__init__() 

    def getPerimeter(self): 
     print('TrianglePerimeterCalculator.getPerimeter() called') 
     perimeter = 42 
     return perimeter 


class Triangle: 

    def __init__(self): 
     delegate_classes = TriangleAreaCalculator, TrianglePerimeterCalculator 

     # Map delegate classes to instances of themselves. 
     self._delegates = {delegate_class: delegate_class() 
          for delegate_class in delegate_classes} 

     self.set_behavior(TriangleAreaCalculator) # Set default delegate. 

    def __getattr__(self, attrname): 
     # Called only for attributes not defined by this class (or its bases). 
     # Retrieve attribute from current behavior delegate class instance. 
     return getattr(self._behavior, attrname) 

    def set_behavior(self, delegate_class): 
     try: 
      self._behavior = self._delegates[delegate_class] 
     except KeyError: 
      raise TypeError("{} isn't a valid {} behavior delegate class" 
           .format(delegate_class, self.__class__.__name__)) 


if __name__ == '__main__': 

    triangle = Triangle() 
    # Uses instance's default behavior. 
    print('triangle.compute() -> {}'.format(triangle.compute())) 

    triangle.set_behavior(TrianglePerimeterCalculator) # Change behavior. 
    print('triangle.compute() -> {}'.format(triangle.compute())) 

Sortie:

TriangleAreaCalculator.getArea() called 
triangle.compute() -> 13 
TrianglePerimeterCalculator.getPerimeter() called 
triangle.compute() -> 42 
2

Je pense que le "modèle de conception" que vous devriez utiliser est l'héritage multiple. Voici une version modifiée de votre code démontrant comment le faire (plus quelques autres modifications pour le rendre réellement exécutable et toutes les classes de style nouveau).

from abc import ABCMeta, abstractmethod 

class AreaCalculator(object): 
    __metaclass__ = ABCMeta 

    def __init__(self): 
     pass 

    @abstractmethod 
    def getArea(self): 
     pass 

    def compute(self): 
     self.getArea() 


class PerimeterCalculator(object): 
    __metaclass__ = ABCMeta 

    def __init__(self): 
     pass 

    @abstractmethod 
    def getPerimeter(self): 
     pass 

    def compute(self): 
     self.getPerimeter() 


class TriangleAreaCalculator(AreaCalculator): 

    def __init__(self): 
     super(TriangleAreaCalculator, self).__init__() 

    def getArea(self): 
     print('TriangleAreaCalculator.getArea() called on instance of {}'.format(
      self.__class__.__name__)) 
#  return area 
     return 13 

class TrianglePerimeterCalculator(PerimeterCalculator): 

    def __init__(self): 
     super(TrianglePerimeterCalculator, self).__init__() 

    def getPerimeter(self): 
     print('TrianglePerimeterCalculator.getPerimeter() called on instance of {}'.format(
      self.__class__.__name__)) 
#  return perimeter 
     return 42 


class MergedCalculator(TriangleAreaCalculator, TrianglePerimeterCalculator): 

    def __init__(self): 
     super(MergedCalculator, self).__init__() 

merged = MergedCalculator() 
print('merged.getArea() -> {}'.format(merged.getArea())) 
print('merged.getPerimeter() -> {}'.format(merged.getPerimeter())) 

Sortie:

TriangleAreaCalculator.getArea() called on instance of MergedCalculator 
merged.getArea() -> 13 
TrianglePerimeterCalculator.getPerimeter() called on instance of MergedCalculator 
merged.getPerimeter() -> 42 
+0

Merci martineau. Le problème avec votre réponse est que "MergedCalculator" se comporte maintenant comme AreaCalculator et PerimeterCalculator en même temps, mais je devais être capable d'instater des objets qui se comportent comme l'un d'entre eux, mais pas les deux en même temps. J'ai vraiment apprécié votre réponse et je vous ai donné +1 parce que cela m'a beaucoup aidé à trouver la solution. Merci! – caspillaga

+0

caspillaga: De rien. Qu'est-ce qui détermine quand votre classe fusionnée agit comme l'une ou l'autre des autres classes si ce n'est pas tout le temps? En d'autres termes, comment «sait-elle» quelle manière de se comporter? – martineau

0

j'ai tout compris moi-même, avec l'inspiration sur les commentas/réponses de Kyle et martineau.

Je peux créer une classe fusionnée "Triangle" comme suit:

class Triangle(): 

    def __init__(self): 
     pass 

    def getTriangleArea(self): 
     print 'Triangle area' 

    def getTrianglePerimeter(self): 
     print 'Triangle perimeter' 

Et puis modifiez TriangleAreaCalculator et TrianglePerimeterCalculator comme suit:

class TriangleAreaCalculator(AreaCalculator, Triangle): 

    def __init__(self): 
     TriangleCalculator.__init__(self) 
     AreaCalculator.__init__(self) 

    def getArea(self): 
     super(TriangleAreaCalculator, self).getTriangleArea() 

class TrianglePerimeterCalculator(PerimeterCalculator, Triangle): 

    def __init__(self): 
     TriangleCalculator.__init__(self) 
     PerimeterCalculator.__init__(self) 

    def getPerimeter(self): 
     super(TrianglePerimeterCalculator, self).getTrianglePerimeter() 

De cette façon, je peux créer un nouveau triangle comme instance qui se comporte comme "PerimeterCalculator" ou "AreaCalculator" (mais pas les deux à la fois):

a = TriangleAreaCalculator() 
b = TrianglePerimeterCalculator() 

a.compute() # correctly prints "Triangle area" 
b.compute() # correctly prints "Triangle perimeter" 
+2

Je ne pense pas que les calculatrices devraient hériter de la classe Triangle –

+1

Vrai ... Après une seconde réflexion je vois que ça fait le boulot, mais d'une manière très laide. Certainement pas la "bonne façon" – caspillaga