2017-10-06 9 views
3

Je fais un refactor sur certains codes.Stratégie Modèle avec chaque algorithme ayant une signature de méthode différente

Nous avons une liste d'investisseurs avec des montants affectés à chacun. Le total des montants devrait être égal à un autre total, mais parfois il y a quelques centimes de différence, donc nous utilisons différents algorithmes pour attribuer ces différences à chaque investisseur.

Le code actuel est quelque chose comme ceci:

public void Round(IList<Investors> investors, Enum algorithm, [here goes a list of many parameters]) { 

    // some checks and logic here - OMMITED FOR BREVITY 

    // pick method given algorithm Enum 

    if (algoritm == Enum.Algorithm1) { 
     SomeStaticClass.Algorithm1(investors, remainders, someParameter1, someParameter2, someParameter3, someParameter4) 
    } else if (algoritm == Enum.Algorithm2) { 
    SomeStaticClass.Algorithm2(investors, remainders, someParameter3) 
    } 
} 

jusqu'à présent, nous avons seulement deux algorithmes. Je dois mettre en œuvre le troisième. J'ai eu la possibilité de refactoriser les implémentations existantes ainsi que de faire du code générique pour faire cette fonction pour de futurs algorithmes, peut-être personnalisés pour chaque client.

Ma première pensée était "ok, c'est un modèle de stratégie". Mais le problème que je vois est que les deux algorithmes reçoivent une liste de paramètres différente (sauf pour les deux premiers). Et les futurs algorithmes peuvent également recevoir une liste de paramètres différente. La seule chose dans "commun" est la liste des investisseurs et les restes.

Comment puis-je concevoir ceci afin d'avoir une interface plus propre? Je pensais

  1. établissement d'une interface avec tous les paramètres possibles, et le partager entre toutes les implémentations.
  2. Utilisation d'un objet avec tous les paramètres possibles en tant que propriétés et utilisation de cet objet générique dans le cadre de l'interface. I aurait 3 paramètres: La liste des investisseurs, l'objet restants, et un objet "paramètres". Mais dans ce cas, j'ai un problème similaire. Instancier chaque objet et remplir les propriétés requises dépend de l'algorithme (sauf si je les ai tous définis). I devrait utiliser une usine (ou quelque chose) pour l'instancier, en utilisant tous les paramètres de l'interface, ai-je raison? Je déplacerais le problème de trop de paramètres à cette «usine» ou peu importe.
  3. Utilisation d'un objet dynamique au lieu d'un objet de type statique. Toujours présente les mêmes problèmes qu'avant, l'instanciation

J'ai aussi pensé à utiliser le modèle des visiteurs, mais comme je comprends, ce serait le cas si j'avais différents algorithmes pour différentes entités à utiliser, comme, une autre catégorie d'investisseurs. Donc, je ne pense pas que ce soit la bonne approche. Jusqu'ici, celui qui me convainc le plus est le second, même si je suis encore un peu réticent à ce sujet.

Des idées?

Merci

+0

Les paramètres sont-ils tous du même type? Si oui, ils pourraient être placés dans une liste. Un seul algorithme pourrait-il itérer la liste des paramètres et faire ce qui est nécessaire? – brumScouse

+0

Les implémentations actuelles ont des valeurs décimales, ints et enums. Il pourrait y avoir une chaîne si –

+1

Tous les params sont-ils définis indépendamment de l'algorithme? Qu'est-ce qui détermine l'algo? Pourquoi ne pas y mettre de l'algo? Semble un niveau supplémentaire inutile – brumScouse

Répondre

1

stratégie a différentes implémentations. C'est simple lorsque tous les Béton Stratégies exigent la même signature de type. Mais lorsque des implémentations concrètes commencent à demander des données différentes de Context, nous devons prendre un peu de recul en relaxant l'encapsulation ("breaking encapsulation" est connu inconvénient de la stratégie), soit on peut passer de Context aux stratégies en signature de méthode ou en fonction du constructeur sur combien est nécessaire. En utilisant des interfaces et en décomposant des arbres d'objets volumineux dans des confinements plus petits, nous pouvons restreindre l'accès à la plus grande partie de l'état Contexte.

Le code suivant illustre le paramètre de méthode de passage.

public class Context { 
     private String name; 
     private int id; 
     private double salary; 
     Strategy strategy; 
     void contextInterface(){ 
      strategy.algorithmInterface(this); 
     } 
     public String getName() { 
      return name; 
     } 
     public int getId() { 
      return id; 
     } 
     public double getSalary() { 
      return salary; 
     } 
    } 

    public interface Strategy { 
    // WE CAN NOT DECIDE COMMON SIGNATURE HERE 
    // AS ALL IMPLEMENTATIONS REQUIRE DIFF PARAMS 
    void algorithmInterface(Context context); 
    } 

    public class StrategyA implements Strategy{ 
     @Override 
     public void algorithmInterface(Context context) { 
      // OBSERVE HERE BREAKING OF ENCAPSULATION 
      // BY OPERATING ON SOMEBODY ELSE'S DATA 
      context.getName(); 
      context.getId(); 
     } 
    } 

    public class StrategyB implements Strategy{ 
     @Override 
     public void algorithmInterface(Context context) { 
      // OBSERVE HERE BREAKING OF ENCAPSULATION 
      // BY OPERATING ON SOMEBODY ELSE'S DATA 
      context.getSalary(); 
      context.getId(); 
     } 
    } 
