Come posso creare wrapper estensibili di lettori e scrittori attorno a una classe di risorsa opaca condivisa?

3

In C ++, Supponi di avere una classe Buffer (non può essere ereditata) che viene fornita e può supportare la seguente operazione:

class Buffer {
public:
    void write_buffer(Data);
    Data read_next_data();
};

Ora desidero qualcosa come BufferWriter e BufferReader .

Ci sono alcuni requisiti:

  1. BufferWriter dovrebbe solo scrivere ma non leggere il buffer, mentre BufferReader dovrebbe solo leggere il buffer.
  2. BufferWriter e BufferReader dovrebbero essere estensibili (possono essere ereditati)
  3. Un Buffer può essere condiviso da più BufferReader se BufferWriter s
  4. Non voglio che gli altri utilizzino direttamente Buffer (che la logica di lettura / scrittura non è stata separata)

Come dovrei organizzare la struttura della classe in questo caso? O il requisito è contraddittorio rispetto a ciò che viene dato?

    
posta pterodragon 31.12.2015 - 03:45
fonte

2 risposte

2

Non conosco il modo "migliore" dato questi ristretti vincoli, ma ammesso che Buffer abbia indicatori di lettura / scrittura separati e funzioni a sufficienza da solo attraverso la sua interfaccia pubblica, puoi farlo in questo modo:

class BufferReader
{
public:
    explicit BufferReader(Buffer& ibuf): buf(&ibuf) {}
    virtual ~BufferReader() {}

    virtual int read_int() = 0;
    ...

protected:
    Buffer* buf;
};

class TextBufferReader: public BufferReader
{
public:
    explicit TextBufferReader(Buffer& buf): BufferReader(buf) {}        

    int read_int() override
    {
        // return an integer from 'buf' as a text entry
        // using lexical conversion to an integer.
    }
    ...
};

class BinaryBufferReader: public BufferReader
{
public:
    explicit BinaryBufferReader(Buffer& buf): BufferReader(buf) {}        

    int read_int() override
    {
        // return an integer from 'buf' in binary.
    }
    ...
};

... idea simile per BufferWriter (con BufferWriter chiama write_buffer e BufferReader chiama read_next_data ).

In questo caso, sia BufferReader che BufferWriter non sono semplicemente estendibili (in grado di essere ereditati). Loro hanno bisogno di per essere ereditati, poiché questo design li trasforma in classi base astratte. Eppure questo è probabilmente il modo più flessibile per andare (e anche più sicuro dal momento che non è soggetto a tagli agli oggetti).

Potresti anche allentare l'accoppiamento rendendo buf privato e esponendo metodi protetti per leggere e scrivere da un buffer, ad es. al contrario di un membro protetto dei dati.

DIP

Vale la pena notare che questo design viola inevitabilmente il principio di inversione di dipendenza, poiché le astrazioni dipendono da dettagli concreti ( Buffer ) attraverso l'iniezione di dipendenza. Eppure ci sono sempre delle eccezioni alla regola. Se il tuo buffer è sempre una sorta di buffer raw di bit e byte, ad es., E non beneficia minimamente di essere astratto, allora probabilmente è giusto violare il DIP in questo caso poiché l'alternativa potrebbe rendere le cose più difficili da mantenere piuttosto che più facile.

Puntatori intelligenti

Per una progettazione più sicura, puoi utilizzare shared_ptr<Buffer> anziché un puntatore raw, ad es. Sono piuttosto prevenuto nel contesto di handle che non hanno bisogno di possedere memoria poiché lavoro in un campo critico per le prestazioni e preferirei riservare la possibilità di allocare semplicemente Buffer nello stack (specialmente se avere loop critici che allocano i buffer teeny a destra ea sinistra), facendo in modo che il cliente sia responsabile di garantire che Buffer non venga distrutto mentre BufferReader e BufferWriter lo utilizzano. Ma se le prestazioni non sono un obiettivo pressante o se sei disposto ad affrontare l'efficienza a livello di pool di memoria (possibile PITA), è sicuramente molto più sicuro usare shared_ptr<Buffer> qui.

In entrambi i casi, sia attraverso il% sicuro dishared_ptr che gestisce il buffer per te sia la responsabilità a livello di client di gestire Buffer manualmente mentre si usano i puntatori raw, si ottiene la possibilità di condividere un singolo buffer tra più lettori / scrittori in questo modo.

I do not want others to use the Buffer directly (which the read/write logic has not been separated)

Se vuoi forzare questo con un pugno di ferro, un modo è rendere Buffer opaco in questo modo:

// In some public header:
class Buffer;

// Returns a newly-created buffer for use with BufferReader/BufferWriter subtypes.
std::shared_ptr<Buffer> create_buffer(...);

Questo finirà per vedere tutti i tuoi clienti, rendendo impossibile l'accesso alla funzionalità del buffer dall'esterno. In questo caso, in genere sarebbe non sciocco usare shared_ptr , poiché l'opacità qui di questo tipo forward-dichiarato richiederà un'allocazione heap (free store) indipendentemente da un allocatore personalizzato che sia solo un modo per velocizzare le cose.

Quindi le implementazioni BufferReader e BufferWriter possono includere un'intestazione privata che rende visibile solo la definizione di Buffer . In questo caso, shared_ptr<Buffer> diventa quindi un handle opaco che può essere passato solo a BufferReader o BufferWriter . I client non possono fare nulla con Buffer , tranne passare un handle ad esso insieme a lettori e scrittori da usare.

    
risposta data 31.12.2015 - 06:11
fonte
-2

Comprendo il tuo obiettivo di progettazione. Puoi provare a utilizzare il modello di combinazione.

Ti piace questo come segue:

class Buffer {
   public:
      WriterBuffer wb
      ReaderBuffer rb;
 };

 class WriterBuffer{

 };

 class ReaderBuffer{

 };
    
risposta data 31.12.2015 - 04:54
fonte

Leggi altre domande sui tag