È sempre una buona idea avere classi mescolate / dipendenti da classi di helper? C #

1

Ho una classe semplice che ha un campo privato e una proprietà pubblica. La proprietà chiama anche un metodo di supporto statico da cui dipende in qualche modo e ritengo che potrebbe non essere una buona cosa.

Questo è il mio codice:

public class PathLine
{
    private Vector2Int _gridA;
    public Vector2Int GridA
    {
        get { return _gridA; }
        set
        {
            _gridA = value;
            PointAInWorld = Grids.ToWorld(gridA, 1, Grids.GridPlane.XZ);
        }
    }

    public Vector3 PointAInWorld { private set; get; }
    private Vector2Int _gridB;
    public Vector2Int GridB
    {
        get { return _gridB; }
        set
        {
            _gridB = value;
            PointBInWorld = Grids.ToWorld(_gridB, 1, Grids.GridPlane.XZ);
        }
    }

    public Vector3 PointBInWorld { private set; get; }
}

Questo è generalmente negativo a causa della sua dipendenza? C'è un modo più pulito di fare questo in modo che la classe sia meno dipendente?

    
posta WDUK 27.08.2018 - 04:14
fonte

3 risposte

3

Is this generally bad because of its dependency ?

Non esiste una regola di cattiva pratica inerente che è stata violata qui. Se non altro, le dipendenze sono il primo passo nella giusta direzione (quando si inizia da un singolo metodo monolitico).

Non ci sono problemi reali qui, a condizione che questo metodo statico soddisfi i criteri per essere un metodo statico . I criteri principali sono:

  • Il metodo statico non si basa su uno stato interno e fa affidamento solo sui parametri del metodo che gli vengono forniti.
  • L'operazione rappresentata dal metodo statico è globalmente corretta e non specifica per il contesto.

Questo sembra essere il caso del tuo codice, quindi non ci sono problemi.

Is there a cleaner way of doing this so the class is less dependant?

Non come una regola globale senza un particolare contesto dato. Nei casi determinati , può essere giustificato utilizzare un'interfaccia e un'iniezione di dipendenza, ma non è il caso del codice. Elaborerò le differenze.

Quando utilizzare i metodi statici:

In base al metodo statico, deduco che il metodo è universalmente corretto e che non ci sarà mai un "metodo concorrente" diverso dal metodo originale ma funzionalmente equivalente.

Ad esempio:

public static int AddNumbers(int a, int b)
{
    return a + b;
} 

Questo è universalmente corretto, e quindi dovrebbe essere un metodo statico.

Quando utilizzare l'iniezione delle dipendenze:

Supponiamo che tu stia facendo affidamento su una fonte esterna (ad esempio Google) per eseguire il calcolo:

public static int AddNumbers(int a, int b)
{
    return Google.Query($"{a} + {b}");
} 

Inizialmente, potresti rendere questo un metodo statico, poiché vuoi che questa sia la tua unica risorsa esterna. Tuttavia, dovresti identificare che è possibile che Google sia offline, o forse addirittura interrompere il servizio di calcoli matematici.

Vuoi che la tua applicazione sia in grado di interrogare altre risorse quando non puoi più fare affidamento su Google. Quindi diciamo che usi WolframAlpha come backup. Poiché ora hai due opzioni concorrenti ma funzionalmente equivalenti , non puoi renderle dei metodi statici.

Bene, potresti:

public static int AddNumbersViaGoogle(int a, int b)
{
    return Google.Query($"{a} + {b}");
} 

public static int AddNumbersViaWolframAlpha(int a, int b)
{
    return WolframAlpha.Add(a, b);
} 

... ma sarebbe cattiva pratica .

Invece, crei un'interfaccia e quindi inserisci la dipendenza corretta nella tua classe:

public interface INumberAddition
{
    int Add(int a, int b);
}

L'interfaccia stipula il contratto che entrambi i gestori di Google e WolframAlpha devono seguire.

public class GoogleAdder : INumberAddition
{
    public int Add(int a, int b)
    {
        return Google.Query($"{a} + {b}");
    } 
}

public class WolframAlphaAdder : INumberAddition
{
    public int Add(int a, int b)
    {
        return WolframAlpha.Add(a, b);
    } 
}

