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.