2
\$\begingroup\$

A Person has coordinates and an Item has coordinates as well, and hence both are derived from Entity, which has the coordinates data member. If a person holding an item moves, the coordinates of the item must change to whatever the person's new coordinates are. And if he drops the item, this is no longer true. So I felt that the Observer Pattern should be used here and would like some opinions:

#include <iostream>
#include <string>
#include <list>

struct Coordinates {
    int x, y, z;
    void print() const {std::cout << "(" << x << ", " << y << ", " << z << ")\n";}  
};

class Entity {
    private:
        Coordinates coordinates = {-1,-1,-1};
    public:
        virtual ~Entity() = default;
        const Coordinates& getCoordinates() const {return coordinates;}
        void setCoordinates (const Coordinates& c) {coordinates = c;}
};

class InventoryObserver {
    public:
        virtual void update (const Coordinates&) = 0;
};

class Item : public Entity, public InventoryObserver {
    public:
        virtual void invokeAdd (class Inventory*) = 0;
        virtual void invokeDropped (Inventory*) = 0;
    private:
        virtual void update (const Coordinates& newCoordinates) override {setCoordinates(newCoordinates);}
};

template <typename Derived>
class ItemCRTP : public Item {
    virtual void invokeAdd (Inventory*) override;
    virtual void invokeDropped (Inventory*) override;
};

class Wallet : public ItemCRTP<Wallet> {};

class Keys : public ItemCRTP<Keys> {};

class Inventory {
    private:
        class Person* holder = nullptr;
        Wallet* wallet = nullptr;
        Keys* keys = nullptr;
    public:
        Inventory (Person* person) : holder(person) {}
        Person* getHolder() const {return holder;}
        void addItem (Wallet* w) {registerItem(w);  wallet = w;}
        void addItem (Keys* k) {registerItem(k);  keys = k;}
        Keys* getKeys() const {return keys;}
        Wallet* getWallet() const {return wallet;}
        void print() const {
            if (wallet) {std::cout << "Coordinates of wallet: ";  wallet->getCoordinates().print();}
            if (keys) {std::cout << "Coordinates of keys: ";  wallet->getCoordinates().print();}
        }
        void dropsItem(Wallet* w) {unregisterItem(w);  wallet = nullptr;}
        void dropsItem(Keys* k) {unregisterItem(k);  keys = nullptr;}
    private:
        void registerItem (Item*);
        void unregisterItem (Item*);
};

class InventoryObservable {
    private:
        std::list<InventoryObserver*> observers;  // Observer Pattern
    public:
        void registerObserver (InventoryObserver* observer) {observers.push_back(observer);}
        void unregisterObserver (InventoryObserver* observer) {observers.remove(observer);}
        virtual void notifyObservers() const = 0;
        const std::list<InventoryObserver*>& getObservers() const {return observers;}
};

class Person : public Entity, public InventoryObservable {
    private:
        std::string name;
        Inventory* inventory;
    public:
        Person (const std::string& n) : name(n), inventory (new Inventory(this)) {setCoordinates({10,20,0});}
        std::string getName() const {return name;}
        Inventory* getInventory() const {return inventory;}
        void print() const {std::cout << "Name: " << name << "\nCoordinates: ";  getCoordinates().print();  inventory->print();}
        void movesTo (const Coordinates& newCoordinates) {
            setCoordinates (newCoordinates);
            notifyObservers();
        }
        void addItem (Item* item) const {item->invokeAdd(inventory);}  // Double Dispatch
        void dropItem (Item* item) const {item->invokeDropped(inventory);}  // Double Dispatch
        virtual void notifyObservers() const override {
            for (InventoryObserver* x : getObservers())
                x->update (getCoordinates());
        }
};

void Inventory::registerItem (Item* item) {
    if (holder) {
        item->setCoordinates(holder->getCoordinates());
        holder->registerObserver(item); 
    }
}

void Inventory::unregisterItem (Item* item) {
    if (holder)
        holder->unregisterObserver(item);
}

template <typename Derived>
void ItemCRTP<Derived>::invokeAdd (Inventory* inventory) {
    inventory->addItem (dynamic_cast<Derived*>(this));
}

template <typename Derived>
void ItemCRTP<Derived>::invokeDropped (Inventory* inventory) {
    inventory->dropsItem (dynamic_cast<Derived*>(this));
}

Output:

Name: Bob
Coordinates: (10, 20, 0)
Coordinates of wallet: (10, 20, 0)
Coordinates of keys: (10, 20, 0)

Bob has moved.
Name: Bob
Coordinates: (50, 40, 0)
Coordinates of wallet: (50, 40, 0)
Coordinates of keys: (50, 40, 0)

Bob has dropped his keys and moved.
Name: Bob
Coordinates: (100, 200, 0)
Coordinates of wallet: (100, 200, 0)
Coordinates of Bob's keys: (50, 40, 0)

Bob has dropped his wallet and moved.
Name: Bob
Coordinates: (500, 400, 0)
Coordinates of Bob's wallet: (100, 200, 0)
Coordinates of Bob's keys: (50, 40, 0)

Now let's say that Bob is carrying a gun. The gun will register to him. But should the bullets register to Bob or to the gun? Although the bullet's coordinates should match Bob's coordinates as he moves, if he, say, throws his gun, then the gun will unregister from Bob and have its own coordinates, and then the bullets' coordinates must match the gun's new coordinates, and hence the bullets must register to the gun instead of to Bob, right?

In general, if a container holds a container that holds a container that holds a container ... that holds an item, that item must register to the last container (and each container must register to the previous container), right?

Update: The following was tested to work correctly:

class Gun : public ItemCRTP<Gun>, public InventoryObservable {
    private:
        std::list<Bullet*> bullets;
    public:
        void addBullets (int amount) {
            for (int i = 0; i < amount; i++) {
                Bullet* bullet = new Bullet;
                bullets.push_back (bullet);
                registerObserver (bullet);  // The bullet shall register to the Gun instead of the Person, because the Gun can move independently of the Person.
            }           
        }
    void movesTo (const Coordinates& newCoordinates) {
        setCoordinates (newCoordinates);
        // notifyObservers();  // Don't need this actually (already below).
 // If the gun moves (whether a Person is holding it or not), its bullets' cooordinates must match it.
    }
    private:
        virtual void setCoordinates (const Coordinates& c) override {  // Own version needed.
            Item::setCoordinates(c);
            notifyObservers();  // A chain of notifyObservers() resulting now.
// If the Person holding the Gun moves, it notifies the Gun, which in turn
// notifies the Bullets, so then the Bullets' coordinates match the Person's coordinates.
        }
        virtual void notifyObservers() const override {
            for (InventoryObserver* x : getObservers())
                x->update (getCoordinates());
        }
};

When Bob drops his gun and then he moves, the bullets' coordinates will match the coordinates of the gun rather than Bob. If the gun then moves (somehow), the bullets will still continue to have their coordinates matching the gun (all tested).

\$\endgroup\$
  • \$\begingroup\$ I think observer pattern is overkill. Why not have object linked to a container object. They can then be put in a trunk or on a person. When asked they then ask their container its position. So a helmet in a bag in a chest being carried by a person does not need to wait for 5 events to be processed to update its state (especially if nobody ever asks where it is). When asked for its position the helmet will ask the bag which will ask the trunk which will ask the person. \$\endgroup\$ – Martin York Feb 1 '15 at 18:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.