+0

ok, vous suivez la deuxième option. Mais ce que vous dites est de toujours remplir toutes les propriétés de l'objet contexte, même si certaines d'entre elles ne sont pas nécessaires dans l'algorithme. Ai-je raison ? Sinon, je ne vois pas comment vous résolvez le problème de signature différent ici, parce que vous le déplacez en dehors de la stratégie et le déplacez dans l'objet d'instanciation de contexte –

+1

@ Gonzalo.- Votre contexte semble plus complexe, alors peut-être un L'objet de contexte passé à chaque appel à la méthode de stratégie n'a pas de sens. Une alternative consiste à faire instancier chaque stratégie concrète avec un constructeur spécifique ayant le contexte nécessaire. Une usine simple pourrait encapsuler la logique de création. Alors la méthode AlgorithmX serait polymorphique et possiblement sans arguments. Vous pouvez également utiliser cette réponse avec une fabrique simple pour créer l'instance de contexte. – Fuhrmanator

+0

@ Gonzalo.-c'est vrai. Après un second passage prudent à travers le code, j'ai trouvé peu de questions concernant les conditions nécessaires pour la stratégie. Ces algorithmes sont-ils réels? la méthode Round appartient-elle à la classe où elle devrait être? (peut-être pas c'est la raison pour laquelle il nécessite autant de paramètres.). Pouvons-nous identifier le bon contexte à partir du code adapté à toutes les stratégies (le contexte ne doit pas changer d'état par rapport à différentes stratégies). Peut-être qu'il faudra plus de refactoring afin d'isoler uniquement la partie applicable à la stratégie. mais pas sûr car ne pas connaître le code complet. –

0

D'accord, je pourrais aller dans la mauvaise direction ... mais il vous semble un peu bizarre que vous passer dans les arguments pour tous les algorithmes, et l'identifiant de l'algorithme à utiliser réellement. La fonction Round() ne devrait-elle pas idéalement obtenir ce dont elle a besoin pour fonctionner?

J'imagine que la fonction ronde invoque() pour ressembler à quelque chose comme:

if (something) 
    algToUse = Enum.Algorithm1; 
else 
    if (otherthing) 
     algToUse = Enum.Algorithm2; 
    else 
     algToUse = Enum.Algorithm3; 
Round(investors, remainder, algToUse, dayOfMonth, lunarCycle, numberOfGoblinsFound, etc); 

... si, au contraire, vous l'avez fait quelque chose comme ceci:

public abstract class RoundingAlgorithm 
{ 
    public abstract void PerformRounding(IList<Investors> investors, int remainders); 
} 
public class RoundingRandomly : RoundingAlgorithm 
{ 
    private int someNum; 
    private DateTime anotherParam; 
    public RoundingRandomly(int someNum, DateTime anotherParam) 
    { 
     this.someNum = someNum; 
     this.anotherParam = anotherParam; 
    } 
    public override void PerformRounding(IList<Investors> investors, int remainder) 
    { 
     // ... code ... 
    } 
} 
// ... and other subclasses of RoundingAlgorithm 

// ... later on: 
public void Round(IList<Investors> investors, RoundingAlgorithm roundingMethodToUse) 
{ 
    // ...your other code (checks, etc)... 

    roundingMethodToUse.Round(investors, remainders); 
}  

... et alors votre fonction précédente ressemble simplement à:

RoundingAlgorithm roundingMethod; 
if (something) 
    roundingMethod = new RoundingByStreetNum(1, "asdf", DateTime.Now); 
else 
    if (otherthing) 
     roundingMethod = new RoundingWithPrejudice(null); 
    else 
     roundingMethod = new RoundingDefault(1000); 
Round(investors, roundingMethod); 

.. En fait, au lieu de remplir cette valeur Enum, créez simplement un objet RoundingAlgorithm et passez-le à Round() à la place.

+0

malheureusement, je ne peux pas mettre à jour la signature de la méthode ronde. Je ne l'aime pas non plus, mais où j'ai posé ma question est le «point de départ» à partir duquel je peux refactoriser. Peut-être que le problème est que "Round" a plus de logique que d'arrondir (que j'ai omis, mais mentionné dans les commentaires), donc il semble plus étrange que cela devrait –

+0

Pourquoi ne pas mettre à jour la signature de la méthode Round()? Ne le mettez-vous pas déjà à jour pour ajouter un autre paramètre à la fin de vos entrées? – Kevin

+0

oui mais je ne peux pas mettre à jour où il est appelé dans un autre dll et non, je ne suis pas mise à jour jusqu'à présent, j'ai la liste complète des arguments. Je ne suis pas en train d'ajouter un nouveau paramètre à la fin de la signature ronde –