2017-01-27 1 views
0

J'ai une classe qui ressemble à quelque chose comme ce qui suit:python 3: comment créer deux classes différentes fonctionnant sur les mêmes données?

# Class violates the Single Responsibility Principle 
class Baz: 
    data = [42] 
    def do_foo_to_data(self): 
     # call a dozen functions that do complicated stuff to data 

    def do_bar_to_data(self): 
     # call other functions that do different stuff to data 

Je veux briser en deux classes distinctes, car elle viole la SRP. Les fonctions appelées do_foo_to_data() sont complètement distinctes de celles appelées par do_bar_to_data(). Pourtant, ils doivent fonctionner sur le même data.

Je suis venu avec un tas de solutions, mais ils sont tous moche. Existe-t-il un moyen de le faire proprement, de préférence en Python 3 (bien que 2.7 soit OK aussi)?

Le meilleur de mes "solutions" est ci-dessous:

# I find this hard to read and understand 
class Baz: 
    data = [42] 

    def create_foo(self): 
     return Baz.Foo() 

    def create_bar(self): 
     return Baz.Bar() 


    class Foo: 
     def do_foo_to_data(self): 
      # call foo functions 


    class Bar: 
     def do_bar_to_data(self): 
      # call bar functions 

Note: Il me est pas essentiel pour que le membre soit un membre de la classe data. Je m'attends seulement à créer une instance de Baz; mais je ne voulais pas poser deux questions dans un seul article et commencer une discussion sur les singletons.

+0

Sans les exigences réelles, il est trop difficile de commenter. Séparation des préoccupations et SRP ne signifie pas que votre classe ne peut pas avoir plusieurs méthodes. Dans votre "solution", Baz a toujours le même problème que dans l'original. Peut-être que penser au problème en tant que Model-View-Controller aiderait. Avoir une classe qui gère les données (Modèle) et d'autres classes qui effectuent les manipulations/opérations (Contrôleurs). Si nécessaire, vous pouvez lancer une classe pour représenter les résultats (vue). – RobertB

Répondre

4

Ceci n'est pas une solution élégante. Mieux vaut de passer une référence à l'objet sur lequel vous voulez qu'ils fonctionnent. Donc, quelque chose comme:

class Foo: 

    def __init__(self,data): self.data = data 

    def do_foo_to_data(self): 
     #... 
     self.data[0] = 14 
     pass 

class Bar: 

    def __init__(self,data): self.data = data 

    def do_bar_to_data(self): 
     #... 
     self.data.append(15) 
     pass

(j'ai ajouté des manipulations d'échantillons comme self.data[0] = 14 et self.data.append(15))

Et maintenant, vous construisez les données. Par exemple:

data = [42] 

Ensuite, vous construire un Foo et un Bar et passer une référence à data comme:

foo = Foo(data) 
bar = Bar(data) 

__init__ est ce que la plupart des langages de programmation appellent le constructeur et que vous avez vu dans le premier fragment, il nécessite un paramètre supplémentaire data (dans ce cas, il s'agit d'une référence à notre construit data).

et vous pouvez pour les appels d'instance:

foo.do_foo_to_data() 

qui établira data à [14] et

bar.do_bar_to_data() 

qui se traduira par data étant égale à [14,15].

l'esprit que vous ne pouvez pas dire self.data = ['a','new','list'] ou quelque chose d'équivalent dans do_foo_to_data ou do_bar_to_data parce cela changerait la référence à un nouvel objet.Au lieu de cela, vous pouvez par exemple .clear() la liste, et ajouter de nouveaux éléments à l'aimer:

def do_foo_to_data(self): #alternative version 
    #... 
    self.data.clear() 
    self.data.append('a') 
    self.data.append('new') 
    self.data.append('list') 

Enfin, pour répondre à votre remarque:

de préférence en Python 3 (bien que 2.7 est trop OK)?

La technique démontrée est presque universelle (ce qui signifie qu'il est disponible dans presque toutes les langues de programmation). Donc, cela fonctionnera à la fois et . Pourquoi avez-vous même besoin d'un cours pour cela?

+0

Oui, je vois cela comme un type MVC décomposer. Votre "Modèle" dans ce cas est un objet liste et le Foo et Bar sont des "Contrôleurs". – RobertB

+0

@RobertB: Eh bien, je pense que c'est la façon la plus élégante de le faire puisque vous pouvez également introduire d'autres "modèles" et contrôleurs alors que le code dans la question n'a qu'un "modèle" et est donc assez limité. Habituellement, ce n'est pas une bonne idée de faire beaucoup d'objets * singleton * puisque tout d'un coup vous pouvez changer d'avis et devoir en présenter d'autres. –

+1

Je suis d'accord. Plutôt que d'avoir des objets singleton, l'OP devrait considérer s'il a besoin de classes ou si ses manipulations ne sont que des fonctions. https://www.youtube.com/watch?v=o9pEzgHorH0&feature=youtu.be – RobertB

4

Tout ce que vous voulez, c'est deux fonctions séparées qui font un travail sur certaines données.

data = [42] 

def foo(data): 
    data.append('sample operation foo') 

def bar(data): 
    data.append('sample operation bar') 

Problème résolu.

1

Vous pouvez retirer les groupes distincts de fonctionnalité aux classes de mélange dans différents:

class Foo: 
    """Mixin class. 

    Requires self.data (must be provided by classes extending this class). 
    """ 
    def do_foo_to_data(self): 
     # call a dozen functions that do complicated stuff to data 

class Bar: 
    """Mixin class. 

    Requires self.data (must be provided by classes extending this class). 
    """ 
    def do_bar_to_data(self): 
     # call other functions that do different stuff to data 

class Baz(Foo, Baz): 
    data = [42] 

Cela repose sur le comportement de frappe de canard de Python. Vous devez appliquer uniquement les mixages Foo et Bar aux classes qui fournissent réellement self.data, comme le fait la classe Baz.

Cela peut convenir lorsque certaines classes sont par convention requises pour fournir certains attributs de toute façon, comme les classes d'affichage personnalisées dans Django. Cependant, lorsque de telles conventions ne sont pas déjà en place, vous pourriez ne pas vouloir en introduire de nouvelles. Il est trop facile de rater la documentation et d'avoir NameError à l'exécution. Donc, rendons la dépendance explicite, plutôt que de la documenter. Comment? Avec un mix-in pour les mix-ins!

class Data: 
    """Mixin class""" 
    data = [42] 

class Foo(Data): 
    """Mixin class""" 
    def do_foo_to_data(self): 
     # call a dozen functions that do complicated stuff to data 

class Bar(Data): 
    """Mixin class""" 
    def do_bar_to_data(self): 
     # call other functions that do different stuff to data 

class Baz(Foo, Baz): 
    pass 

Si cela est approprié pour votre utilisation, il est difficile de dire à ce niveau d'abstraction. Comme le montre RayLuo's answer, vous pourriez ne pas avoir besoin de cours du tout. Au lieu de cela, vous pouvez mettre les différents groupes de fonctions dans différents modules ou paquets, pour les organiser.