Given an application-wide collection of objects, and many unrelated classes that need frequent access to these objects, what is the best way to provide said access?
Example:
// Object A, stored in collections, used to do useful things
class A
{
...
public:
QString property(const QString& propertyName) {return m_properties.value(propertyName);}
protected:
QHash<QString,QString> m_properties;
}
// Collection class, contains methods to:
// - Access members of collections
// - Add/Remove members from collection
class GlobalCollection
{
public:
// Accessors to collection/collection members
static A* getAs() {return aHash;}
static QHash<QString,A*> getAByKey(const QString& key) {return aHash.value(key);}
static QList<A*> getAsMatchingCriteria(const QString& property, const QString& value)
{
QHash<A*> subsetOfA;
foreach(A* pA, aHash.values())
{
if (pA->property(property) == value)
subsetOfA << pA;
}
return subsetOfA;
}
protected:
QHash<QString,A*> aHash;
}
// Example client class that uses A's to do its job
class Client
{
public:
// This is tied to a button click, and is executed during run-time at the user's whim
void doSomethingNonTrivialWithAs()
{
// Get A* list based on criteria, e.g. "color" == "green"
QList<A*> asWeCareAbout = ???;
// Draw all the "green" A's in a circle holding hands
foreach(A* pA, asWeCareAbout)
{
// Draw a graphical representation of pA
// If pA has "shape" == "square", get a list of all the non-"green" "square" A's and draw them looking on jealously from the shadows
// Else if pA has "shape" == "circle", draw the non-"green" "circles" cheering it on
}
}
}
Assumptions:
Design Requirements and Problems with Other Solutions:
While I'm refactoring legacy code to do this, my main concern is designing the right architecture in the first place. This seems like a very common logical concept, but all the solutions I see fail to address some important aspect of using it for production or have a glaring flaw/tradeoff. Maybe I'm being too picky, but in my experience the right tool for the job has zero drawbacks in that context.
An application architecture describes the patterns and techniques used to design and build an application. The architecture gives you a roadmap and best practices to follow when building an application, so that you end up with a well-structured app. Software design patterns can help you to build an application.
The three-tier architecture includes: Browser (client-side) Presentation layer (server-side) Business/application layer (server-side)
For smaller applications, monolithic architecture is often the best solution.
There is one clean, maintainable and testable solution, but you reject it in your requirements:
Dependency injection looks like an unreasonable burden on calling code (given the layering,) and sacrifices too much clarity for my liking
I'm going to ignore that requirement for now. See the end of my answer if you really really want to avoid dependency injection (which I don't recommend).
The solution needs to promote thread-safety
This screams for a factory-like intermediary: Create a factory that provides access to the collection. The factory can then decide when to return a new collection or an existing one. Make sure clients give the collection back when they are finished, so that you know how many clients are using it.
Now all clients access the collection through the factory. They only see the interface, never the real implementation.
Now that we introduced the factory, clients don't need to know access the collection directly (statically) anymore. However, they still need to get hold on the factory itself.
Make the factory a dependency by injecting it to the constructors of the clients. This design clearly communicates that the clients depend on the factory. It also enables you to switch out the factory during testing, e.g. to replace it with a mock.
Note that using dependency injection does not mean you need to use a DI framework. The important thing it to have a clean, well-defined composition root.
As I already said, this is not recommended. DI is the basis for clean, decoupled designs that emphasize testability.
If you still want to avoid DI, then modify the above design as follows:
Your collections and usage thereof sound a lot like the repository pattern. My design suggestions above are in line with this similarity (e.g. access collections in a narrow scoped way and "give them back"). I think reading about the repository pattern will help you to get your design right.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With