Scegliere un'astrazione di classe quando esistono approcci molteplici

1

Ho difficoltà a provare a progettare una struttura di classe per alcune funzionalità di ricerca. È abbastanza probabile che mi stia avvicinando completamente a questo in modo errato, ma mettendo da parte questo sono curioso di come le altre persone si avvicinerebbero ad una situazione come segue:

Abbiamo bisogno di funzionalità di ricerca che consentano agli utenti di accedere alle informazioni sui pacchi:

  • Le persone saranno in grado di cercare tramite 3 diversi metodi di ricerca: MethodA, MethodB, MethodC.
    (Per dare un po 'di prospettiva su cosa intendo per "metodo", pensiamo a queste cose come ad una ricerca a una riga che farà un parsing intelligente, un altro metodo ha campi modulo specifici per inserire ogni parametro, ecc.)
  • Le persone saranno in grado di cercare tramite 3 diversi tipi di ricerca: TypeA, TypeB, TypeC. (Ad esempio, ricerca per proprietario, ricerca per indirizzo pacco, ecc.)

La situazione non è terribilmente complessa, ma ciò su cui sono bloccato è se dovrei progettare le mie classi attorno a un oggetto metodo astratto o un oggetto di tipo astratto. In sostanza, dovrei fare questo:

public class ConcreteMethodA : BaseMethod 
{
  public ReturnDataType GetResultsTypeA(){};
  public ReturnDataType GetResultsTypeB(){};
  //Etc.
}

O questo:

public class ConcreteTypeA : BaseType
{
  public ReturnDataType GetResultsMethodA(){};
  public ReturnDataType GetResultsMethodB(){};
  //Etc.
}

Se hai qualche consiglio su come risolvere il mio scenario specifico, certamente lo apprezzerò. Tuttavia, sono più interessato a euristica per affrontare un problema di progettazione di questa natura.

    
posta gerg 04.08.2014 - 19:09
fonte

5 risposte

-1

Entrambi gli approcci sono giusti, sovrascrivendo un metodo o sottoclassi. Con "giusto", voglio dire, puoi creare un programma che funzioni, con entrambi gli approcci.

Tuttavia suggerisco di prendere una definizione di classe per ogni ricerca, è più flessibile.

È possibile aggiungere proprietà e sostituire il metodo "Cerca" e aggiungere ulteriori metodi di ricerca, semplicemente aggiungendo classi. Sembra un po 'complicato, ma, nel mondo reale, rende la tua applicazione più facile da estendere.

Inoltre, dividi ciascun metodo di ricerca, in una classe specifica.

public abstract class SingleSearchClass : Object
{
  public ReturnDataType Search(Object SomeParameters){};
}

public /* concrete */ class QuickSearchClass : SearchAbstractClass
{
  public ReturnDataType Search(Object SomeParameters){};
}

public /* concrete */class HeuristicsSearchClass : SearchAbstractClass
{
  public ReturnDataType Search(Object SomeParameters){};
}

public /* concrete */ class RandomSearchClass : SearchAbstractClass
{
  public ReturnDataType Search(Object SomeParameters){};
}

E dichiara un wrapper / adattatore, che consente all'utente di scegliere i diversi metodi di ricerca disponibili:

public enum SearchMethodEnum
{
  NoSearch,
  QuickSearch,
  HeuristicsSearch,
  RandomSearch,
}   

public /* concrete */ class SearchClientClass : Object
{
  public /* property*/ SearchMethodEnum SearchMethod
    { get; set; }

  public /* property*/ Object Params
    { get; set; }

  public ReturnDataType Execute()
  {
    ReturnDataType Result = null;          

      if (this.SearchMethod != SearchMethodEnum.NoSearch)
      {
        SingleSearchClass Search = null;

        switch (this.SearchMethod)
        {
          case SearchMethodEnum.QuickSearch:
            Search = new SearchClientClass();
          break;

          case SearchMethodEnum.HeuristicsSearch:
            Search = new HeuristicsSearchClass ();
          break;

          case SearchMethodEnum.RandomSearch:
            Search = new RandomSearchClass ();
          break;
        } // switch

        Result = Search.Search(this.Params);
      } // if

    return Result;
  }
} // class SearchClientClass

E, un esempio generico (pseudocodice):

public /* concrete */ class SearchExampleClass
{   
  public static void main(string[] args)
  {
    SearchClientClass Client = new SearchClientClass();

    Client.Params = "John Doe";
    Client.SearchMethod = SearchMethodEnum.QuickSearch;

    ReturnDataType Result = Client.Execute();

    Client = null;
  }

}

[UPDATE] Estendi descrizione.

Solo i miei 2 cent.

    
risposta data 04.08.2014 - 22:05
fonte
4

La tua semantica è completamente basata sulle intenzioni.

public ReturnDataType findBasedOnStaticQuery(){};
public ReturnDataType findByPredeterminedFields(Fields...){};
public ReturnDataType findBySomethingElse(){};

Il punto dei metodi di denominazione è quello di trasmettere il loro intento, non la loro implementazione.

getXXX di solito significa recuperare e valore di istanza privato, get non è un buon prefisso quando stai facendo veramente ricerche. findXXX è più semanticamente corretto.

    
risposta data 04.08.2014 - 19:57
fonte
1

in entrambi i casi hai nomi come frobnicateTypeA e frobnicateTypeB . Questa è una strong indicazione che dovresti passare istanze di questi tipi come parametri.

In questo caso specifico, stai provando a progettare un sistema per eseguire query. Raccogliamo alcuni parametri generali:

  • gli utenti possono utilizzare diversi metodi per eseguire una query, ad es. un'interfaccia basata su moduli o l'elaborazione in linguaggio naturale.
  • gli utenti possono cercare per proprietà diverse, ad es. dal proprietario del pacco o dall'indirizzo del pacco.

Trovo importante notare che l'hardcoding nei nomi dei metodi non è auspicabile, poiché ostacola l'estensibilità:

  • Potresti voler aggiungere un metodo di query diverso. Per esempio. potrebbe essere necessario aggiungere un metodo di query basato sulla voce. Vuoi aggiungere un metodo GetResultsMethodSpeech a ogni classe che capita di toccare i metodi di query?

    Fortunatamente, questo può essere evitato.

  • È possibile che si desiderino consentire le richieste di proprietà aggiuntive, come il destinatario di pacchi o il peso del pacco o l'ora di arrivo stimata. Vuoi davvero aggiungere un altro metodo per ogni classe che capita di toccare queste proprietà?

    Purtroppo, questo non può essere evitato.

In breve, entrambe le architetture suggerite non vengono ridimensionate.

La chiave della soluzione è che tutti i metodi di query sono solo espressioni diverse dello stesso contenuto: con ogni metodo di query esiste un modo equivalente di esprimere la stessa query. Pertanto, ci sarà solo una classe che rappresenta la query, ma diverse funzioni che assemblano questa query dall'input dell'utente. Per esempio. potremmo avere un QueryFactory che emette Query s. Dovresti quindi passare quella query su un wrapper del database.

class Query { ... }

class QueryFactory {
    public Query FromForm(FormData);
    public Query FromNLP(String);
    public Query FromAudio(AudioRecording);
}

// in the controller, different handlers would then invoke
// the correct method in the factory, and pass the query to the DB
QueryFactory makeQuery = ...;

database.Execute(makeQuery.FromForm(formInput));

// or in a different handler:
database.Execute(makeQuery.FromAudio(audioInput));

Per aggiungere un ulteriore metodo di query, è sufficiente aggiungere la logica di analisi effettiva per quel tipo di metodo di query e aggiungere il codice per quella specifica interfaccia utente. Non è necessario aggiungere alcun codice nelle classi che eseguono effettivamente tale query.

    
risposta data 04.08.2014 - 20:12
fonte
1

Non lo so, se ti sto facendo bene, ma per quanto ho capito, hai il seguente scenario:

1) Esistono diversi tipi di query:

  • ricerca per proprietario
  • ricerca per indirizzo di destinazione ...

