4
\$\begingroup\$

When putting on a piece of clothing, we need to check for any clothing that is already on the relevant body parts(s). The possible plural means that we should use the Composite Pattern for the body parts, and then check if the existing clothes must be removed or not. Some clothes can go over other clothes, while some cannot. Some covered body parts are subsets, supersets, or intersect with the body part(s) being covered by the clothing currently being handled. For example, if one is wearing a skirt and then desires to put on pants. Should the skirt be removed first? How can we tell? How do we put on shoulder pads if someone is already wearing a coat over a sweater over a tank top? If someone is wearing a gown and then wants to put on pants, what should be done with the gown which intersects with the pants at the hips? Can you put on pants if you are already wearing snowpants? A coat should be allowed to cover anything, right? But what if you are already wearing a space suit? Here is how I handled this, but I do mention some logical bugs that remain and I cannot seem to find a way out of right now. Or maybe the entire design must change.

#include <iostream>
#include <list>
#include <set>
#include <memory>

class Clothing {
    std::string name;
public:
    Clothing (const std::string& n) : name(n) { }
    virtual bool canBeWornOver() const { return true; }  // Any article of clothing can be worn over this.
    virtual bool canWearOverAnything() const { return false; }  // Value true means this can wear over anything except for clothes with absolutelyCannotBeWornOver() == false.
    virtual bool absolutelyCannotBeWornOver() const { return false; }  // 'canBeWornOver()' alone will not suffice, else Coat can be worn over a SpaceSuit, for example.
    std::string getName() const { return name; }
    void removeFromBodyPart() { std::cout << name << " has been removed and dropped to the floor.\n"; }
};

