Classe con metodi che dipendono da un argomento variabile, ma questa variabile è costante in ampie parti dell'applicazione

3

Sto cercando consigli su come progettare il seguente scenario:

Ho una classe vettoriale tridimensionale, le voci sono di tipo double . Voglio che due vettori siano considerati uguali se i loro elementi corrispondenti differiscono di meno di una tolleranza data, ad es. 10 ^. (- 10)

(Gli esempi di codice sono in sintassi C # mescolati con pseudocode.)

public class Vector
{
    double x, y, z;

    public bool TolEquals(Vector other)
    {
        return ((|x-other.x| < 10^(-10))  // absolute value of difference
             && (|y-other.y| < 10^(-10))  // must be small enough
             && (|z-other.z| < 10^(-10)));
    }
}

Ho bisogno di una certa flessibilità perché altre persone potrebbero voler usare un altro valore di tolleranza di 10 ^ (- 10).

Ciò significa che ho bisogno di incorporare in qualche modo il valore di tolleranza nel mio metodo di controllo di uguaglianza, ad es. così:

public bool TolEquals(Vector other, double tolerance)
{
    return ((|x-other.x| < tolerance)
         && (|y-other.y| < tolerance)
         && (|z-other.z| < tolerance);
}

Ma so che durante tutta la mia applicazione, la tolleranza sarà costante. E non voglio scrivere

v1.TolEquals(v2, tolerance)

ogni volta che controllo l'uguaglianza poiché non è molto leggibile e poiché gli errori di battitura possono accadere facilmente.

Un'altra idea è di memorizzare tolerance come membro della classe Vector e impostarlo nel costruttore di Vector . Quindi il metodo TolEquals richiede solo un argomento:

public class Vector
{
    double x, y, z;
    double tolerance;

    public Vector(double x, double y, double z, double tolerance)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.tolerance = tolerance;
    }

    public bool TolEquals(Vector other)
    {
        return ((|x-other.x| < tolerance)
             && (|y-other.y| < tolerance)
             && (|z-other.z| < tolerance));
    }
}

Ma poi qualcuno che usa il mio codice potrebbe creare accidentalmente due vettori v e w con diverse tolleranze e otterrebbe risposte diverse da v.TolEquals(w) e w.TolEquals(v) . Neanche questo dovrebbe accadere.

Probabilmente aggiungerò alcuni metodi statici alla mia classe Vector prima o poi che prendono diversi vettori come input e eseguono alcuni calcoli che coinvolgono TolEquals con essi. Quindi devo assicurarmi che tutti i vettori utilizzino lo stesso valore di tolleranza.

Sto cercando una soluzione che mi permetta di definire tolerance da qualche parte nel programma, al di fuori della classe Vector , e quindi tutti i vettori usano quel tolerance per il loro TolEquals . E voglio essere in grado di cambiare tolerance in fase di esecuzione (non ne ho bisogno ora ma potrebbe averne bisogno in seguito), risultando invece in tutti i vettori che utilizzano il nuovo valore. MA potrebbe essere che qualcuno che usa il mio codice abbia bisogno di due diversi valori di tolleranza coesistenti. Avrebbe due distinti gruppi di vettori, utilizzando i diversi valori di tolleranza, ma i gruppi non interagiscono tra loro.

Quindi un campo statico tolerance non è nemmeno un'opzione.

Scriverò anche una classe matrix e forse alcuni altri che avranno un analogo di TolEquals , quindi la soluzione non dovrebbe essere limitata alla mia classe Vector .

Qualche idea?

Modifica

Ispirato alla risposta accettata, mi sento come presentare la mia soluzione finale (in C #) e inserire alcuni commenti. Tutte le informazioni sono fornite nella risposta accettata, ma mi ci è voluto un po 'di tempo per elaborarle in modo da perfezionarle un po' qui. Ho deciso di implementarlo con un campo statico per la tolleranza nella classe vettoriale generica:

public interface ISpace { double TOL(); }

public class ZeroTolSpace : ISpace { public double TOL() => 0; }
public class Tol10Space : ISpace { public double TOL() => 1E-10; }
// Others can easily write new classes for other tolerance values.

public class Vector<T> where T: ISpace, new()
{
    private double x, y, z;
    public static double? tol = null; // Changeable at runtime (if really needed)!

     public Vector(double x, double y, double z)
     {
         this.x = x; this.y = y; this.z = z;
         tol = tol ?? (new T()).TOL(); // Since tol is static, the new-operator
                                       // gets called only once, minimzing overhead.
     }

    public bool TolEquals(Vector other)
    {
         return ((Math.Abs(x-other.x) < tol)
              && (Math.Abs(y-other.y) < tol)
              && (Math.Abs(z-other.z) < tol));
    }

}

Quindi nel metodo Main possiamo fare:

        Vector<ZeroTolSpace> v1 = new Vector<ZeroTolSpace> (0, 0, 0);
        Vector<ZeroTolSpace> v2 = new Vector<ZeroTolSpace>(1E-11, 1E-11, 1E-11);
        v1.TolEquals(v2)); // false since _tol is 0

        Vector<Tol10Space> w1 = new Vector<Tol10Space> (0, 0, 0);
        Vector<Tol10Space> w2 = new Vector<Tol10Space> (1E-11, 1E-11, 1E-11);
        w1.TolEquals(w2)); // true since tol is 1E-10

        v1.TolEquals(w2)); // compile error: types do not match

