Proper architecture for application-level collections

Given the collection of objects as a whole and the many unrelated classes that need frequent access to these objects, what is the best way to provide this 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 to do its job class Client { public: // This is tied to a button click, and is executed during run-time at the user whim void doSomethingNonTrivialWithAs() { // Get A* list based on criteria, eg "color" == "green" QList<A*> asWeCareAbout = ???; // Draw all the "green" A 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 and draw them looking on jealously from the shadows // Else if pA has "shape" == "circle", draw the non-"green" "circles" cheering it on } } } 

Assumptions:

  • Small, lightweight classes are preferred, so client objects are legions
  • The client object can be in several layers inside the peer GlobalCollection, and the intermediate levels are independent of A * or GlobalCollection
  • It is currently implemented as singleton

Design requirements and problems with other solutions:

  • Injection dependency looks like an unreasonable load on code invocation (considering layering) and sacrifices too much clarity to my liking.
  • I'm not against a static class, not a single, but it's not much better than a singleton
  • The code that modifies the collection is isolated, so I'm not worried about it at this time
  • The solution should help ensure thread safety in GlobalCollection and inside A (given that multiple clients can work with the same A *.) This is currently achieved with a single mutex and excessive blocking, in large part because it is so difficult control access to A.
  • I am trying to iterate according to testability, and the current design of almost every client test requires the correct GlobalCollection setting.
  • In the production code, we have several GlobalCollections (for A, B, C, etc.), so we welcome template solutions.

While I'm refactoring legacy code for this, my main task is to design the right architecture first. This seems like a very common logical concept, but all the solutions that I see cannot solve some important aspect of using this product for production or have a glaring disadvantage / compromise. I may be too picky, but in my experience the right tool for the job has zero flaws in this context.

+7
c ++ design-patterns architecture refactoring legacy-code
source share
1 answer

There is one clean, supported, and verifiable solution, but you reject it in your requirements:

Dependency injection looks like an unreasonable load on code invocation (given the bundle) and sacrifices too much clarity for my liking

I will ignore this requirement now. See the end of my answer if you really want to avoid dependency injection (which I do not recommend).

Designing collection objects

  • Creating a wrapper around the actual collection (as you already know) is a good idea. This gives you complete control over customer interactions with the collection (for example, regarding blocking).
  • Do not put it. Create it so that you can instantiate the collection, use it, and finally delete it. In the end, all collections of the standard library and Qt also work.
  • Enter an interface for the collection object.

Design a collection access mechanism

The solution should help ensure thread safety.

This yells for a factory-like intermediary: create a factory that provides access to the collection. factory can decide when to return a new collection or an existing one. Make sure customers return the collection when they are finished so you know how many customers use it.

Now all clients get access to the collection through the factory. They see the interface, not the actual implementation.

Getting factory link

Now that we have introduced the factory, customers no longer need to know access to the collection directly (statically). However, they still need to grab onto the factory.

Make a factory dependency by injecting it into client constructors. This design clearly states that customers are factory dependent. It also allows you to disable the factory during testing, for example. replace it with the layout.

Note that using dependency injection does not mean that you need to use the DI structure. It is important that it has a clean, well-defined compound root .

Avoidance DI

As I said, this is not recommended. DI is the foundation for clean, decoupled designs that emphasize testability.

If you still want to avoid DI, modify the above construct as follows:

  • Create a singleton that provides access to the factory.
  • Access to the factory through this singleton from all customers.
  • Keep collections and factories as they are, i.e. non-static and unaware of any singleton.

Additional notes

Your collections and their use sound just like a repository template . My design suggestions given above correspond to this similarity (for example, access collections in a narrow space and β€œreturn them”). I think reading about the repository template will help you set up your design correctly.

+4
source share

All Articles