99 Bottles of Beer/C++/Object Oriented

From Rosetta Code

Another solution, which in addition correctly handles the grammar. This solution is object-oriented. It is completely overkill for this problem.

Works with: GCC version 4.1.2 20061115 (prerelease) (SUSE Linux)
#include <iostream>
#include <string>
#include <sstream>

namespace bottle_song
{
  // =================================================================

  // ***********************************
  // * Abstract base class for things. *
  // ***********************************

  class thing
  {
  public:
    // return the singular of the thing
    virtual std::string singular() const = 0;

    // return the plural of the thing
    virtual std::string plural() const = 0;

    // we need a virtual destructor, too
    virtual ~thing() {}
  };

  // =================================================================

  // ***************
  // * Containers. *
  // ***************

  // Containers are things which can contain other things. The
  // following class makes any thing into a container. The container
  // class is actually a decorator which makes any thing into a
  // container. Note that the contained thing is actually mutable,
  // even if the container is not. Note that the container can only
  // contain a single thing; if it shall contain several things, make
  // it contain a collection instead.

  class container: public thing
  {
  public:
    // The format gives the name. %self% is replaced by the containing
    // object's name (in proper pluralization), %contained% is
    // replaced by the contained object's name.
    container(std::string fmt, thing const& what, thing const& contained);
    std::string singular() const;
    std::string plural() const;
  private:
    std::string format;
    thing const& self;
    thing const& contained_thing;
    // helper function to replace strings
    static void replace(std::string& str, std::string from, std::string to);
  };

  container::container(std::string fmt,
                       thing const& what,
                       thing const& contained):
    format(fmt),
    self(what),
    contained_thing(contained)
  {
  }

  std::string container::singular() const
  {
    std::string result = format;
    replace(result, "%self%", self.singular());
    replace(result, "%contained%", contained_thing.singular());
    return result;
  }

  std::string container::plural() const
  {
    std::string result = format;
    replace(result, "%self%", self.plural());
    replace(result, "%contained%", contained_thing.singular());
    return result;
  }

  void container::replace(std::string& str, std::string from, std::string to)
  {
    std::string::size_type pos = str.find(from);
    if (pos != std::string::npos)
      str.replace(pos, from.length(), to);
  }
  // =================================================================

  // *********************************
  // * A collection of equal things. *
  // *********************************

  // In the context of this program, a collection of things is again
  // considered a single thing.
  // This is a concrete class.
  class equal_collection: public thing
  {
  public:
    // constructor
    equal_collection(int count, thing const& what);

    // get singular
    std::string singular() const;

    // get plural. This has to be implemented, even if it isn't used,
    // because the inherited version is pure virtual, and not
    // implementing this would make the class abstract.
    std::string plural() const;

    // this just returns whether thwere are still things left to take away.
    bool there_is_some_left();

    // this takes one thing away from the collection. Taking a thing
    // away from an empty collection is undefined behaviour (i.e. not
    // explicitly checked).
    void take_one_away();
  private:
    int count_of_things;
    thing const& type_of_thing;
  };

  // equal_collection constructor
  equal_collection::equal_collection(int count, thing const& what):
    count_of_things(count),
    type_of_thing(what)
  {
  }

  // get singular. The singular of the collection is just the number
  // followed by the thing, proper pluralized. The fact that it's
  // grammatically still a plural form doesn't matter for the problem
  // at hand.
  std::string equal_collection::singular() const
  {
    std::ostringstream oss;
    oss << count_of_things << " ";
    if (count_of_things == 1)
      oss << type_of_thing.singular();
    else
      oss << type_of_thing.plural();
    return oss.str();
  }

  // get plural. For collections, the plural is just "times " followed
  // by the singular. That is 3 collections of 4 bottles each give 3
  // times 4 bottles.
  std::string equal_collection::plural() const
  {
    return "times " + singular();
  }

  // tell if there are still things to take away. There are things to
  // take away if there are more than 0 things.
  bool equal_collection::there_is_some_left()
  {
    return count_of_things > 0;
  }

  // take one thing away from the collection. That is, just decrement
  // the count of things.
  void equal_collection::take_one_away()
  {
    --count_of_things;
  }

  // =================================================================

  // ************
  // * The beer *
  // ************

  class beer: public thing
  {
  public:
    std::string singular() const { return "beer"; }
    std::string plural() const { return "beers"; }
  };

  // =================================================================

  // **************
  // * The bottle *
  // **************

  class bottle: public thing
  {
  public:
    std::string singular() const { return "bottle"; }
    std::string plural() const { return "bottles"; }
  };

  // =================================================================

  // ************
  // * The wall *
  // ************

  class wall: public thing
  {
  public:
    std::string singular() const { return "wall"; }
    std::string plural() const { return "walls"; }
  };

  // =================================================================

  // this is the class for the song.
  class song
  {
  public:
    song(int bottle_count);
    void sing(std::ostream& where); // note: singing the song modifies it!
  private:
    beer beverage;
    bottle drink_source;
    container bottle_of_beer;
    equal_collection collection_of_bottles;
    wall bottle_storage;
    container wall_of_bottles;
  };

  song::song(int bottle_count):
    bottle_of_beer("%self% of %contained%", drink_source, beverage),
    collection_of_bottles(bottle_count, bottle_of_beer),
    wall_of_bottles("%contained% on the %self%",
                    bottle_storage, collection_of_bottles)
  {
  }

  void song::sing(std::ostream& where)
  {
    while (collection_of_bottles.there_is_some_left())
    {
      where << wall_of_bottles.singular() << ".\n"
            << collection_of_bottles.singular() << ".\n"
            << "Take one down, pass it around.\n";
      collection_of_bottles.take_one_away();
      where << wall_of_bottles.singular() << ".\n\n";
    }
  }
}

int main()
{
  bottle_song::song song(100);
  song.sing(std::cout);
  return 0;
}