Matrice e altre classi dipendenti dalla tolleranza possono essere implementate allo stesso modo.

    
posta Kjara 19.07.2016 - 16:15
fonte

2 risposte

4

But I know that throughout my application, the tolerance will be constant.

Questo è un po 'sospetto perché tale tolleranza è solitamente molto specifica dell'algoritmo. Ho avuto a che fare con molto dolore perché qualcuno pensava che potevano farla franca usando una tolleranza costante ovunque.

Ma supponendo che tu lo voglia davvero, potrei fare:

public class Space {
    public Space(double tolerance);


    public Vector vector(double x, double y, double z);
    public Matrix matrix(...);
}

Quindi crei una classe Space che fornisce l'unico modo per creare un vettore. Non è possibile combinare i vettori creati da classi spaziali diverse. All'interno di una particolare applicazione, si crea semplicemente una costante Spazio che definisce la tolleranza che si sta utilizzando.

And I want to be able to change tolerance at run-time (don't need it now but might need it later), resulting in all vectors using the new value instead.

No, non lo fai. Cambiare la tolleranza in fase di esecuzione porterà a dolore e sofferenza. Se il tuo codice deve avere tolleranze diverse in circostanze diverse, devi davvero passare un parametro.

Ora, davvero, cosa sarebbe bello se potessimo usare i generici:

Vector<MySpace>

Quindi disponi di un metodo statico su MySpace per definire la tolleranza. Quindi il sistema di tipi potrebbe verificare che non mischi mai tipi diversi di vettori. Ahimè, questo è al di là di alcuni linguaggi di programmazione quindi potresti o meno essere in grado di usarlo.

== EDIT ==

C ++ può farlo molto bene con i modelli:

struct MySpace {
   public static double tolerance = 0.000001;
}

template<typename T>
class Vector {
    Vector(double x, double y, double z);

    // We can refer to T::tolerance
}

// I can use Vector<MySpace> to refer to vectors with a particular
// space, and have the type system enforce that.

Ma ciò dipende dal fatto che un template C ++ può liberamente riferirsi a elementi all'interno dei suoi parametri di tipo. Ma in altre lingue spesso manca questo.

In C # potresti fare qualcosa di simile (non faccio C #, ma credo che sia valido)

interface Space {
   double tolerance();
}

public class MySpace extends Space {
   double tolerance() {
       return 0.000001;
   }
}

public class Vector<T> where T: Space, new() {
   public Vector(double x, double y, double z);

   // I can get my tolerance via (new T()).tolerance()
   // We hope that the optimizer can get rid of the overhead.
}

Ma in Java, questo non funzionerà, perché non possiamo fare new T . Potremmo fare qualcosa di simile:

class Vector<T extends Space> {
    Vector(T t, double x, double y, double z);

    // t.tolerance() will be the tolerance.    
}

E poi segui una convenzione per creare sempre istanze di Spazio con una sottoclasse di singleton.

===

Maggiori dettagli sulla soluzione Java

In libreria:

public interface Space {
    double tolerance();

    public static <T extends Space> double toleranceForSpace(Class<T> spaceClass) {
    return spaceClass.newInstance().tolerance();
}
}

public Vector<SpaceType> {
    Vector(Class<SpaceType> spaceType, double x, double y, double z);

    // call Space.toleranceFor(spaceType) to get the tolerance
}

Nel codice non di libreria:

class MySpace extends Space {
   double tolerance() { return 0.1; }
}


Vector<MySpace> space = new Vector<>(MySpace.class, 2.3, 4.5, 6.7);
    
risposta data 19.07.2016 - 16:42
fonte
1

I parametri opzionali possono funzionare bene qui.

public class Vector {
  public static readonly double DefaultTolerance = 0.0001; // or whatever.

  public bool Equals(Vector other, double? tolerance = null) {
    tolerance = tolerance ?? DefaultTolerance;
    // go about your business
  }
}

// later
w.Equals(v);
w.Equals(v, 0.001);

E poiché la tolleranza si applica all'operazione , e non ai dati, va bene che i vettori non li posseggano da soli.

    
risposta data 19.07.2016 - 22:24
fonte

Leggi altre domande sui tag