design-patterns - software - strategy pattern




Incapsulamento di un algoritmo in una classe (4)

Mi chiedo come (un) comune sia incapsulare un algoritmo in una classe? Più concretamente, invece di avere un numero di funzioni separate che inoltrano parametri comuni tra loro:

void f(int common1, int param1, int *out1);
void g(int common1, int common2, int param1, int *out2)
{
  f(common1, param1, ..);
}

incapsulare parametri comuni in una classe e svolgere tutto il lavoro nel costruttore:

struct Algo
{
  int common1;
  int common2;

  Algo(int common1, int common2, int param)
  { // do most of the work }

  void f(int param1, int *out1);
  void g(int param1, int *out2);
};

Sembra molto pratico non dover inoltrare parametri comuni e risultati intermedi attraverso gli argomenti delle funzioni. Ma non ho visto questo "pattern" ampiamente utilizzato. Quali sono i possibili aspetti negativi?


C'è un modello di progettazione che affronta il problema; si chiama "Strategy Design Pattern" - qui puoi trovare alcune buone informazioni.

La cosa bella di "Strategia" è che ti permette di definire una famiglia di algoritmi e quindi usarli in modo intercambiabile senza dover modificare i client che usano gli algoritmi.


Forse un approccio migliore (a meno che mi manchi qualcosa) è quello di ecapsualizzare l'algoritmo in una classe e farlo eseguire attraverso una chiamata al metodo. È possibile passare tutti i parametri a proprietà pubbliche, tramite un costruttore o creare una struttura che esegue l'ecapsulazione di tutti i parametri che vengono passati a una classe che contiene l'algoritmo. Ma in genere non è una buona idea eseguire le cose nel costruttore in questo modo. Innanzitutto perché non è intuitivo.

public class MyFooConfigurator
{
   public MyFooConfigurator(string Param1, int, Param2) //Etc...
   {
      //Set all the internal properties here
      //Another option would also be to expose public properties that the user could
      //set from outside, or you could create a struct that ecapsulates all the
      //parameters.
      _Param1 = Param1; //etc...
    }

    Public ConfigureFoo()
    {
       If(!FooIsConfigured)
          return;
       Else
          //Process algorithm here.
    }
}

La tua domanda potrebbe essere formulata in termini più generali come "come utilizziamo la progettazione orientata agli oggetti quando l'idea principale del software è solo l'esecuzione di un algoritmo?"

In tal caso, penso che un design come te sia un buon primo passo, ma queste cose sono spesso dipendenti dal problema.

Penso che un buon design generale sia come quello che hai lì. . .

class InputData {};
class OutputData {};

class TheAlgorithm 
{
private:
     //functions and common data

public:
   TheAlgorithm(InputData);      
   //other functions
   Run();
   ReturnOutputData();
};

Quindi, lascia che interagisca con il main () o la tua GUI come vuoi.


Non è affatto una brutta strategia. Infatti, se si ha la capacità nella propria lingua (che si fa in C ++) di definire qualche tipo di superclasse astratta che definisce un'interfaccia opaca alla funzionalità sottostante, è possibile scambiare diversi algoritmi dentro e fuori al runtime (si pensi agli algoritmi di ordinamento, per esempio). Se la tua lingua scelta ha una riflessione, puoi persino avere un codice che è infinitamente estensibile, consentendo di caricare e utilizzare algoritmi che potrebbero non essere nemmeno esistiti quando il consumatore di detti algoritmi è stato scritto. Ciò consente anche di associare liberamente le altre classi funzionali e le classi algoritmiche, che è utile per il refactoring e per mantenere intatta la tua sanità mentale quando si lavora su progetti di grandi dimensioni.

Ovviamente, ogni volta che inizi a costruire una struttura di classi complessa, ci sarà un'architettura extra - e quindi un codice - che dovrà essere costruita e mantenuta. Tuttavia, a mio parere, i benefici a lungo termine superano questo piccolo inconveniente.

Un ultimo suggerimento: non fare il tuo lavoro nel costruttore. I costruttori dovrebbero essere utilizzati solo per inizializzare una struttura interna delle classi a valori predefiniti ragionevoli. Sì, questo può includere chiamare altri metodi per completare l'inizializzazione, ma l'inizializzazione non è operativa. I due dovrebbero essere separati, anche se richiede una chiamata in più nel codice per eseguire il particolare algoritmo che stavi cercando.





class-design