2015-12-18 1 views
1

J'essaie actuellement de comprendre les principes SOLID en lisant le livre appelé Practical Object Oriented Design in Ruby. Le premier principe est la responsabilité unique, la façon dont je comprends ce concept est qu'une classe/méthode ne devrait avoir qu'une responsabilité ou une raison de changer.J'ai besoin d'aide refactoring code pour se conformer au principe de responsabilité unique

Dans le code ci-dessous j'ai une classe Calculer qui est responsable de (4) quatre opérations différentes, add, subtract, multiply et divide, qui me ne sont pas conformes à la théorie de responsabilité unique.

Quelqu'un peut-il être si gentil et refactoriser la classe suivante d'une manière qui adhère à la Single Responsibility?

Je sais que cela pourrait être un sujet très opiniâtre, mais j'ai vraiment besoin de mieux comprendre ce concept.

FYI - Pour simplifier, j'utilise uniquement des Ints, ce que je sais que ce n'est pas idéal pour les divisions et les soustractions.

class Calculate{ 

    let num1:Int 
    let num2:Int 

    init(firstNum:Int, secondNum:Int){ 
     num1 = firstNum 
     num2 = secondNum 
    } 

    func add()->Int{ 

     let total = num1 + num2 

     return total 
    } 

    func subtract()->Int{ 

     let total = num1 - num2 

     return total 
    } 

    func multiply()->Int{ 

     let total = num1 + num2 

     return total 
    } 

    func divide()->Int{ 

     let total = num1/num2 

     return total 
    } 

} 


let operation = Calculate(firstNum:5 , secondNum:5) 
print(operation.divide()) 
+4

Ceci est certainement conforme à SRP. 'Calculer 'est composé d'une famille d'algorithmes connexes utilisés pour effectuer sa fonction de calcul.Je ne refactoriserais pas ça. –

+0

@Mister Epic - Oh, donc la classe peut avoir autant d'actions (méthodes) que nécessaire tant que ces actions sont liées à ce que la classe fait, dans ce cas puisque l'addition, la soustraction etc., sont considérées comme faisant partie d'un calcul , c'est bon d'être dans la classe 'Calculate', n'est-ce pas? La raison pour laquelle j'étais confus est que les quatre méthodes retournent une valeur et sont exposées au monde extérieur, mais en théorie elles fonctionnent avec les mêmes données (arguments). –

Répondre

1

La responsabilité unique signifie que la classe ne doit avoir qu'une seule fonctionnalité logique. Par conséquent, si vous avez une calculatrice, elle est parfaitement ok, elle peut calculer. Le "single" ne signifie pas "single method". Le calculateur peut additionner, diviser, etc. Alors oui, c'est exactement ce que la méthode de classe Calculator devrait faire.

Si vous voulez par exemple connecter deux classes Calculator, ajouter la méthode "connectToAnotherCalculator" est contre SRP. L'approche correcte consiste à créer une classe Connector, qui doit gérer la communication entre les calculatrices.

1

Je voudrais suggérer un meilleur design. Imaginez que vous deviez ajouter beaucoup d'autres opérations: pourcentage, racine carrée ou même sinus. Votre calculatrice se développera à des milliers de lignes. Qu'en est-il des tests? Supposons, nous avons déjà testé la calculatrice. Maintenant, nous devons le tester à nouveau après chaque opération. Ainsi, l'ajout d'une nouvelle opération nous coûtera de changer de calculatrice, de changer tous les endroits où elle est utilisée et de nombreux tests qui la couvrent.

Mais nous pouvons faire calculatrice avec une seule responsabilité - calculer (opération): 1) Maintenant, nous pouvons utiliser partout calculatrice-> calculer (opération), couvrir avec des tests et les faire passer. 2) Pour chaque nouvelle opération, nous ajoutons simplement une nouvelle classe d'opération et des tests pour celle-ci. Nous pouvons facilement changer n'importe quelle opération sans même toucher la calculatrice et les objets qui l'utilisent.

+0

Cela semble intéressant, quelque chose comme l'utilisation d'un protocole (interface)? J'ai entendu l'expression «Programmer une interface à une classe», c'est ce que vous suggérez? Je vais essayer d'améliorer mon code original et je vais poster ici. –

+0

Oui, les interfaces ont plus d'avantages par rapport aux classes concrètes. Vous pouvez utiliser des interfaces, puis modifier la mise en œuvre concrète en un seul endroit. Si vous couvrez votre code avec des tests unitaires, il vous permet également d'injecter des mocks en tant qu'implémentation de vos interfaces. –

0

@Denis Efimov Voici le code que j'ai trouvé après votre suggestion. Ce video from Laracasts m'a beaucoup aidé, c'est en PHP mais il était facile de le traduire en Swift.

Je montre seulement deux opérations (Addition et Multiplication) mais je pourrais facilement ajouter autant que je voulais. S'il vous plaît noter que pour la simplicité, je n'utilise que Int s.

protocol Operation{ 

    func equation()->Int 

} 

class Calculator{ 

    func calculate(operation:Operation)->Int{ 

     return operation.equation() 

    } 
} 

class Addition:Operation{ 

    var addendOne:Int 
    var addendTwo:Int 

    init(addendOne:Int, addendTwo:Int){ 

     self.addendOne = addendOne 
     self.addendTwo = addendTwo 
    } 

    func equation()->Int{ 
     return self.addendOne + self.addendTwo 
    } 

} 

class Multiplication:Operation{ 

    var multiplicand:Int 
    var multiplier:Int 

    init(multiplicand:Int, multiplier:Int){ 

     self.multiplicand = multiplicand 
     self.multiplier = multiplier 
    } 

    func equation()->Int{ 
     return self.multiplicand * self.multiplier 
    } 

} 


let addition = Addition(addendOne:5, addendTwo:5) 
let multiplication = Multiplication(multiplicand:5, multiplier:5) 

let calculator = Calculator() 

print(calculator.calculate(addition)) 
print(calculator.calculate(multiplication)) 

Que pensez-vous de ce code, est-ce que cela ressemble à une meilleure approche? Est-ce que cela respecte davantage le principe SOLID (SO)?

Merci

+1

Oui cela fonctionnerait mais vous êtes sur la mauvaise voie. Le design de votre question est conforme à SRP. Il ne sert à rien d'introduire une nouvelle publication pour chaque méthode que vous voulez ajouter à une classe. –

+0

Je vois votre point de vue, mon code adhère maintenant plus au principe "open close" ** S'O'LID **, non? Tout commence à avoir un sens. –

+1

Votre deuxième approche adhère à l'OCP ainsi que la première approche. Eh bien, vous pourriez le voir de cette façon, mais je pense que ce n'est pas le meilleur exemple pour une implémentation OCP. L'ajout de méthodes supplémentaires ne viole pas l'OCP. –