Inheritance or strategy in my circular buffer
https://softwareengineering.stackexchange.com/questions/363063
-
25-01-2021 - |
题
I've looked at some other threads but they don't quite answer my question, I think...
I have a CircularBuffer object which I have separated from a ThreadSafeCircularBuffer object so that the CB logic is separated from the concurrency logic, mostly for a clear separation of logic/function and partly to make testing easier.
I would have thought that a ThreadSafeCircularBuffer is a CircularBuffer. But this leads me to an implementation problem which is represented by a small artificial example below:
#include <iostream>
class A
{
public:
virtual void GetBytesAvailable()
{
std::cout << "GetBytesAvailable() from A\n";
}
virtual void doit()
{
std::cout << "doit() from A\n";
GetBytesAvailable();
}
};
class B: public A
{
public:
virtual void GetBytesAvailable() override
{
std::cout << "GetBytesAvailable() from B\n";
std::cout << "LOCKED\n";
}
virtual void doit() override
{
std::cout << "doit() from B\n";
std::cout << "LOCKED\n";
A::doit();
}
};
int main()
{
B b;
b.doit();
}
The output is this, as one would expect in C++...
doit() from B
LOCKED
doit() from
GetBytesAvailable() from
LOCKED // <-- Oops, this will break if lock not recursive!
Trouble is, I don't want to use a recursive lock.
I could solve this by re-writing A's doit()
method as so:
virtual void doit()
{
std::cout << "doit() from A\n";
A::GetBytesAvailable();
// ^^^
// Make sure we call the non-locking function
}
But it feels a little artificial... firstly, what if the override of GetBytesAvailable()
should be called in a child class (although not in this case, I mean in the general case)? Also, its a bit clunky... class A
shouldn't need to care about protecting against its children like this, I don't think.
So... this leads me to the idea of using a strategy pattern where the ThreadSafeCircularBuffer would create and instance of CircularBuffer or maybe even better be passed a CircularBuffer as a strategy. This is a "has-a" relationship. This would solve my problem but it seems to me like I should really have an "is-a" relationship.
Another possible solution is to have both objects inherit from an interface but then instead of making ThreadSafeCB a child of CB... this way they are both CB's, but the ThreadSafeCB would still have to use a "has-a" relationship under the hood to get the CB functionality.
How I could improve this design / is there is a better way of going about this?
解决方案
Go with the interface and let the ThreadSafeCircularBuffer
have an instance of CircularBuffer
. The main reason for this is that it seems to be the simplest solution, but if you need to rationalize it:
The CircularBuffer
class is a specific implementation of the abstract concept of a circular buffer. It's not really relevant that your ThreadSafeCircularBuffer
is a CircularBuffer
in the sense of that specific implementation.
As for passing the CircularBuffer
in - I don't see a good reason for this, unless you might want to use different implementations of a CB in a thread-safe way. This, of course, would be all the more reason to use an interface.