Reputation: 26365
I have the following classes:
class Stream {};
class FileStream : public Stream {};
class NetworkStream : public Stream {};
Each class has a write()
method (virtual).
I can use a FileStream on two types of media: HDD and SSD. If I'm writing to an HDD, I don't do anything special over what FileStream
already provides. However, if I'm writing so an SSD, there is some logic I need to execute prior to calling write()
. From within FileStream
, I have no idea what the media is I'm writing to. Only the call site knows. I'd like to use decorator here, but decorator is intended to be used with all streams. I only want to extend the functionality of NetworkStream
in certain situations. Is some form of decorator appropriate here? If not, what design pattern should I use? If we assume FileStream::write()
simply flushes the entire internal buffer to the file and saves it to the disk, then the decorator will need to write some metadata to the stream prior to writing it.
I thought of creating a simple decorator class that doesn't use inheritance like so:
class FileStreamDecorator
{
public:
FileStreamDecorator( FileStream& stream ) : m_stream( stream ) {}
void write() {
m_stream << "Some Metadata";
m_stream.write();
}
private:
FileStream& m_stream;
};
And it would be used like so:
FileStream stream;
stream << "Complete file data";
// At this point we know we are writing to SSD, so we must use the decorator
FileStreamDecorator decorator( stream );
decorator.write();
Would this be an appropriate solution? Can anyone think of a better way?
Upvotes: 0
Views: 85
Reputation: 27365
Would this be an appropriate solution? Can anyone think of a better way?
No. You are creating a decorator class and relying on client code to not forget to use it, when required.
If the client code forgets to do the extra steps, the code looks OK (there will be nothing in client code to suggest a decorator should be initialized there).
In one month (or five years for that matter) you will have forgotten about this, or moved on to other projects and whoever maintains this project will have to realize a new object needs to be initialized in the client.
You are better off with a SSDFileStream specialization, that overrides a write() from the base class (the default behavior), calls the base class version internally, then performs any extra steps.
Best implementation I can think of:
class FileStream {
virtual void write();
};
class SSDFileStream: public FileStream {
virtual void write() {
FileStream::write();
write_ssd();
}
protected:
void write_ssd();
};
Aditionally, you could make FileStream abstract, and add a HDDFileStream specialization. If the HDDFileStream detects it is writing on SSD, it could throw an exception. The SSDFileStream probably could do the same if you ask it to write at a HDD path.
This would make client code easy to write correctly, and impossible'* to write incorrectly.
'* Writing buggy/unstable/brittle/ugly code is never impossible, but you can still make it difficult to achieve.
Upvotes: 1