OODesign: struttura dati che chiama l'algoritmo su insert

3

Ho una struttura dati con una funzione aggiungi . Quando l'utente crea un'istanza di un nuovo oggetto struttura dati, può specificare un algoritmo che verrà eseguito ogni volta che viene chiamata la funzione di aggiunta e modifica il valore appena inserito in base ai valori precedenti nella struttura dati. L'algoritmo deve avere accesso a tutti i dati nella struttura dei dati.

Quindi la struttura dati deve memorizzare un oggetto algoritmo (poiché questo viene chiamato ogni volta che viene eseguita l'aggiunta). D'altra parte l'algoritmo ha bisogno di accedere alla struttura dei dati, e quindi ha bisogno di memorizzare l'oggetto della struttura dei dati.

Penso che le situazioni in cui la classe A abbia un'istanza di classe B e la classe B abbiano un'istanza di classe A sono solitamente indesiderabili.

Qualcuno ha un'idea di come risolvere questa ricorrenza qui?

    
posta user695652 09.07.2015 - 16:48
fonte

5 risposte

1

Un modo per affrontare questo problema senza estendere i livelli del tuo sistema è quello di implementare i modelli di comando e strategia nel tuo progetto. Incapsula il metodo di azione (nel tuo caso, "aggiungi") come Command e decora con Strategy che incapsula il tuo algoritmo.

All'interno del Execute del tuo AbstractCommand , esegui un metodo virtuale OnAfterExecute() , che indica dove vuoi che il tuo Algorithm sia elaborato. Per legare insieme i modelli, concettualizzare le responsabilità del Context e del Invoker come identiche.

Untested code. Typed by hand from memory.

public interface ICommand
{
    void OnBeforeExecute();
    void OnExecute();
    void OnAfterExecute();
    void Execute();
}
public abstract class Command : ICommand
{
    protected IStrategy Algorithm{ get;set; }
    public Command(IStrategy algorithm)
    {
      this.Algorithm = algorithm;
    }
    public virtual void OnBeforeExecute(){};
    public virtual void OnExecute(){};
    public virtual void OnAfterExecute(){};
    public virtual void Execute()
    {
       OnBeforeExecute();
       OnExecute();
       OnAfterExecute();    
    };
}

public class AddCommand : Command
{
    private IPersistenceService PersistenceService {get;set;}
    public AddCommand(IStrategy algorithm, IPersistenceService persistenceService) :base(algorithm)
    {
       this.PersistenceService = persistenceService;
    }
    public virtual override OnExecute()
    {
       this.PersistenceService.ExecuteAddStuffs();
    }
    public virtual override OnAfterExecute()
    {
       this.Algorithm.Process();
    }
}

public interface IStrategy
{
    void Process();
}

public class HighPassFilterStrategy : IStrategy
{
    private IPersistenceService PersistenceService { get;set; }
    public void Process()
    {
       //Execute your HighPassFilter logic.
    }
}

//DomainProcess is your InvokerContext
//As an alternative design consideration, you could move the virtual 'On' methods
//up into DomainProcess and decouple the strategy from the command.
public class DomainProcess
{
    public ICommand Command{ get; protected set;}
    public DomainProcess(IStrategy strategy, ICommand command)
    {
       command.Algorithm = strategy;
       this.Command = command;
    }

    public void Run()
    {
       Command.Execute();
    }
}

public class YourDataStructure
{
   public string Foo {get;set;}
   public string Bar {get;set;}

   //List of DomainProcess. Won't accept angle brackets.
   //List"left angle bracket"DomainProcess"right angle bracket"
    private List DomainProcesses { get;set; }

    public DataStructure(List workflows)
    {
       this.DomainProcesses = workflows;

       //alternative, but indicative of poor design. **Objects should not
       //construct their own dependencies.**
       //--------------------------------------------------------- 
       //I'm including this so that I don't have to design all the way up to
       //Composition Root for this illustration.
       //---------------------------------------------------------
       if(!this.DomainProcesses.Any(process => process.Name.Equals("Add"))
       {
          var algorithm = new HighPassFilterStrategy();
          var command = new AddCommand();
          var add = new DomainProcess(algorithm, command);
          this.DomainProcesses.Add(add);
       }
    }

    public void AddRuntimeOperation(string name, ICommand command, IStrategy algorithm)
    {
       DomainProcess process = new DomainProcess(algorithm, command);
       process.Name = name;

       DomainProcesses.Add(process);
    }
    public void Add()
    {
       var add = this.DomainProcesses.Where(operation => operation.Name.Equals("Add'")).SingleOrDefault();

       add.Run();
    }
}

La tua struttura che al momento ha un metodo Add potrebbe invece incapsulare un DomainProcess chiamato Aggiungi

  public void Add()
  {
    DomainProcess.Run();
  }

I metodi OnBeforeExecute , OnExecute e OnAfterExecute sono colloquialmente noti come aspetti.

If you are going to use an object hierarchy in your design, aspects should serve as your primary extension points of that hierarchy.

Creando l'implementazione predefinita di Execute come aggregazione di questi 3 aspetti, puoi facilmente specificare all'interno degli Algoritmi che vuoi che vengano elaborati prima o dopo l'esecuzione dell'operazione principale.

Informazioni più specifiche sui tuoi requisiti potrebbero cambiare la raccomandazione.

    
risposta data 09.07.2015 - 21:12
fonte
3

La struttura dei dati dovrebbe contenere un link all'algoritmo, poiché ha esattamente un algoritmo. Destra? (come ho capito i requisiti)

Ma lo stesso algoritmo, ad esempio, "normalizza alla media" potrebbe funzionare su molte diverse istanze della struttura dati. Quindi non dovrebbe contenere / memorizzare un'istanza della struttura dati. Invece, il suo metodo dovrebbe prendere la struttura dei dati come argomento aggiuntivo. per es.

calculate(newValue, existingDataStructure);

Quindi, le chiamate del metodo add () della tua infrastruttura

tweakedValue = this.algorithm.calculate(newValue, this);
this.add(tweakedValue);
    
risposta data 09.07.2015 - 19:05
fonte
1

Se hai una dipendenza ciclica come questa, questo suggerisce uno dei due problemi:

  • i due oggetti dovrebbero essere veramente un oggetto
  • i due oggetti dovrebbero essere in realtà tre oggetti (con le parti correlate tra loro estratti nella terza)

Quello che hai qui, è altamente insolito. L'aggiunta di un elemento a una struttura dati è una delle le responsabilità primarie di una struttura dati, esternalizzata al cliente, specialmente in un modo che richiede un accesso privilegiato agli interni privati della struttura dati, sembra in qualche modo sbagliato in qualche modo .

Questo è molto diverso da, ad esempio, un SortedSet che ha bisogno sia di un Ordering sia di un Equality algoritmo, ma nessuno di questi due algoritmi ha bisogno di sapere nulla su Set , ha solo bisogno di sapere come confrontare due elementi. Oppure un HashMap che ha bisogno di un algoritmo di Hash che possa eseguire l'hash di un singolo elemento. Questo è il caso tipico di una struttura dati parametrizzata da un algoritmo.

    
risposta data 09.07.2015 - 16:57
fonte
1

I have a data structure which has an add function. When the user instantiates a new data structure object, she can specify an algorithm which will be executed each time the add function is called and alters the newly inserted value based on the previous values in the data structure. The algorithm needs to have access to all the data in the data structure

Questa non è una buona idea e inoltre non ha molto senso .

Se ti prendo in modo corretto, vuoi creare un oggetto che contenga alcuni elementi e1, e2, e3, ... . Ad un certo punto nel tempo, vuoi fare una trasformazione degli elementi finora. In un secondo momento, si desidera eseguire un'altra trasformazione sull'insieme nel suo insieme.

Non è necessario eseguire una trasformazione tra , poiché non fa differenza se la raccolta memorizza i valori plain vanilla o transform . Ad un certo punto, è necessario leggere o modificare i valori trasformati: questo è il punto giusto per eseguire una trasformazione della raccolta ( finora ) nel suo complesso.

Se questo è ciò che desideri, questo sarebbe un caso d'uso perfetto per il modello di visitatore : Nel caso, hai Python a portata di mano o non ti dispiace installazione , ecco un bell'esempio su come impostare il modello di visitatore

#!/usr/bin/env python3


class Container:
    def __init__(self, values):
        self._values = values

    def transform(self, transformator):
        self._values = transformator.transform(self._values)

    def accept(self, visitor):
        visitor.visit(self)


class Visitor:
    pass


class Transformator(Visitor):
    pass


class DoublingVisitor(Transformator):
    def visit(self, container):
        container.transform(self)

    def transform(self, values):
        return list(map(lambda x: 2*x, values))


def main():
    container = Container([1, 2, 3])
    doublingVisitor = DoublingVisitor()
    container.accept(doublingVisitor)
    print(container._values)

if __name__ == '__main__':
    main()

Comunque, il codice Python è facile da capire. Il punto è che hai bisogno di una classe che ho chiamato (privo di un nome migliore) Container , che accepts un visitatore. D'altra parte, è necessario un oggetto derivato da Visitor , che contiene come Transformator l'algoritmo (che era il termine che hai usato). Il Visitor ha bisogno di un metodo visit , che ha un parametro (l'oggetto, che visita). Questa è l'intera magia.

Durante la visita, il visitatore chiama transform method sull'oggetto da visitare, che a sua volta riceve il Visitor / Transformator che implementa un metodo transform . Questo è tutto.

Ogni volta che vuoi fare qualcosa con la tua raccolta, puoi accettare un'altra Visitor .

    
risposta data 09.07.2015 - 21:56
fonte
0

Le classi A e B non dovrebbero avere-un istanza l'una dall'altra; in questo modo giace la follia.

Non fai menzione di ciò che fanno gli algoritmi di post-aggiunta, ma dato che devono avere accesso all'istanza di A, devono essere metodi di A, ignorando le costruzioni fastidiose come le funzioni di friend .

class A:
  member x
  member y

  constructor(x, y, post_add_func(pointer or reference or name)

  method add()
      do_add_stuff
      call_post_add_func

  method post_add_1()
  method post_add_2()

Ciò consente a un "algoritmo" di essere associato a un'istanza di A, dandogli così pieno accesso ai membri e ai metodi di A.

Non capisco quale sia lo scopo previsto della classe B, sospetto che fosse solo un contenitore di "algoritmi" e aggiungerebbe complessità non necessaria. B probabilmente non ha bisogno di esistere.

    
risposta data 09.07.2015 - 17:04
fonte