CSE2305 - Object-Oriented Software
Engineering
Assessment
Develop a design for Pirate Pete's Ship Shop.
NOTE: There is no "right" answer... but some answers will be better than others! Here are some thoughts to inform your own thinking.
* Greg Paperin has kindly provided detail of his possible solution to this assignment.
* Ben Porter has also kindly provided detail of his possible solution to this assignment.
The first part of developing a design is to look for possible classifications
from which classes and objects can be discovered. Most of the items stocked by the shop have a lot in common, so
we can devise a base level abstraction that represents items that it rents. We'll call this class StockItem
. More specialised classes will
inherit from this base class, such as ships, parrots, cannons and sails. (The diagram below has been coloured for clarity. You don't have to colour code your own diagrams.)
StockItem
represents the base level abstraction. Notice how
only the required methods are exposed in its public interface. We assume that
classes like string
,
Percent
and Price
are value objects (see Lecture
Topic 17 for details). We can use the standard library string class. Percentage
and Price
could
be typedef'd to floats as a first step.
Whilst Cannons
and Sails
are both associated with all Ships (in my interpretation of the situation), each Ship type was associated independently with its fittings to show clearly the different numbers of Sails
and Cannons
a particular Ship type can mount. This independent association with different Ship types (cf. Parrot class below) makes the diagram slightly misleading in that it may appear that a particular cannon or sail may be on board 1 Galleon, 1 Schooner AND 1 Canoe simultaneously. To indicate that this is not the case, the relationship "mounts" is noted to be "mutually exclusive" on the diagram.
A Parrot may be associated directly with a ship since the way I interpretted Pirate Pete's specification any ship (regardless of type) may have only single parrot (or not - it might fly away in the heat of battle or it may be fed to the sharks for entertainment).
The Print
function of StockItem
and its sub-classes
is a good example of a polymorphic function – what
it does depends on type. For example, if we are printing a Ship
object (or something derived from it) the
code would be something like this:
ostream& GalleonShip::Print(ostream& s) const
{
s << getItemType() << ": " << shipName << endl;
s << "Guns mounted: " << numberOfCannons<< endl;
s << "Sails on board:" << numberOfSails<< endl;
s << "Is a Parrot fluttering around?" << HasParrot() << endl;
s << "Weekly rental price: " << WeeklyRentalPrice() << endl;
s << "Deposit required: " << DepositPrice() << endl;
return s;
}
Sail
and Cannon
would define a Print
function
with slightly different behaviour. The specific Print
functions for the different types of ship could be redefined to print something specific for each ship class. In this instance, such a refinement might not be necessary.
Looking more closley at the Price
type, it may become important to make Price
a
class in its own right (to aid with expanded options such as rounding, change,
pretty printing, currency conversion, etc.). Price
has
been expanded in the diagram below for this reason (it is still a value object
however).
A number of classes so far need to define methods to read from a stream, write
to a stream and print to a stream. The read/write methods are for saving and
loading objects to a stream (a serialisation mechanim) – something
that forms part of a persistence
mechanism. The Print
method is used to print the object in
a human readable format (which
can be used when printing search matches for example). Because this appears
in many classes it makes sense to factorise these methods into a separate class
and get all the necessary classes to inherit from this class. As we'll need
to input object information from the user, we could also define a get method,
that reads object state in from the user (i.e. prompts to enter values like
a ship's name). The base class, Streamable
, contains only
pure virtual functions (defining an interface but
no implementation
– that is left to subclasses). From Streamable
's point
of view all we know is that the object can be read, written and printed to
a supplied stream. The getObject
method is used to return an object read from
a stream - this will be required to stream StockItem
and its subclasses
from files. Please see Stroustrup section 25.4.1 for more details.
This type of class is known as a mixin (or property) class. It represents a "uses a" rather than an "is a" relationship, even though it uses inheritance (usually multiple inheritance).
The next stage is the database component of the system. We need a database
of StockItems
. We need
to add and delete, search and
rent StockItems
. We also need to be able to move them between a "wharf" and the "sea". There
are many possible solutions. We could use the List
class from assignment
1 or the associative Array
class
from assignment 2, with the key type a string
or UniqueID
and
the data a pointer to a StockItem
. We can also use the idea
of genericity and develop a generic "database" class.
This will effectively be a wrapper class that can wrap around an STL
container class.
Whether or not a specific item is "at sea" or "at wharf" could be represented by an enumerated type stored within each StockItem that is toggled when a ship is rented out from AtWharf to AtSea (and vice versa when the ship is returned). Alternatively, there could be separate containers within the Shop: one storing items at the wharf, the other storing items at sea. I prefer this second approach as I do not wish to add any value to a StockItem that should really be independent of StockItems. Its location is (I think) such a value. The enumerated type would also need to be updated if Pete bought a new wharf to accomodate a new position for his goods... something that would be cumbersome and error-prone. So here is one possible solution...
Above we have a Shop that has independent container classes Sea and Wharf Stock that store objects (one object for each instance of a StockItem) according to their position. These containers are created and destroyed with the Shop, hence the specification of a composition relationship. Now there is one catch here... when a StockItem is rented out it will move from WharfStock, to SeaStock.
Where does the return date for the item get stored? It could be stored in each StockItem. Alternatively, since the return date is only an attribute of SeaStock-contained StockItems, SeaStock could actually be a container for pairs of Date and StockItem objects. A Date object is therefore associated with each StockItem in the SeaStock container. it would also be possible to order the SeaStock list according to return date of the objects it stores. This would ensure that checking the list for overdue items would be faster than if the list was unordered.
How do we know how many of a particular type of StockItem are stored (for example) at the Wharf? In the above solution no value is maintained that keeps track of the number of StockItems or their type at each location. Such a variable could be added at various places: A counter could be kept in the WharfStock and SeaStock objects for each object type stored. A static counter could be kept in each StockItem sub-class keeping track of instances of that class (this could keep track of whether the instances were at Sea or at the Wharf only if each object also stored this information (see above). Alternatively, I take the less rapid but simpler approach of scanning each list and counting up the instances of an object (checked by asking them to return their ItemType string). This would be tedious if Pete had a lot of stock and a running total would need to be implemented that was maintained when stock moved between locations or was added and deleted from the Shop.
...further diagrams and information will be added here shortly...
Last Modified: October 10, 2006