Quindi modifica la classe per ricevere qualsiasi oggetto che implementa INumberAddition :

public class Calculator
{
    private readonly INumberAddition _additionHandler;

    public Calculator(INumberAddition additionHandler)
    {
        _additionHandler = additionHandler;
    }

    public int FindOutWhatOnePlusOneIs()
    {
         return _additionHandler.Add(1,1);
    }
}

In questo modo, scegli la tua dipendenza a un livello superiore:

var googleCalculator = new Calculator(new GoogleAdder());
var googleResult = googleCalculator.FindOutWhatOnePlusOneIs();

var wolframCalculator = new Calculator(new WolfranAlphaAdder());
var wolframResult = wolframCalculator.FindOutWhatOnePlusOneIs();

Il caso d'uso è molto semplificato, ma mostra l'intenzione principale: Calculator può facilmente passare da una risorsa esterna all'altra quando necessario.

Riassunto

Se ragionevolmente si aspetta che la tua logica sia universalmente vera e non debba mai essere sostituita, allora è sufficiente un metodo statico.

Se ragionevolmente si aspetta che la dipendenza venga scambiata a un certo punto, o se si desidera ridurre al minimo le modifiche al codice, è necessario che una dipendenza sia scambiata inaspettatamente; quindi è meglio iniettare la dipendenza utilizzando un'interfaccia in modo da poterlo sostituire facilmente senza dover modificare gran parte del codice.

    
risposta data 27.08.2018 - 16:12
fonte
0

È abbastanza comune che le classi dipendano da altre classi per alcune funzionalità. Ma di solito è definito dalle interfacce, non dai metodi statici.

Nel tuo caso, potresti creare un'interfaccia, ad es. IWorldConverter con un metodo (non statico, ovviamente) Vector3 ToWorld(Vector2Int vector, GridPlane plane) .

Quando viene creato un PathLine , quel convertitore viene iniettato, idealmente tramite il costruttore:

public PathLine(IWorldConverter converter)
{
    // store the converter in a field
}

e in seguito, chiameresti il metodo del convertitore invece del metodo statico.

In questo modo, non esiste un accoppiamento diretto con la classe che implementa il metodo e l'istanza concreta può essere sostituita per alcuni scopi (ad es. test).

    
risposta data 27.08.2018 - 11:15
fonte
0

La dipendenza dal metodo statico crea un accoppiamento stretto tra PathLine e Grids.ToWorld , ma non possiamo davvero dire dalla domanda posta se questo accoppiamento stretto porterà a problemi nell'applicazione. Se i chiamanti non conoscono e non dovrebbero conoscere la classe Grid o i metodi di conversione alternativi, allora lascialo come è.

Quello che trovo scomodo con PathLine è il raggruppamento di due coppie vettore2int e vettore mutabili in un oggetto - qui preferirei fornire due parametri nel costruttore che rendono l'oggetto immutabile e che richiede ai chiamanti di creare un nuovo oggetto se qualcosa deve cambiare.

Diamo un'occhiata alle conseguenze della mutabilità in termini di potenziale refattore, per esempio, per iniettare una conversione (alternativa). Così com'è, l'oggetto attraversa diverse fasi potenziali:

  • costruito ma non inizializzato - poiché i campi sono ancora vuoti,
  • un campo (coppia) inizializzato
  • entrambi i campi (coppie) inizializzati
  • oggetto riutilizzato per altri scopi, per usi legati alla mutazione o per usi totalmente indipendenti

In un refactoring (di iniezione) dobbiamo preoccuparci in particolare dell'ultima fase, poiché un uso non correlato potrebbe richiedere una conversione diversa, il che significa ora una diversa istanza di oggetto invece di riutilizzare quella vecchia, e potenzialmente qualche ulteriore la ristrutturazione è necessaria poiché in precedenza i chiamanti potevano condividere una singola istanza di oggetto in modo più ampio.

Al contrario, lavorare con oggetti immutabili elimina l'ultima fase, e quindi affrontarla durante il refactoring.

(C'è anche il problema di ordinare che le proprietà dell'oggetto possano essere lette prima di essere inizializzate.)

    
risposta data 27.08.2018 - 18:25
fonte

Leggi altre domande sui tag