Domanda

Ho una classe astratta in una libreria. Sto cercando di rendere il più semplice possibile per implementare correttamente una derivazione di questa classe. Il problema è che ho bisogno di inizializzare l'oggetto in un processo in tre fasi: afferrare un file, fare un paio di passaggi intermedi, e poi lavorare con il file. Il primo e l'ultimo passo è particolare alla classe derivata. Ecco un esempio ridotto all'osso.

abstract class Base
{
    // grabs a resource file specified by the implementing class
    protected abstract void InitilaizationStep1();

    // performs some simple-but-subtle boilerplate stuff
    private void InitilaizationStep2() { return; }

    // works with the resource file
    protected abstract void InitilaizationStep3();

    protected Base()
    {
        InitilaizationStep1();
        InitilaizationStep2();
        InitilaizationStep3();
    }
}

Il problema, naturalmente, è il metodo di chiamata virtuale nel costruttore. Ho paura che il consumatore della biblioteca si troveranno costretti quando si utilizza la classe se non possono contare sulla classe derivata essere completamente inizializzato.

ho potuto tirare fuori la logica del costruttore in un metodo Initialize() protetta, ma poi il realizzatore potrei definire Step1() e Step3() direttamente invece di chiamare Initialize(). Il nocciolo della questione è che non ci sarebbe stato nessun errore evidente se Step2() viene saltato; semplicemente terribile prestazioni in determinate situazioni.

Mi sento come in entrambi i casi v'è una grave e non ovvio "Beccato" che i futuri utenti della biblioteca dovranno lavorare intorno. C'è qualche altro disegno dovrei usare per ottenere questo tipo di inizializzazione?

Posso fornire maggiori dettagli, se necessario; Stavo solo cercando di fornire l'esempio più semplice che esprime il problema.

È stato utile?

Soluzione

Questo è il modo troppo per inserire nel costruttore di qualsiasi classe, molto meno di una classe base. Vi suggerisco di fattore che fuori in un metodo Initialize separata.

Altri suggerimenti

Vorrei prendere in considerazione la creazione di un Abstract Factory che è responsabile per istanziare e inizializzare le istanze del tuo derivato classi con un metodo modello per l'inizializzazione.

Per fare un esempio:

public abstract class Widget
{
    protected abstract void InitializeStep1();
    protected abstract void InitializeStep2();
    protected abstract void InitializeStep3();

    protected internal void Initialize()
    {
        InitializeStep1();
        InitializeStep2();
        InitializeStep3();
    }

    protected Widget() { }
}

public static class WidgetFactory
{
    public static CreateWidget<T>() where T : Widget, new()
    {
        T newWidget = new T();
        newWidget.Initialize();
        return newWidget;
    }
}

// consumer code...
var someWidget = WidgetFactory.CreateWidget<DerivedWidget>();

Questo codice fabbrica potrebbe essere migliorata drammaticamente - soprattutto se si è disposti a utilizzare un contenitore CIO di gestire questa responsabilità ...

Se non si ha il controllo sulle classi derivate, non sarai in grado di impedire loro di offrire un costruttore pubblico che può essere chiamato -. Ma almeno si può stabilire un modello di utilizzo che i consumatori potrebbero aderire

Non è sempre possibile per impedire agli utenti di voi classi da se stessi la zappa sui piedi - ma, è possibile fornire l'infrastruttura per aiutare i consumatori a utilizzare il codice correttamente quando si familiarizzare con il design

.

In molti casi, roba di inizializzazione prevede l'assegnazione di alcune proprietà. E 'possibile effettuare tali proprietà si abstract e hanno classe derivata loro ignorare e tornare un certo valore invece di passare il valore al costruttore di base per impostare. Naturalmente, se questa idea è applicabile dipende dalla natura della vostra classe specifica. In ogni caso, avendo più di tanto il codice nel costruttore è puzzolente.

A prima vista, vorrei suggerire di spostare questo tipo di logica ai metodi basandosi su questa inizializzazione. Qualcosa di simile

public class Base
{
   private void Initialize()
   {
      // do whatever necessary to initialize
   }

   public void UseMe()
   {
      if (!_initialized) Initialize();
      // do work
   }
}

Dal punto 1 "afferra un file", potrebbe essere utile avere Initialize (IBaseFile) e saltare il punto 1. In questo modo il consumatore può ottenere il file tuttavia loro piacimento - dal momento che è astratto comunque. È ancora possibile offrire un 'StepOneGetFile ()' come astratto che restituisce il file, in modo che potessero implementare in questo modo, se lo desiderano.

DerivedClass foo = DerivedClass();
foo.Initialize(StepOneGetFile('filepath'));
foo.DoWork();

Edit: ho risposto questo per C ++ per qualche motivo. Siamo spiacenti per C # vi consiglio contro un metodo Create() -. Utilizzare il costruttore e assicurarsi che gli oggetti rimane in uno stato valido fin dall'inizio. C # consente di effettuare chiamate virtuali dal costruttore, ed è OK per utilizzare loro se si documentare attentamente la loro funzione prevista e pre e post-condizioni. Ho dedotto C ++ la prima volta attraverso perché non consente chiamate virtuali dal costruttore.

rendere l'individuo funzioni di inizializzazione private. L'può essere sia private e virtual. Poi offrire una funzione di Initialize() non virtuale pubblica che li chiama nel giusto ordine.

Se si vuole fare in modo tutto avviene come viene creato l'oggetto, fare la protected costruttore e utilizzare una funzione statica Create() nelle classi che chiama Initialize() prima di tornare l'oggetto appena creato.

Si potrebbe impiegare il seguente trucco per fare in modo che l'inizializzazione viene eseguita nell'ordine corretto. Presumibilmente, si dispone di alcuni altri metodi ( DoActualWork ) implementato nella classe base, che si basano su l'inizializzazione.

abstract class Base
{
    private bool _initialized;

    protected abstract void InitilaizationStep1();
    private void InitilaizationStep2() { return; }
    protected abstract void InitilaizationStep3();

    protected Initialize()
    {
        // it is safe to call virtual methods here
        InitilaizationStep1();
        InitilaizationStep2();
        InitilaizationStep3();

        // mark the object as initialized correctly
        _initialized = true;
    }

    public void DoActualWork()
    {
        if (!_initialized) Initialize();
        Console.WriteLine("We are certainly initialized now");
    }
}

Non vorrei fare questo. Trovo in generale che fare qualsiasi lavoro "reale" in un costruttore finisce per essere una cattiva idea in fondo alla strada.

Al minimo, avere un metodo separato per caricare i dati da un file. Si potrebbe fare un argomento di prendere un ulteriore passo avanti e hanno un oggetto separato responsabile per la costruzione uno dei tuoi oggetti da file, separando le preoccupazioni di "caricamento dal disco" e le operazioni in memoria sull'oggetto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top