class BodyComponent {
    std::list<BodyComponent*> children;  // For example, Legs has children Thighs, Shins, Knees, Hips, Feet (which itself has Toes, Heel, Sole as children).
    BodyComponent* parent;
    std::list<Clothing*> clothingWorn;  // For example, Foot can have two layers of socks and shoes on.
    std::string name;
public:
    BodyComponent (const std::string& n) : name(n) { }
    std::string getName() const { return name; }
    void add (BodyComponent* component) {
        children.push_back(component);
        component->parent = this;
    }
    void addWithoutChangingParent (BodyComponent* component) { children.push_back(component); }
    bool attemptToPutOn (Clothing* clothing) {
        std::set<Clothing*> alreadyRefusedToRemove;
        std::cout << "\nAttempting to put on " << clothing->getName() << ":\n";
        if (checkIfWillBePutOn(clothing, alreadyRefusedToRemove)) {
            clothingWorn.push_back(clothing);
            // clothing->setIsWornBy(this);
            std::cout << clothing->getName() << " has been put on " << name << ".\n";
            return true;
        }
        else {
            std::cout << clothing->getName() << " was not put on " << name << ".\n";
            return false;
        }   
    }
    bool checkIfWillBePutOn (Clothing* clothing, std::set<Clothing*>& alreadyRefusedToRemove) {
        // First check the children, e.g. before putting on pants decide if the shin guards should be removed first. Note we assume that 'clothing' will be worn over any clothing on the children body parts.
        std::list<Clothing*>::const_iterator it = clothingWorn.cbegin();
        while (it != clothingWorn.cend()) {
            if (alreadyRefusedToRemove.count(*it) != 0) {
                ++it;  // No need to ask again if the user has already refused to remove the clothing *it.  Note that '(*it)->canBeWornOver()' must be true if *it is already in alreadyRefusedToRemove.
                continue;
            }
            char yesNo;
            std::cout << (*it)->getName() << " is covering " << name << ".\n";
            if ( (*it)->canBeWornOver() || (clothing->canWearOverAnything() && !(*it)->absolutelyCannotBeWornOver() ) ) {
            // 'if ( (*it)->canBeWornOver() || clothing->canWearOverAnything() )' is not good enough, as it would allow a Coat to be worn over a SpaceSuit.
                std::cout << "But " << clothing->getName() << " can be worn on top.  Do you wish remove it anyway? (y/n) ";
                std::cin >> yesNo;  
                if (yesNo == 'y') {
                    (*it)->removeFromBodyPart();
                    it = clothingWorn.erase(it);
                }
                else
                    alreadyRefusedToRemove.insert(*it++);
            }
            else {
                std::cout << "It must be removed first.  Do you wish to do so? (y/n) ";
                std::cin >> yesNo;
                if (yesNo == 'n')
                    return false;  // 'clothing' will not be put on at all.  Nothing left to do.
                (*it)->removeFromBodyPart();
                it = clothingWorn.erase(it);  // Removing element while iterating through must be done this way.    
            }
        }
        // Next check the parent, if any. Note we assume that 'clothing' will be worn under (not over) any clothing on 'parent'.  This changes the above algorithm a bit.
        if (!willRemoveNecessaryParentClothing(clothing, alreadyRefusedToRemove))
            return false;
        // Repeat recursively with all the children, if any (Composite Pattern).
        for (BodyComponent* component : children)
            if (!component->checkIfWillBePutOn(clothing, alreadyRefusedToRemove))
                return false;  // One child failing to remove whatever is necessary means that 'clothing' will not be put on.
        return true;  // All children (if any) succeeded, so the entire operation succeeds, and 'clothing' will be put on.
    }
private:
    bool willRemoveNecessaryParentClothing (Clothing* clothing, std::set<Clothing*>& alreadyRefusedToRemove) {
        if (parent) {
            std::list<Clothing*>::const_iterator it = parent->clothingWorn.cbegin();
            while (it != parent->clothingWorn.cend()) {
                if (alreadyRefusedToRemove.count(*it) != 0) {
                    ++it;  // No need to ask again if the user has already refused to remove the clothing *it.  Note that '(*it)->canBeWornOver()' must be true if *it is already in alreadyRefusedToRemove.
                    continue;
                }
                char yesNo;
                std::cout << (*it)->getName() << " is covering " << name << ".\n";
                // Since we assume that 'clothing' will be worn under (not over) any clothing on 'parent', this if condition is changed a bit.
                if ( clothing->canBeWornOver() || ((*it)->canWearOverAnything() && !clothing->absolutelyCannotBeWornOver() ) ) {
                    std::cout << "But " << clothing->getName() << " can be worn underneath.  Do you wish remove it anyway? (y/n) ";
                    std::cin >> yesNo;  
                    if (yesNo == 'y') {
                        (*it)->removeFromBodyPart();
                        it = clothingWorn.erase(it);
                    }
                    else
                        alreadyRefusedToRemove.insert(*it++);
                }
                else {
                    std::cout << "It must be removed first.  Do you wish to do so? (y/n) ";
                    std::cin >> yesNo;
                    if (yesNo == 'n')
                        return false;  // 'clothing' will not be put on at all.  Nothing left to do.
                    (*it)->removeFromBodyPart();
                    it = clothingWorn.erase(it);  // Removing element while iterating through must be done this way.    
                }
            }
            return parent->willRemoveNecessaryParentClothing(clothing, alreadyRefusedToRemove);  // Repeat recursively if 'parent' also has a parent.
        }
        return true;
    }
};

class Legs : public BodyComponent { public: using BodyComponent::BodyComponent; };
class LegsExceptFeet : public BodyComponent { public: using BodyComponent::BodyComponent; };
class Shins : public BodyComponent { public: using BodyComponent::BodyComponent; };
class Hips : public BodyComponent { public: using BodyComponent::BodyComponent; };
class Thighs : public BodyComponent { public: using BodyComponent::BodyComponent; };
class Torso : public BodyComponent { public: using BodyComponent::BodyComponent; };
class TorsoAndLegsExceptFeet : public BodyComponent { public: using BodyComponent::BodyComponent; };
class EntireBody : public BodyComponent { public: using BodyComponent::BodyComponent; };

class Pants : public Clothing {
public:
    using Clothing::Clothing;
};

class SnowPants : public Pants {
public:
    using Pants::Pants;
    virtual bool canBeWornOver() const override { return false; }  // Cannot have something, likes Pants, worn on top because it is too big.
};

class Underwear : public Clothing { public: using Clothing::Clothing; };
class ShinGuards : public Clothing { public: using Clothing::Clothing; };

class Coat : public Clothing {
    public: using Clothing::Clothing;
    virtual bool canBeWornOver() const override { return false; }
    virtual bool canWearOverAnything() const override { return true; }
};

class FullLengthCoat : public Coat {
    using Coat::Coat;
};

class ThreeQuarterCoat : public Coat {
    using Coat::Coat;
};