2) Ognuno dei quali ha per esempio origini diverse o qualsiasi altra cosa

Quindi il modo migliore per risolverlo sarebbe tramite strategia . Definire le query consentite in un'interfaccia (supponendo che stiate scrivendo C #):

interface IQueryMechanism
{
   Result SearchByOwner(string owner);
   ...
}

Quindi definendo un servizio che si occupa delle chiamate in entrata.

public class ParcelService 
{
    IQueryMechanism mechanism;

    public void setQueryMechanism(IQueryMechanism mechanism) { this.mechanism=mechanism; }
    public SearchByOwner(string owner) { return mechanism.SearchByOwner(string owner);} 
}

Quando installi il ParcelService potresti iniettare il meccanismo appropriato tramite setter injection (per motivi di leggibilità ho lasciato le clausole di protezione). Quindi ogni volta che arriva una richiesta, il repository delega questa richiesta al meccanismo sottostante.

E la tua classe di meccanismo sembra

public class OnlineSearch : IQueryMechanism  
{
    public SearchByOwner(string owner) { /* business logic here */} 
    ...
}

Quindi istanzia un Service e scambia in fase di esecuzione, QueryMechanism .

    
risposta data 04.08.2014 - 20:57
fonte
0

Adattatore & modello di repository

my examples are written in php, because it is more verbose.

