quale pattern è più intuitivo per un'app calcolatrice?

0

Il titolo dice tutto. Sto provando a costruire un'applicazione per calcolatrice (per scopi di autoapprendimento). L'applicazione avrà un'interfaccia utente molto comune, con più (+), meno (-), moltiplicato (*) e un pulsante dividi (/). Inoltre, l'app può eseguire calcoli con numeri reali e numeri complessi.

Quindi, la situazione qui è, a seconda della modalità (normale o complessa), lo stesso pulsante dovrebbe eseguire calcoli diversi? Quale modello dovrei usare per questa situazione? Sento che lo schema della strategia dovrebbe essere una buona idea, ma poi, non so esattamente come implementarlo - voglio dire, non sono sicuro di come progettare le mie classi, cosa avere come interfacce e cosa delegati.

ATTUALMENTE, IL MIO DESIGN CONTAINS

IOperation
{
  Do();
}
Add:IOperation{}
Subtract:IOperation{}
Multiply:IOperation{}
Divide:IOperation{}
Root:IOperation{} //not supported by ComplexNumber
ISupportedOperation 
{ 
   IList<IOperation> SupportedOps {get;}
}
INumber : ISupportedOperation {}
RealNumber:INumber{}
ComplexNumber : INumber {}
  1. I nomi dell'interfaccia iniziano con I
  2. Tutti gli altri sono classi concrete

MA È UN MESS. E, sono totalmente perso nelle mie classi e interfacce.

PS: Certo, posso farlo usando if / else, ma non è quello che voglio fare. Non perché io voglia forzatamente usare un modello, ma perché, quelli if-else saranno sparsi ovunque nel programma per es. ReadInput, PlusButtonClick, MinusButtonClick, ecc. E, come ho capito, i pattern di progettazione dovrebbero evitare questo tipo di situazioni di ripetizioni del codice, mediante una riorganizzazione intelligente / complicata del codice esistente.

PPS: mi spiace essere troppo prolisso.

    
posta MrClan 26.09.2014 - 03:45
fonte

4 risposte

6

Stai facendo questo all'indietro. Dimentica gli schemi di progettazione per un po '.

Costruisci il tuo programma di calcolatrice. Costruiscila da zero e disegnalo ma è intuitivo per te. Pensaci per qualche minuto, fatti qualche idea, poi rotola con esso, dall'inizio alla fine.

Mentre stai provando a implementare la tua calcolatrice, potresti non progettarla correttamente la prima volta e dovrai ristrutturarla di tanto in tanto mentre impari di più su ciò che è coinvolto nel tuo progetto. Questo è il processo di apprendimento, e questo ti insegnerà come hai strutturato il tuo codice per attaccare il problema a portata di mano.

Quindi, dopo averlo fatto, dai uno sguardo agli schemi di progettazione descritti nel GoF book e scopri quali sono gli schemi che descrivono meglio il modo in cui hai finito per progettare la tua calcolatrice. Ora sai per cosa sono usati questi modelli, e ora saprai come scegliere quale schema ha senso da applicare quando.

I modelli di progettazione non sono elementi costitutivi, sono nomi per paradigmi strutturali comuni, per migliorare la comunicazione con altri sviluppatori.

    
risposta data 29.09.2014 - 23:29
fonte
4

Prima di tutto, una semplificazione: tutti i numeri reali sono un sottoinsieme di numeri complessi, quindi fai semplicemente tutto con numeri complessi.