class SpaceSuit : public Clothing {
    public: using Clothing::Clothing;
    virtual bool canBeWornOver() const override { return false; }
    virtual bool absolutelyCannotBeWornOver() const override { return true; }
    virtual bool canWearOverAnything() const override { return true; }
};

class Person {
    struct Anatomy {
        Person* owner;
        EntireBody* entireBody = new EntireBody(owner->name + "'s entire body");
        Legs* legs = new Legs(owner->name + "'s legs");  // Let this be for both legs for simplicity.
        LegsExceptFeet* legsExceptFeet = new LegsExceptFeet(owner->name + "'s legs except feet");
        Shins* shins = new Shins(owner->name + "'s shins");  // Let this be for both shins for simplicity.
        Thighs* thighs = new Thighs(owner->name + "'s thighs");  // Let this be for both thighs for simplicity.
        Hips* hips = new Hips(owner->name + "'s hips");
        TorsoAndLegsExceptFeet* torsoAndLegsExceptFeet = new TorsoAndLegsExceptFeet(owner->name + "'s torso and legs");
        Torso* torso = new Torso(owner->name + "'s torso");
        Anatomy (Person* person) : owner(person) {
            legsExceptFeet->add(shins);
            legsExceptFeet->add(thighs);
            legsExceptFeet->add(hips);
            torsoAndLegsExceptFeet->add(legsExceptFeet);
            torsoAndLegsExceptFeet->add(torso);
            // legs->add(legsExceptFeet); legs-->add(feet);
            // feet->add(heel);  feet->add(toes);
            // torso->add(arms);
            // arms->add(hands);
            // hands->add(palm);  hands->add(fingers); etc...
            entireBody->add(torsoAndLegsExceptFeet);        
        }
    };
    std::string name;
    Anatomy* anatomy;   
public:
    Person (const std::string& n) : name(n), anatomy(new Anatomy(this)) { }
    bool attemptToPutOn (Pants* pants) { return anatomy->legsExceptFeet->attemptToPutOn(pants); }
    bool attemptToPutOn (Underwear* underwear) { return anatomy->hips->attemptToPutOn(underwear); }
    bool attemptToPutOn (ShinGuards* shinGuards) { return anatomy->shins->attemptToPutOn(shinGuards); }
    bool attemptToPutOn (FullLengthCoat* coat) { return anatomy->torsoAndLegsExceptFeet->attemptToPutOn(coat); }
    bool attemptToPutOn (SpaceSuit* spaceSuit) { return anatomy->entireBody->attemptToPutOn(spaceSuit); }
    bool attemptToPutOn (ThreeQuarterCoat* threeQuarterCoat) {
        BodyComponent* torsoHipsThighs = new BodyComponent(name + "'s body under part under " + threeQuarterCoat->getName());  // Using a smart pointer causes a crash down the road that I don't understand.
        torsoHipsThighs->addWithoutChangingParent(anatomy->torso);
        torsoHipsThighs->addWithoutChangingParent(anatomy->hips);
        torsoHipsThighs->addWithoutChangingParent(anatomy->thighs);
        return torsoHipsThighs->attemptToPutOn(threeQuarterCoat);  // But how to reuse 'torsoHipsThighs' if, for example, another ThreeQuarterCoat is to be put on afterwards?  Store it in 'anatomy'?
    }
};

int main() {
    Person person("Bob");
    Pants pants("pants");
    SnowPants snowPants("snow pants");
    Underwear underwear("underwear");
    ShinGuards shinGuards("shin guards");
    FullLengthCoat fullLengthCoat("full length coat");  
    SpaceSuit spaceSuit("space suit");
    ThreeQuarterCoat threeQuarterCoat("red coat that covers down to knees"), threeQuarterCoat2("blue coat that covers down to knees");

    person.attemptToPutOn(&underwear);
    person.attemptToPutOn(&shinGuards);
    person.attemptToPutOn(&snowPants);
    person.attemptToPutOn(&threeQuarterCoat);
    person.attemptToPutOn(&threeQuarterCoat2);
    person.attemptToPutOn(&pants);
    person.attemptToPutOn(&snowPants);
    person.attemptToPutOn(&fullLengthCoat);  // fullLengthCoat does not check the threeQuarterCoat worn because 'torsoHipsThighs' is not being checked.
    person.attemptToPutOn(&spaceSuit);
    person.attemptToPutOn(&spaceSuit);
    person.attemptToPutOn(&fullLengthCoat);
}