Il tipo di problema

Per quanto posso capire, i tuoi 'tipi' sono in effetti DataObjects (o modelli, o entità o come mai vuoi chiamarli). Ogni tipo estende la classe DataOBject di base:

class TypeA extends|implements DataObject {}
class TypeB extends DataObject {}

Questi oggetti dati sono la rappresentazione dei tuoi dati. Ti danno un bel set di metodi per interagire con i dati. per esempio. getGoo() e setBar('ook')

Abbiamo anche bisogno di un tipo di interfaccia per recuperare, creare (roba CRUD) gli oggetti dati. Ecco dove arriva il repository:

class TypeARepository extends AbstractRepository implements Repository {}

con

interface Repository
{
    public function findAll();

    public function findById($id);

    public function where($key, $operator, $value);
}

Ora abbiamo una bella interfaccia per cercare i nostri diversi tipi. Il nostro controller non si cura nemmeno di quale tipo sta cercando. Per quanto ne sa, sono solo i dati restituiti da un repository:

class MyController
{
    private $repository;

    public function __construct(Repository $repository)
    {
        $this->repository = $repository;
    }
    public function doStuff()
    {
        $data = $this->repository->where('foo','=','bar');
        pass_to_View_magic($data);
    }
}

E non siamo antagonisti a quel controller:

// il controllore non si preoccupa di ciò che si passa dentro, purché sia un deposito che può interrogare. $ controller = new MyController (new TypeARepository ()); $ Controller- > doStuff ();

Ma per quanto riguarda i miei diversi metodi?

I tuoi 3 diversi metodi in realtà fanno esattamente la stessa cosa. Cercano. L'unica cosa che fanno è prendere una sorta di input e poi passarlo alla funzione di ricerca nel modo corretto. Questo è il modello dell'adattatore. (google). È possibile utilizzare il modello di adattatore nelle funzioni del controller o come classi completamente diverse:

class MyController
{
    private $repository;

    public function __construct(Repository $repository)
    {
        $this->repository = $repository;
    }
    public function doStuff()
    {
        $data = $this->repository->where('foo','=','bar');
        pass_to_View_magic($data);
    }
    public function doSuffDifferently($data)
    {
        $data = $this->repository->where($data[0],$data[1],$data[2]);
    }
}

Ora doStuffand doStuffDifferently fungono da piccoli adattatori dell'interfaccia di Repository.

Hoep aiutato

    
risposta data 04.08.2014 - 20:07
fonte

Leggi altre domande sui tag