Dovrei aggiungere un livello per mantenere l'oggetto sempre valido e immutabile?

3

Sto sviluppando COM. Una fabbrica deve essere inizializzata prima fornendo un ID. Non posso passare argomenti all'istanza IFactory tramite CoCreateInstance. Quindi IFactory potrebbe aver bisogno di aggiungere un metodo init per essere chiamato allora. (come approccio 2)

Non voglio richiedere al client di chiamare init esplicitamente (potrebbero dimenticare). In modo che aggiungo un livello chiamato IEntryPoint . Ciò garantisce che GetFactory restituisca un IFactory verificato senza prima chiamato init . Quindi l'istanza IFactory non avrà lo stato che indica che è valido o non valido. Tutti i suoi metodi possono essere const. (come approccio 1)

È incapsulato se IEntryPoint ha solo il metodo GetFactory ?

Approccio 1: IEntryPoint::GetFactory assicura IFactory istanza è valida se viene restituito S_OK .

interface IEntryPoint
{
    HRESULT GetFactory(size_t id, IFactory** ppFactory) const;
};


interface IFactory
{
    HRESULT GetInstance(size_t id, IInstance** ppInstance) const;
};


interface IInstance
{
    HRESULT Getter1() const;
    HRESULT Getter2() const;
    HRESULT Getter3() const;
};

vs

Approccio 2: % istanza diIFactory richiede al client di chiamare prima init .

interface IFactory
{
    HRESULT Init(size_t id);
    HRESULT GetInstance(size_t id, IInstance** ppInstance) const;
};


interface IInstance
{
    HRESULT Getter1() const;
    HRESULT Getter2() const;
    HRESULT Getter3() const;
};
    
posta Chen OT 03.11.2015 - 04:53
fonte

2 risposte

5

Entrambe le soluzioni mi sembrano accettabili.

La tua prima soluzione è essenzialmente una "factory factory", che potrebbe essere percepita come un'astrazione superflua, ma hai ragione che impone fermamente il flusso di lavoro richiesto all'utente. Questo è un approccio "al successo", in cui la ovvia cosa da fare e la giusta cosa da fare sono le stesse.

Hai ragione che la tua seconda soluzione offre al cliente l'opportunità di scrivere un bug, un'opportunità che prenderanno! Ma possiamo anche supporre che le persone che scrivono i client COM siano abbastanza intelligenti e familiari con le coclasse che necessitano di inizializzazione. Ho scritto oggetti COM con stato con requisiti di flusso di lavoro complessi (più tipi di inizializzazione e arresto); la chiave per convincere i clienti a scrivere correttamente il codice era essere brutali nel rilevare i bug dei client e restituire dei REALI veramente, veramente duri. Il mio preferito personale era E_UNEXPECTED, che ha dato il messaggio di errore "Insuccesso catastrofico".

Se decidi di utilizzare la seconda opzione, devi scrivere un codice che rilevi flussi di lavoro non validi, chiamando Init() zero volte, chiamando Init() due volte e così via e rendendo l'oggetto factory completamente inutilizzabile ogni volta che il contratto viene violato. Non essere clemente; che genera insetti e comportamenti indefiniti. Se hai un requisito di flusso di lavoro, rendi il flusso di lavoro il più semplice possibile, documentalo chiaramente e costringi i tuoi clienti a eseguire il debug del codice quando sbagliano.

Un terzo modello che mi piace è modificare il tuo metodo Init in modo che invece di prendere l'ID di cui ha bisogno, invece prende un oggetto di tipo IFactoryClient che ha un metodo GetFactoryId che restituisce un ID. Il chiamante è quindi tenuto a fornire un'implementazione valida di questa interfaccia. La fabbrica decide quindi quando chiamare il client per recuperare il suo ID quando è necessario. Questa tecnica è aggiunta al sovraccarico per il client, ma offre un buon punto di estensione per il futuro. Le versioni future dello stabilimento possono QueryInterface o QueryService del client per avviare una conversazione tra il client e il servizio sul livello di servizi che si aspettano l'uno dall'altro.

    
risposta data 03.11.2015 - 20:35
fonte
0

I don't want to required client to call init explicitly (they may forget)

Come hai detto, l'approccio 2 induce un accoppiamento temporale che è realmente soggetto a errori (l'utente può dimenticare di chiamare Init e lasciare una fabbrica in uno stato indefinito).

Ovviamente, il modo migliore per risolvere questo problema sarebbe fornire l'ID quando si costruisce un'implementazione di IFactory . Ma dal momento che stai usando COM e non puoi passare argomenti al costruttore, aggiungere questo livello ( IEntryPoint ) è il modo migliore per limitare questo accoppiamento temporale incapsulandolo e rendendolo esplicito nell'implementazione di IEntryPoints ( s).

    
risposta data 03.11.2015 - 10:10
fonte

Leggi altre domande sui tag