Sample output:

Attempting to put on underwear:
underwear has been put on Bob's hips.

Attempting to put on shin guards:
shin guards has been put on Bob's shins.

Attempting to put on snow pants:
shin guards is covering Bob's shins.
But snow pants can be worn on top.  Do you wish remove it anyway? (y/n) n
underwear is covering Bob's hips.
But snow pants can be worn on top.  Do you wish remove it anyway? (y/n) n
snow pants has been put on Bob's legs except feet.

Attempting to put on red coat that covers down to knees:
underwear is covering Bob's hips.
But red coat that covers down to knees can be worn on top.  Do you wish remove it anyway? (y/n) n
snow pants is covering Bob's hips.
It must be removed first.  Do you wish to do so? (y/n) y
snow pants has been removed and dropped to the floor.
red coat that covers down to knees has been put on Bob's body under part under red coat that covers down to knees.

Attempting to put on blue coat that covers down to knees:
underwear is covering Bob's hips.
But blue coat that covers down to knees can be worn on top.  Do you wish remove it anyway? (y/n) n
blue coat that covers down to knees has been put on Bob's body under part under blue coat that covers down to knees.  // *** Bug. The already worn ThreeQuarterCoat is not checked because the previous 'torsoHipsThighs' is not being used anymore.

Attempting to put on pants:
shin guards is covering Bob's shins.
But pants can be worn on top.  Do you wish remove it anyway? (y/n) n
underwear is covering Bob's hips.
But pants can be worn on top.  Do you wish remove it anyway? (y/n) n
pants has been put on Bob's legs except feet.

Attempting to put on snow pants:
pants is covering Bob's legs except feet.
But snow pants can be worn on top.  Do you wish remove it anyway? (y/n) n
shin guards is covering Bob's shins.
But snow pants can be worn on top.  Do you wish remove it anyway? (y/n) n
underwear is covering Bob's hips.
But snow pants can be worn on top.  Do you wish remove it anyway? (y/n) n
snow pants has been put on Bob's legs except feet.

Attempting to put on full length coat:
pants is covering Bob's legs except feet.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
snow pants is covering Bob's legs except feet.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
shin guards is covering Bob's shins.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
underwear is covering Bob's hips.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
full length coat has been put on Bob's torso and legs.

Attempting to put on space suit:
full length coat is covering Bob's torso and legs.
But space suit can be worn on top.  Do you wish remove it anyway? (y/n) n
pants is covering Bob's legs except feet.
But space suit can be worn on top.  Do you wish remove it anyway? (y/n) n
snow pants is covering Bob's legs except feet.
But space suit can be worn on top.  Do you wish remove it anyway? (y/n) n
shin guards is covering Bob's shins.
But space suit can be worn on top.  Do you wish remove it anyway? (y/n) y
shin guards has been removed and dropped to the floor.
underwear is covering Bob's hips.
But space suit can be worn on top.  Do you wish remove it anyway? (y/n) n
space suit has been put on Bob's entire body.

Attempting to put on space suit:
space suit is covering Bob's entire body.
It must be removed first.  Do you wish to do so? (y/n) n
space suit was not put on Bob's entire body.

Attempting to put on full length coat:
full length coat is covering Bob's torso and legs.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
space suit is covering Bob's torso and legs.
But full length coat can be worn underneath.  Do you wish remove it anyway? (y/n) n
pants is covering Bob's legs except feet.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
snow pants is covering Bob's legs except feet.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
underwear is covering Bob's hips.
But full length coat can be worn on top.  Do you wish remove it anyway? (y/n) n
full length coat has been put on Bob's torso and legs.

Note: To follow the Open-Closed Principle, all the Person::attemptToPutOn overloads should be moved out of Person and into some Visitor class. I just didn't bother to, in order to keep this more concise.

\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

Memory leaks

Your code is full of memory leaks since you call new but never call delete. Instead of doing manual memory management, consider using smart pointers such as std::unique_ptr in combination with std::make_unique() for all owning pointers, which in your case is everything where new is used in Anatomy, and anatomy itself.

But even better is not using pointers there at all! You can just write:

