Collections of objects that know about each other

This is a fairly broad question, but for my own understanding, I decided that I would throw it there.

Can anyone recommend projects or, perhaps, even generally accepted design patterns for situations where it would be necessary to know different objects that know about each other?

To use people / community simulations as an analogy, what would be the best pattern when you have X-people objects, where X can dynamically increase / contract, and each person has an opinion or relationship to another object?

A thought outside the programming syntax, I could just have an X to X grid that represents each person’s relationship with each other (like, dislike, not found, etc.). I thought that basically it is implemented in the code as a separate object from each object, which was updated every time a new one was created, but it seemed like an absolutely not elegant solution. Does anyone have any advice?

Secondly, if I have collections of people, each of which has an “inventory” or objects that they carry, I thought I could create an inventory of each person as a member of a class list; this linked list will grow and shrink as objects that a person grows and shrinks.

People’s objects can be easily requested for their objects, which they carry, but I can’t find an effective way to do the opposite; that is, I want to be able to request an item and find which people have this item.

I read this QA: Game objects talking to each other .. but I'm not sure if it is fully applicable to my case.

Can anyone offer any advice?

Thanks!

- R

+4
source share
4 answers

What you are looking for is a relatively simple association.

class Person { struct Relationship { // whatever }; std::map<Person*, Relationship> relationships; public: Person() {} void Meet(Person* other) { // think about it relationships[other] = ...; } ~Person() { // Loop through the map and ensure no dud relationships for(auto& pair : relationships) { pair.first->relationships.erase(this); } } }; 

Regarding the definition of a person who has certain elements, I think that the (relatively) simple decision of the manager should work well, although I hate the calls of the Manager classes.

 class PersonManager { struct Item { ... }; std::set<Item, std::set<Person*>> items; // set for no dupes public: void AddToInventory(Item i, Person* p) { items[i].insert(p); } std::vector<Person*> PeopleWithItem(Item i) { return std::vector<Person*>(items[i].begin(), items[i].end()); } }; 

If you need to model many relationships this way, I would suggest going to the database. However, I doubt that any database can match the efficiency of this code - you can even go to the hash containers and get O (1) for most of these actions.

+1
source

Relational databases were made just for this purpose. If you are not familiar with this, read about it!

You can use a database with multiple tables. One table will be a "person" that contains the basic properties of people plus an identifier.

To model relationships like “met,” create a table called met with two columns that contain the identifiers of two people who met each other.

Databases are highly optimized for queries such as "find all the people who met John." If you do not want to install the client / server database, you can use a file-based database such as SQLite

+1
source

If a relationship is just communication, not an actual lifetime possession, and you do not want everyone to be able to talk to everyone automatically, then proxies are usually a good start. These are just intermediate classes that have a way of communicating with a colleague and provide notifications when the life of each of the parties changes. They are usually unidirectional, but when you connect them and add a little more glue code, they become bidirectional.

So you have a class

 class ColleagueProxy { private: // a pointer to the Colleague // it can be a naked pointer since it does not deal with lifetime Colleague friend_; // a callback for when death comes to the colleague typedef std::function<void (ColleagueProxy *)> DeathHandler; DeathHandler deathCallback_; // an identifier for friends to know me by std::string id_; public: // ctor takes a colleague and a callback for when colleague dies // ctor notifies friend of new proxy following - in case that is useful info ColleagueProxy(Colleague * friend, DeathHandler callback, std::string const& myName) : friend_(friend), deathCallback_(callback) { if (friend) friend_->proxyConnecting(this); } // dtor may notify friend as well ~ColleagueProxy() { if (friend) friend_->proxyLeaving(this); } // the communication interface void sayHi() { if (friend) friend_->sayHi(this); } // ... // my name badge std::string id() { return id_; } // a way for the friend to say goodbye void Goodbye() { deathCallback_(this); } }; 

and then colleagues will store these communication proxies.

 class Colleague { private: std::map<std::string, std::shared_ptr<ColleagueProxy>> friends_; std::vector<std::shared_ptr<ColleagueProxy>> colleaguesWhoConsiderMeAFriend_; void GoodbyeCallback(ColleagueProxy * that) { // search through friends_ and remove that } } 

You can automate part of the cleaning part through the manager class, so you do not duplicate the code if you have different dynamic types. However, if you have one type of colleague, this is not necessary. Another useful reason a manager class might be useful is because it can create peers that can apply restrictions or other rules of visibility.

The point of this type of design is the ability to create a communication mechanism that can be configured for each connection (each proxy object may contain a state that depends on the particular connection). This can be applied to opinions (as in the original question) and many others. You do not need to worry about cyclic dependencies because lifecycle management is not part of the connection (for example, if you just saved the structure with shared_ptr for a friend, where cyclic dependencies can cause problems with orphaned cycles).

If you really wanted everyone to be able to see everyone else without restrictions, then storing the proxy in the Colleague class is not required, and the manager class is preferable. Then all messages occur through the manager, which acts as a collection of proxies. In these cases, perhaps the same manager who manages the Colleague’s lifetime may also play a role as a proxy manager to prevent data duplication and optimize access.

+1
source

The canonical example in the GOF book is to use the Mediator template. An example they give is that a GUI window / dialog (broker) monitors its controls (sometimes called peers) and notifies the controls for relevant events such as window resizing, etc. I believe that this example extends to the ability of the management to request the intermediary information about other controls.

If you want your objects to be able to track specific instances of objects, the Observer pattern (as cbamber85 points out) is useful. But for general information about events occurring in a set of objects, you can consider using an intermediary.

Regarding your specific problem, the Mediator can help track changes in the general population (growth or reduction).

0
source

All Articles