In secondo luogo, perché Root non è supportato da ComplexNumber ? Se stai supportando numeri complessi, puoi prendere la radice di qualsiasi numero, compresi quelli negativi o complessi. Tutto diventa semplicemente moltiplicazione vettoriale. Quindi, se si tratta di numeri complessi, non è necessario avere ISupportedOperation , poiché ogni operazione è supportata (a meno di dividere per zero, ovviamente, ma di solito gestiamo tale eccezione generando un'eccezione).

In realtà, questo dovrebbe fare:

interface ICalculator
{
    IComplexNumber Add(IComplexNumber a, IComplexNumber b);
    IComplexNumber Subtract(IComplexNumber a, IComplexNumber b);
    IComplexNumber Multiply(IComplexNumber a, IComplexNumber b);
    IComplexNumber Divide(IComplexNumber a, IComplexNumber b);
    IComplexNumber Root(IComplexNumber a, IComplexNumber b);
}

Ricorda anche che una radice quadrata è solo a^(1/2) , quindi un'interfaccia più generica sarebbe:

interface ICalculator
{
    IComplexNumber Add(IComplexNumber a, IComplexNumber b);
    IComplexNumber Subtract(IComplexNumber a, IComplexNumber b);
    IComplexNumber Multiply(IComplexNumber a, IComplexNumber b);
    IComplexNumber Divide(IComplexNumber a, IComplexNumber b);
    IComplexNumber Power(IComplexNumber a, IComplexNumber b);
}
    
risposta data 26.09.2014 - 13:51
fonte
1

In realtà prenderò un approccio diverso. Ti suggerisco di usare un modello di strategia (pseudo codice - è stato un po 'che non usavo C #).

class CalculatorContext {
   private List<Number> numberHistory;
   private Number currentDisplay;
   private List<Operation> commandHistory;
   private Operation currentCommand;

   public void evaluateAnswer(Operation op) {
       currentCommand = op;
       commandHistory.put(op);
       Number returnVal = currentCommand.execute(this);
       numberHistory.put(currentDisplay);
       currentDisplay = returnVal;
   }

   //Getters/setters.
}

interface Operation {
   public Number execute(CalculatorContext ctx);
}

class AddOperation : Operation {
   public Number execute(CalculatorContext ctx) {
      //Current number to add
      Number a = ctx.getCurrentDisplay();

      //Previous number to add
      assert(ctx.getNumberHistory() != null && ctx.getNumberHistory().size() > 1);
      Number b = ctx.getNumberHistory().last();

      return a + b;
    }
}

Il motivo per cui mi piace questo approccio è che potresti modificarlo per gestire le parentesi piuttosto facilmente, ma riflette anche come funziona un vero calcolatore (uno stato attuale e poi modificato tramite operazioni). Nel metodo in cui non si utilizza un approccio simile alla coda, è necessario gestirlo in un modo diverso e molto più doloroso dall'interfaccia utente. Cose come la radice quadrata diventano un problema, mentre qui si ottiene solo "A", non A + B. Potresti usare un "potere" come sopra ma il problema con questo è - presumibilmente - che non vuoi il CalculatorContext o l'IU essere consapevoli delle specificità di tale operazione. Quindi probabilmente lo gestirò nell'operazione e se diventasse oneroso raccogliendo i parametri, avrei creato metodi di supporto per questo.

    
risposta data 29.09.2014 - 23:13
fonte
1

Che ne dici di questo ...

interface IOperator
{
    double Calculate(double num1, double num2);

    // ComplexNumber Calculate(ComplexNumber num1, ComplexNumber num2); // up to you
}

class AddOperator : IOperator
{
    public AddOperator() { }

    public double Calculate(double num1, double num2)
    {
        return num1 + num2;
    }

    // ComplexNumber Calculate(ComplexNumber num1, ComplexNumber num2) { ... }
}

class MinusOperator : IOperator
{
    public MinusOperator() { }

    public double Calculate(double num1, double num2)
    {
        return num1 - num2;
    }

    // ComplexNumber Calculate(ComplexNumber num1, ComplexNumber num2) { ... }
}

// ...

Factory usa per creare un'istanza:

static class Factory
{
    public static T CreateInstance<T>() where T : new()
    {
        return new T();
    }
}

Cliente:

static void Main(string[] args)
{
    IOperator op = Factory.CreateInstance<AddOperator>();
    Console.WriteLine(op.Calculate(1, 2));

    // Console.WriteLine(op.Calculate(complexNum1, complexNum2));

    Console.Read();
}

Ora, se voglio aggiungere un operatore, aggiungi solo una classe suboperator e implementa IOperator . Il metodo di sovraccarico determinerà calcolare semplice o complesso .

Questa è una versione di aggiornamento basata su Metodo Facotry usa Tipo generico .

    
risposta data 30.09.2014 - 05:12
fonte

Leggi altre domande sui tag