class Person {
    struct Anatomy {
         Person* owner;
         EntireBody entireBody {owner->name + "'s entire body"};
         …
         TorsoAndLegsExceptFeet torsoAndLegsExceptFeet {owner->name + "'s torso and legs"};

         Anatomy (Person& person): owner(person) {
             …
             entireBody.add(&torsoAndLegsExceptFeet);
         }
    }
         
    std::string name;
    Anatomy anatomy {this};

public:
    Person (const std::string& n): name(n) {}
    bool attemptToPutOn (SpaceSuit* spaceSuit) {
        return anatomy.entireBody.attemptToPutOn(spacesuit);
    }
    …
};

Use std::vector instead of std::list

std::vector uses less space and is more efficient here than std::list. The reason is that std::list allocates a new piece of memory for every item you add, and those pieces might not be allocated sequentially or even close together in memory. std::vector keeps everything in a single array, so for the rather small lists of pointers you have it is more optimal.

Separate the user interface from the logic

You are printing things to the console and asking questions inside member functions of BodyComponent and Clothing. This is considered bad practice, because it makes it hard to restructure your code later on. Think what would happen is you changed your program from being a terminal application to having a GUI. Then you would have to print and read responses in a completely different way. If the user interaction part was not in Clothing and BodyComponent at all, those classes would not have to change. Try to change your code so all user interaction is done outside those classes, and if ncessary change/add member functions of Clothing and BodyComponent that provide the user interface with the necessary information it needs.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ If using vectors in place of lists, it's worth using the erase-remove idiom rather than individually erasing elements, since erase(iterator) has different complexity requirements in the different containers. \$\endgroup\$ Commented Dec 10, 2022 at 11:38
  • \$\begingroup\$ Good point. Maybe it's also possible to use std::erase() with a predicate instead of the while-loop. \$\endgroup\$
    – G. Sliepen
    Commented Dec 10, 2022 at 12:23
  • \$\begingroup\$ Yes, I think that's a prerequisite for erase-remove. \$\endgroup\$ Commented Dec 10, 2022 at 12:45
5
\$\begingroup\$

First impressions: the long lines make the code less readable. Use more line-breaks to help keep it comprehensible.


We're not very robust when reading input:

            std::cout << "It must be removed first.  Do you wish to do so? (y/n) ";
            std::cin >> yesNo;
            if (yesNo == 'n')

Consider writing a function to ask the question and read a reply, checking that std::cin is good afterwards¹ and that the answer is one of the allowed choices.


Class Clothing should have a virtual destructor:

    Clothing (std::string name)
        : name{std::move(name)}
    { }
    virtual ~Clothing = default;

There's no need to write both virtual and override, since non-virtual methods cannot be overridden. override alone is fine.


I don't see why checkIfWillBePutOn() needs to be public. Its name is a bit misleading too, as it does more than just check. Users might expect such a function to be const, but this one actually removes clothing sometimes.


Class Person owns a bare pointer to an Anatomy. This is leaked because the destructor does not delete it. The default copy constructor and assignment will cause the copies to share the same Anatomy, which is clearly undesirable.

All these problems can be fixed by having an Anatomy object as member, rather than a pointer. (If there's really a good reason to have a pointer, use a smart pointer instead of a raw one).


We have more memory leaks. A simple Valgrind run with the included main() identifies these allocations that are never released:

    Legs* legs = new Legs(owner->name + "'s legs");
    LegsExceptFeet* legsExceptFeet = new LegsExceptFeet(owner->name + "'s legs except feet");
    Shins* shins = new Shins(owner->name + "'s shins");
    Thighs* thighs = new Thighs(owner->name + "'s thighs");
    Hips* hips = new Hips(owner->name + "'s hips");
    TorsoAndLegsExceptFeet* torsoAndLegsExceptFeet = new TorsoAndLegsExceptFeet(owner->name + "'s torso and legs");
    Torso* torso = new Torso(owner->name + "'s torso");
    BodyComponent* torsoHipsThighs = new BodyComponent(name + "'s body under part under " + threeQuarterCoat->getName());

It looks like these could all be objects rather than pointers.


There's a lot of use of raw pointers here that I would expect to be references (this goes with the unnecessary use of new above). All the arguments to functions should be references, and the elements of the containers ought to be std::reference_wrappers.


¹ Or set the stream to throw exceptions at the program start-up: std::cin.exceptions(std::ios::badbit|std::ios::failbit).


Modified code

This version fixes all the compilation and Valgrind warnings.

#include <iostream>
#include <functional>
#include <list>
#include <set>
#include <string>
#include <string_view>
#include <utility>


static bool ask_yn(std::string_view prompt)
{
    for (;;) {
        std::cout << prompt << " (y/n) ";
        char yesNo;
        if (std::cin >> yesNo) {
            switch (yesNo) {
            case 'y': case 'Y': return true;
            case 'n': case 'N': return false;
            }
        } else {
            // input failed; assume no
            std::cout << '\n';
            return false;
        }
    }
}

class Clothing
{
    std::string name;

public:
    Clothing (std::string name)
        : name{std::move(name)}
    { }
    virtual ~Clothing() = default;

    virtual bool canBeWornOver() const {
        // Any article of clothing can be worn over this.
        return true;
    }
    virtual bool canWearOverAnything() const {
        // Value true means this can wear over anything except for
        // clothes with absolutelyCannotBeWornOver() == false.
        return false;
    }
    virtual bool absolutelyCannotBeWornOver() const {
        // 'canBeWornOver()' alone will not suffice, else Coat can be
        // worn over a SpaceSuit, for example.
        return false;
    }
    const std::string& getName() const {
        return name;
    }
    void removeFromBodyPart() {
        std::cout << name << " has been removed and dropped to the floor.\n";
    }

    auto operator<=>(const Clothing& other) const {
        // compare by identity
        return std::compare_three_way{}(this, &other);
    }
};

// Standard library ought to provide this, I think
template<typename T>
auto operator<=>(std::reference_wrapper<T> a,
                 std::reference_wrapper<T> b)
{
    return a.get() <=> b.get();
}

using ClothingPile = std::set<std::reference_wrapper<const Clothing>>;

class BodyComponent
{
     // For example, Legs has children Thighs, Shins, Knees, Hips,
     // Feet (which itself has Toes, Heel, Sole as children).
    std::list<std::reference_wrapper<BodyComponent>> children = {};
    BodyComponent* parent = nullptr;

    // For example, Foot can have two layers of socks and shoes on.
    std::list<std::reference_wrapper<Clothing>> clothingWorn = {};
    std::string name;

public:
    BodyComponent(std::string name)
        : name{std::move(name)}
    { }
    BodyComponent(const BodyComponent&) = default;
    BodyComponent(BodyComponent&&) = default;
    BodyComponent& operator=(const BodyComponent&) = default;
    BodyComponent& operator=(BodyComponent&&) = default;

    const std::string& getName() const {
        return name;
    }

    void add (BodyComponent& component) {
        children.push_back(component);
        component.parent = this;
    }

    void addWithoutChangingParent(BodyComponent& component) {
        children.push_back(component);
    }

    bool attemptToPutOn(Clothing& clothing) {
        ClothingPile alreadyRefusedToRemove;
        std::cout << "\nAttempting to put on " << clothing.getName() << ":\n";
        if (checkIfWillBePutOn(clothing, alreadyRefusedToRemove)) {
            clothingWorn.push_back(clothing);
            // clothing->setIsWornBy(this);
            std::cout << clothing.getName() << " has been put on " << name << ".\n";
            return true;
        }
        else {
            std::cout << clothing.getName() << " was not put on " << name << ".\n";
            return false;
        }
    }

private:
    bool checkIfWillBePutOn(const Clothing& clothing, ClothingPile& alreadyRefusedToRemove) {
        // First check the children, e.g. before putting on pants
        // decide if the shin guards should be removed first. Note we
        // assume that 'clothing' will be worn over any clothing on
        // the children body parts.
        for (auto it = clothingWorn.cbegin();  it != clothingWorn.cend();  ) {
            auto& item = it->get();
            if (alreadyRefusedToRemove.count(item) != 0) {
                // No need to ask again if the user has already
                // refused to remove the clothing *it.  Note that
                // '(*it)->canBeWornOver()' must be true if *it is
                // already in alreadyRefusedToRemove.
                ++it;
                continue;
            }

            std::cout << item.getName() << " is covering " << name << ".\n";
            if ( item.canBeWornOver() || (clothing.canWearOverAnything() && !item.absolutelyCannotBeWornOver() ) ) {
                // 'if ( itemcanBeWornOver() ||
                // clothing.canWearOverAnything() )' is not good enough,
                // as it would allow a Coat to be worn over a SpaceSuit.
                std::cout << "But " << clothing.getName()
                          << " can be worn on top.  ";
                if (ask_yn("Do you wish remove it anyway?")) {
                    item.removeFromBodyPart();
                    it = clothingWorn.erase(it);
                } else {
                    alreadyRefusedToRemove.insert(item);
                    ++it;
                }
            }
            else {
                if (!ask_yn("It must be removed first.  Do you wish to do so?"))
                    // 'clothing' will not be put on at all.  Nothing left to do.
                    return false;
                item.removeFromBodyPart();
                it = clothingWorn.erase(it);  // Removing element while iterating through must be done this way.
            }
        }
        // Next check the parent, if any. Note we assume that 'clothing'
        // will be worn under (not over) any clothing on 'parent'.  This
        // changes the above algorithm a bit.
        if (!willRemoveNecessaryParentClothing(clothing, alreadyRefusedToRemove)) {
            return false;
        }
        // Repeat recursively with all the children, if any (Composite Pattern).
        for (BodyComponent& component : children) {
            if (!component.checkIfWillBePutOn(clothing, alreadyRefusedToRemove)) {
                // One child failing to remove whatever is necessary
                // means that 'clothing' will not be put on.
                return false;
            }
        }
        // All children (if any) succeeded, so the entire operation
        // succeeds, and 'clothing' will be put on.
        return true;
    }

    bool willRemoveNecessaryParentClothing(const Clothing& clothing, ClothingPile& alreadyRefusedToRemove) {
        if (!parent) {
            return true;
        }
        auto &worn = parent->clothingWorn;
        for (auto it = worn.cbegin();  it != worn.cend();  ) {
            auto& item = it->get();
            if (alreadyRefusedToRemove.count(item) != 0) {
                // No need to ask again if the user has already
                // refused to remove the clothing *it.  Note that
                // 'itemcanBeWornOver()' must be true if *it is
                // already in alreadyRefusedToRemove.
                ++it;
                continue;
            }

            std::cout << item.getName() << " is covering " << name << ".\n";
            // Since we assume that 'clothing' will be worn
            // under (not over) any clothing on 'parent', this
            // if condition is changed a bit.
            if (clothing.canBeWornOver() || (item.canWearOverAnything() && !clothing.absolutelyCannotBeWornOver())) {
                std::cout << "But " << clothing.getName()
                          << " can be worn underneath.  ";
                if (ask_yn("Do you wish remove it anyway?")) {
                    item.removeFromBodyPart();
                    it = clothingWorn.erase(it);
                } else {
                    alreadyRefusedToRemove.insert(item);
                    ++it;
                }
            }
            else {
                if (!ask_yn("It must be removed first.  Do you wish to do so?")) {
                    // 'clothing' will not be put on at all.
                    // Nothing left to do.
                    return false;
                }
                item.removeFromBodyPart();
                // Removing element while iterating through must
                // be done this way.
                it = clothingWorn.erase(it);
            }
        }
        // Repeat recursively if 'parent' also has a parent.
        return parent->willRemoveNecessaryParentClothing(clothing, alreadyRefusedToRemove);
    }
};

struct Legs : BodyComponent { using BodyComponent::BodyComponent; };
struct LegsExceptFeet : BodyComponent { using BodyComponent::BodyComponent; };
struct Shins : BodyComponent { using BodyComponent::BodyComponent; };
struct Hips : BodyComponent { using BodyComponent::BodyComponent; };
struct Thighs : BodyComponent { using BodyComponent::BodyComponent; };
struct Torso : BodyComponent { using BodyComponent::BodyComponent; };
struct TorsoAndLegsExceptFeet : BodyComponent { using BodyComponent::BodyComponent; };
struct EntireBody : BodyComponent { using BodyComponent::BodyComponent; };

struct Pants : Clothing
{
public:
    using Clothing::Clothing;
};

struct SnowPants : Pants
{
public:
    using Pants::Pants;
    // Cannot have something like Pants, worn on top because it is too
    // big.
    bool canBeWornOver() const override { return false; }
};

struct Underwear : Clothing { using Clothing::Clothing; };
struct ShinGuards : Clothing { using Clothing::Clothing; };

struct Coat : Clothing
{
    using Clothing::Clothing;
    bool canBeWornOver() const override { return false; }
    bool canWearOverAnything() const override { return true; }
};

struct FullLengthCoat : Coat
{
    using Coat::Coat;
};

struct ThreeQuarterCoat : Coat
{
    using Coat::Coat;
};

struct SpaceSuit : Clothing
{
    using Clothing::Clothing;
    bool canBeWornOver() const override { return false; }
    bool absolutelyCannotBeWornOver() const override { return true; }
    bool canWearOverAnything() const override { return true; }
};

class Person {
    struct Anatomy {
        Person& owner;
        EntireBody entireBody{owner.name + "'s entire body"};
        Legs legs{owner.name + "'s legs"};  // Let this be for both legs for simplicity.
        LegsExceptFeet legsExceptFeet{owner.name + "'s legs except feet"};
        Shins shins{owner.name + "'s shins"};  // Let this be for both shins for simplicity.
        Thighs thighs{owner.name + "'s thighs"};  // Let this be for both thighs for simplicity.
        Hips hips{owner.name + "'s hips"};
        TorsoAndLegsExceptFeet torsoAndLegsExceptFeet{owner.name + "'s torso and legs"};
        Torso torso{owner.name + "'s torso"};
        Anatomy (Person& person) : owner(person) {
            legsExceptFeet.add(shins);
            legsExceptFeet.add(thighs);
            legsExceptFeet.add(hips);
            torsoAndLegsExceptFeet.add(legsExceptFeet);
            torsoAndLegsExceptFeet.add(torso);
            // legs.add(legsExceptFeet); legs.add(feet);
            // feet.add(heel);  feet.add(toes);
            // torso.add(arms);
            // arms.add(hands);
            // hands.add(palm);  hands.add(fingers); etc...
            entireBody.add(torsoAndLegsExceptFeet);
        }
    };
    std::string name;
    Anatomy anatomy{*this};
public:
    Person(std::string name)
        : name{std::move(name)}
    { }

    bool attemptToPutOn(Pants& pants) { return anatomy.legsExceptFeet.attemptToPutOn(pants); }
    bool attemptToPutOn(Underwear& underwear) { return anatomy.hips.attemptToPutOn(underwear); }
    bool attemptToPutOn(ShinGuards& shinGuards) { return anatomy.shins.attemptToPutOn(shinGuards); }
    bool attemptToPutOn(FullLengthCoat& coat) { return anatomy.torsoAndLegsExceptFeet.attemptToPutOn(coat); }
    bool attemptToPutOn(SpaceSuit& spaceSuit) { return anatomy.entireBody.attemptToPutOn(spaceSuit); }

    bool attemptToPutOn(ThreeQuarterCoat& threeQuarterCoat) {
        BodyComponent torsoHipsThighs(name + "'s body under part under " + threeQuarterCoat.getName());
        torsoHipsThighs.addWithoutChangingParent(anatomy.torso);
        torsoHipsThighs.addWithoutChangingParent(anatomy.hips);
        torsoHipsThighs.addWithoutChangingParent(anatomy.thighs);
        return torsoHipsThighs.attemptToPutOn(threeQuarterCoat);
    }
};

int main() {
    Person person{"Bob"};
    Pants pants{"pants"};
    SnowPants snowPants{"snow pants"};
    Underwear underwear{"underwear"};
    ShinGuards shinGuards{"shin guards"};
    FullLengthCoat fullLengthCoat{"full length coat"};
    SpaceSuit spaceSuit{"space suit"};
    ThreeQuarterCoat redThreeQuarterCoat{"red coat that covers down to knees"};
    ThreeQuarterCoat blueThreeQuarterCoat{"blue coat that covers down to knees"};

    person.attemptToPutOn(underwear);
    person.attemptToPutOn(shinGuards);
    person.attemptToPutOn(snowPants);
    person.attemptToPutOn(redThreeQuarterCoat);
    person.attemptToPutOn(blueThreeQuarterCoat);
    person.attemptToPutOn(pants);
    person.attemptToPutOn(snowPants);
    // fullLengthCoat does not check the threeQuarterCoat worn because
    // 'torsoHipsThighs' is not being checked.
    person.attemptToPutOn(fullLengthCoat);
    person.attemptToPutOn(spaceSuit);
    person.attemptToPutOn(spaceSuit);
    person.attemptToPutOn(fullLengthCoat);
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.