Algorithms and OOD (CSC 207 2014F) : Assignments
Primary: [Front Door] [Schedule] - [Academic Honesty] [Disabilities] [Email] - [Learning Outcomes] [FAQ] [Teaching & Learning] [Grading] [Rubric] - [Calendar]
Current: [Assignment] [EBoard] [Lab] [Outline] [Reading]
Sections: [Assignments] [EBoards] [Examples] [Handouts] [Labs] [Outlines] [Readings]
Reference: [Student-Curated Resources] [Java 8 API] [Java 8 Tutorials] [Code Conventions]
Related Courses: [CSC 152 2006S (Rebelsky)] [CSC 207 2014S (Rebelsky)] [CSC 207 2014F (Walker)] [CSC 207 2011S (Weinman)]
Misc: [Submit Questions] - [SamR] [Glimmer Labs] [CS@Grinnell] [Grinnell] - [Issue Tracker (Course)] [Issue Tracker (Textbook)]
This assignment is currently in draft form.
Due: 10:30 p.m., Tuesday, 30 September 2014
Summary: In this assignment, you will explore aspects of object-oriented design, particular some aspects of polymorphism and inheritance.
Purposes: To help you think about polymorphism and inheritance. the Java API. To improve your skills at string processing (in Java and beyond).
Collaboration: You should do the assignment alone. You may discuss this assignment with anyone, provided you credit such discussions when you submit the assignment. Note that students in the other section may not be able to discuss the grocery cart problems because of different policies in that course. Note also that discussing a problem is not the same as writing code together - You may consider approaches to a problem, help debug each other's code, and even look at other people's code for inspiration, but you should not develop code together.
Wrapper (Prologue): Individually read through this assignment and make sure that you understand what is required. Then use the form available at http://bit.ly/207hw6pro to indicate (a) how long you think this assignment will take and (b) what you think will be the most challenging aspect of this assignment.
Wrapper (Epilogue): When you are done with the assignment, fill out the form available at http://bit.ly/207hw6epi to indicate (a) how long the assignment took, (b) what the most challenging part of the assignment was, and (c) something important you learned from doing the assignment. If you find that the assignment took much less or much more time than you expected, also include (d) a note as to what might have led to that difference.
Submitting:
Please put all of your work in a GitHub repository named
csc207-hw6
. Email the address of that
repository to
grader-207-01@cs.grinnell.edu. Please use a subject of “CSC207
2014F Assignment 6 (Your Name)”.
Warning: So that this assignment is a learning experience for everyone, we may spend class time publicly critiquing your work.
a. Create a new Eclipse project for this assignment. You can name the project whatever you like, provided it's not in bad taste.
b. Create a new package named
edu.grinnell.csc207.username.layout
.
You will use this package on parts A and B of the assignment.
c. Copy the various classes from the recent lab on polymorphism into that package.
d. Create a new package named com.farevee.groceries
.
You will use this package in part C of the assignment.
e. Create a new package named com.farevee.shopping
.
You will use this package in part D of the assignment.
f. Create a new package named com.farevee.tests
.
You will use this package for your experiments and tests.
In the reading
and lab on polymorphism,
you explored a number of related classes that permitted you
to build interesting two-dimensional layouts of characters.
You were able to combine and recombine text objects because of
Java's support of polymorphism. That is, if
we treat every combination of text as an object that implements the
TextBlock
interface, then we can combine these objects
arbitrarily.
The textual layout example also had a hidden agenda: To help you think about problems in a more object-oriented fashion. In particular, most beginning programmers, when asked to do some form of textual composition, focus on the methods that would compose pieces of text. In this example, we built objects that represent those composed pieces of text. The advantages of using objects included the ability to reuse a composed piece of text and a better ability to take advantage of polymorphism.
However, we left some aspects of the example unexplored in both the reading and the lab. Let's begin by building some new composition classes.
a. Write a class, Grid
, which implements
TextBlock
and represents an w-by-h grid of a single
character. You should provide one constructor, Grid(int width,
int height, char ch)
. The width()
method of
the object you construct should return width
. The
height()
method of the object you construct should
return height
. And the row(i)
method
should return a string of width
copies of
ch
for all non-negative i
less than
height
.
For example, given the following line,
TBUtils.print(pen, new BoxedBlock(new Grid(7, 3, '*')));
the output will be
+-------+ |*******| |*******| |*******| +-------+
You may choose to throw an exception in the constructor if the width or height are not sensible values.
b. Write a class, TruncatedBlock
, which implements
TextBlock
and represents the truncation of a text block
to a desired width. As you might expect, the constructor for this
class should take a TextBlock
and a width as parameters.
public class TruncatedBlock implements TextBlock { /** * Create a new truncated block of the specified width. */ public TruncatedBlock(TextBlock tb, int width) { ... } // TruncatedBlock(TextBlock, int) /** * Get the ith row of the block. */ public String row(int i) { ... } // row(int) /** * Determine how many rows are in the block. */ public int height() { ... } // height() /** * Determine how many columns are in the block. */ public int width() { ... } // width() } // class TruncatedBlock
For example, given the following program fragment,
TextBlock block = new VComposition(new TextLine("Hello"), new TextLine("Goodbye")); TextBlock block2 = new TruncatedBlock(block, 3); TBUtils.print(pen, block); TBUtils.print(pen, block2);
we see output something like the following
Hello Goodbye Hel Goo
c. Write a class, CenteredBlock
, which implements
TextBlock
and represents the result of centering a text
block within a certain width, which is provided as a parameter to
the constructor. That width should be no less than the
width of the underlying text block, and your constructor should throw
an exception if given an inappropriate width.
Your class should not attempt to look at the contents of the block which is centered. If that text is left justified, you simply center the left-justified text within your block.
For example, given the following program fragment,
TextBlock block = new VComposition(new TextLine("Hello")), new TextLine("Goodbye")) TextBlock block2 = new BoxedBlock(new CenteredBlock(block, 11)); TBUtils.print(pen, block2);
we see output something like the following
+-----------+ | Hello | | Goodbye | +-----------+
In contrast, given the following program fragment,
TextBlock top = new CenteredBlock(new TextLine("Hello"), 11); TextBlock bottom = new CenteredBlock(new TextLine("Goodbye"), 11); TextBlock block = new BoxedBlock(new VComposition(top,bottom)); TBUtils.print(pen, block);
we should see something like the following
+-----------+ | Hello | | Goodbye | +-----------+
d. Write a class, RightJustified
, which implements
TextBlock
and represents the
result of right-justifying the underlying text block in a block of
a width specified in the constructor.
e. Write a class, BlockPair
, which implements
TextBlock
and represents two copies of the same block,
side by side. For example, if the third row of block
is "Hello"
, then the third row of new
BlockPair(block)
should be "HelloHello"
.
As written, our various text blocks are immutable. What would it mean to make them mutable? Let's consider those issues.
Add a setContents(String newContents)
method to the
TextLine
class. As the name suggests, this method should
set the contents of that block to the new value.
Some of the other classes may make assumptions about the immutability of text blocks. Identify where those assumptions are made and update the code so that it accommodates blocks that change.
Note that the only block that you will explicitly make mutable is
TextLine
. Your job is to update everything else so
that when a TextLine
changes, any block that contains
that TextLine
also changes.
You need only submit one set of code for parts A and B.
Here's a code fragment to help you think about changes.
TextLine tb1 = new TextLine("Hello"); TextLine tb2 = new TextLine("World"); TextBlock compound = new BoxedBlock(new CenteredBlock(new BoxedBlock( new CenteredBlock(new VComposition(tb1, tb2), 7)), 15)); pen.println("ORIGINAL"); TBUtils.print(pen, compound); tb2.setContents("Someone"); pen.println("UPDATED"); TBUtils.print(pen, compound); tb1.setContents("Nice to meet you,"); pen.println("RE-UPDATED"); TBUtils.print(pen, compound);
And here's some corresponding output.
ORIGINAL
+---------------+
| +-------+ |
| | Hello | |
| | World | |
| +-------+ |
+---------------+
UPDATED
+---------------+
| +-------+ |
| |Hello | |
| |Someone| |
| +-------+ |
+---------------+
RE-UPDATED
Something that indicates that there was an error, which may just be a missing line.
Fare-Vee corporation has decided to upgrade the software used in their grocery stores and has hired you to help implement their object model. (You may think it strange that they are hiring a relatively experienced programmer. However, the demand for programmers outstrips supply, and you are fortunate to have a general aura of competence.)
They've decided to start by having you model the items in the grocery store, focusing on how the consumer might think of purchasing those items. Here's their initial assessment of how they want the data organized.
+-Item----------------+ +-BulkFood----------+ +-Weight------+ | getWeight(): Weight | | name: String | | unit: Units | | getPrice(): int | | unit: Units | | amount: int | | toString(): String | | pricePerUnit: int | +-------------+ +---------------------+ | supply: int | ^ ^ ^ ^ +-------------------+ | | | | | | | +-----------------------------------+ | | +----------------------------------+ | | +--------------------+ | | | | | | +-BulkItem------------+ +-Package--------+ | +-ManyPackages---+ | food: BulkFood | | name: String | | | type: Package | | unit: Units | | weight: Weight | | | count: int | | amount: int | | price: int | | +----------------+ +---------------------+ +----------------+ | ^ | | +-NonFood--------+ +-BulkContainer-------+ | name: String | | container: String | | weight: Weight | +---------------------+ | price: int | +----------------+
That is,
Item
is an interface.
BulkItem
, Package
, NonFood
,
and ManyPackages
all implement the Item
interface. Hence, each must include implementations of the
getWeight()
, getPrice()
, and
toString()
methods.
BulkContainer
extends BulkItem
(and therefore
also implements the Item
interface.
BulkFood
and Weight
are additional
classes, used mostly to represent objects used by the other
lasses.
As you might guess, each of the implementations of Item
differs in its implementation of the toString
,
getWeight
, getPrice
,
and equals
methods. Note that the
getWeight
method returns the weight in
whatever units the item uses. The getPrice
method should return the price in cents.
BulkItem
toString
method should return a string that
gives the number of units, the units, and the type of bulk
food. For example, “5 pounds of bananas”.
getWeight
method should return the weight
(built from the unit and quantity).
getPrice
method should multiply the number
of units times the price per unit.
equals
method should only return true if the
compared object is a BulkItem
object with equal
fields.
BulkContainer
toString
method should add the container to
the string returned by the underlying BulkItem
.
For example, “bag of 5 pounds of bananas” or
“jar of 3 grams of tapioca”.
getWeight
and getPrice
methods
should behave as in BulkItem
.
equals
method should only return true if the
compared object is a BulkContainer
object with equal
fields.
Package
toString
method should give the weight,
the word “package”, and the name. For example,
“5 oz. package of mac and cheeze”.
getWeight
and getPrice
methods
should return the obvious values.
equals
method should only return true if the
compared object is a Package
object with equal
fields.
ManyPackages
toString
method should give the quantity, a
times sign (x), and the description of the package. For example,
“4 x 5 oz. package of mac and cheeze”.
getWeight
and getPrice
methods
should multiply the corresponding values in the underlying
package by the quantity.
equals
method should only return true if
the compared object occupies the same memory location.
NonFood
toString
method should give the name.
For example, “can opener”.
getWeight
and getPrice
methods
should return the obvious values.
equals
method should only return true if the
compared object is a NonFood
object with equal
fields.
Create a new package called com.farevee.groceries
and create
classes for these objects in the package.
Your predecessor has implemented the Units
class to
give you a head start. You can find that class at the end of this
assignment.
Here's a program fragment that shows some of these classes in action.
// The store has 20 pounds of bananas, priced at 50 cents per pound BulkFood bananas = new BulkFood("bananas", Units.POUNDS, 50, 20); // The store has 200 grams of saffron, priced at 1000 cents per gram BulkFood saffron = new BulkFood("saffron", Units.GRAMS, 1000, 200); // The customer adds three pounds of bananas to the cart cart.addItem(new BulkItem(bananas, Units.POUNDS, 3); // The customer adds a jar of 3 grams of saffron to the cart cart.addItem(new BulkContainer("jar", saffron, Units.GRAMS, 3); // The customer adds a bag of 1 gram of saffron to the cart cart.addItem(new BulkItem(saffron, Units.GRAMS, 1); // The customer adds a can opener to the cart, priced $3.489. cart.addItem(new NonFood("can opener", new Weight(Units.OUNCES, 2), 349); // The customer adds a box of oreos to the cart cart.addItem(new Package("oreos", new Weight(Units.OUNCES, , 12), 399); // The customer adds five 6oz packages of macncheez to the cart, each // priced at 77 cents. cart.addItem(new ManyPackages(new Package("macncheez", new Weight(Units.OUNCES, 6), 77), 5));
Note that if we were modeling this fully, we would decrement the supply of bananas or saffron when we added each to the cart. You do not need to worry about doing so, although you should also feel free to include that action in your code.
Fare-Vee are optimistic about your ability to implement the design above
and have asked you to build a Cart
class, too. They ask
that you put that class in package com.farevee.shopping
and that you include the following methods.
addItem(Item)
. Add an item to the cart.
numThings()
. Get the number of things in the cart.
For counting items, you should count most items as one thing,
except for ManyPackages
, for which you should use
the count. (You may want to add an accessor for that count.)
numEntries()
. Get the number of entries in the cart.
This is much like numThings()
, except that you should
count a ManyPackages
objects as one entry.
printContents(PrintWriter)
. Print the contents of
the cart.
getPrice()
. Computes the total price of the order,
in cents.
getWeight()
. Since there are multiple kinds of weights
in use at Fare-Vee, they note that this method can return an array
of weights, but that you should combine similar weights. (E.g., you
should combine all of the weights in pounds together, but you should
not convert from ounces, grams, or kilograms to pounds.) You may
assume that we have only the four basic kinds of weight.
remove(String name)
. Removes all of the products whose
name exactly matches name
.
merge()
. Finds identical items and merges them into
a single item. For example, if you have two Package
items with the same name, weight, and price, you should combine
them into a single ManyPackages
object. If you have
a ManyPackages
object and a Package
object of the same kind, you should combine them. Continuing,
if you have two ManyPackages
objects that contain
the same kind of package, you should combine them into a single
ManyPackages
object. Similarly, if you have two
BulkItem
objects with the same food and units, you should
combine them into one BulkItem
. However, there is no
way to combine BulkContainer
objects with anything.
This method does not need to do anything with NonFood
objects.
You may use whatever data structure you consider most appropriate
to store the data in the cart - an array, a linked list you create,
a Vector
, an ArrayList
, a LinkedList
,
or whatever else you decide is appropriate. Note that your choice of
data structure will have a large effect on the ease of implementing the
various methods, so choose thoughtfully.
Reflect on the design of the Fare-Vee project in Parts C and D. Some questions to get you started appear below. You should certainly address those questions, but you may also find it useful to consider others.
Comment on the differences in the relationships between
BulkItem
and BulkContainer
on one hand,
and Package
and ManyPackages
on the other.
Each takes an existing class and adds information, but does so in
different ways. What do you see as the advantages and disadvantages
of each? When would you use one approach or the other?
Both Cart.remove
, and Cart.merge
require
that you determine whether the name of an item matches a desired
name. There are at least four approaches to achieving this goal.
You could directly access the name
field of each class.
(That may be difficult, given that the name
field
probably isn't public, and you're working in a different package.)
You could write a getName
method for each class (or at
least each appropriate class). You could write an equals(String
name)
method for each appropriate class. You could write a
hasName(String name)
method for each appropriate class.
And, if you're doing that for each class, you might even put it into
Item
interface.
There are also probably others. What do you see as the advantages and
disadvantages of each approach?
Consider the differences between Package
and
NonFood
. They appear to have the same fields. Does
it make sense to make them separate classes? What other approaches
could we use? Which would make most sense in terms of what we're
describing? Which would make the most sense in terms of writing
more maintainable code?
You can find another design at the beginning of http://www.cs.grinnell.edu/~walker/courses/207.fa14/suppl-prob.shtml.
Comment on at least five differences between that design and the
Fare-Vee design given in Parts C and D. In each case, indicate which
choice you think is better and why. Please make sure that one issue you
address is the choice between double
and int
values to represent prices.
Units
Class/** * Units of measurement. The primary units available at * Units.GRAM, Units.KILOGRAM, Units.OUNCE, and Units.POUND. */ public class Units { // +--------+------------------------------------------------------ // | Fields | // +--------+ /** * The name of the unit. */ String name; /** * The abbreviation of the unit. */ String abbrev; /** * The plural name of the unit. */ String plural; // +--------------+------------------------------------------------ // | Constructors | // +--------------+ /** * Create a new unit with a given name. */ private Units(String name, String abbrev, String plural) { this.name = name; this.abbrev = abbrev; this.plural = plural; } // Units(String, String, String) // +-----------+--------------------------------------------------- // | Accessors | // +-----------+ /** * Get the unit name. */ public String toString() { return this.name; } // toString() /** * Get the abbreviation of the unit name. */ public String abbrev() { return this.abbrev; } // abbrev() /** * Get the plural of the unit name. (One does not always form a plural * by just adding "s", so we provide this additional method.) */ public String plural() { return this.plural; } // plural // +-----------+--------------------------------------------------- // | Constants | // +-----------+ /** * Standard unit: Pounds */ public static final Units POUND = new Units("pound", "lb.", "pounds"); /** * Standard unit: Ounces */ public static final Units OUNCE = new Units("ounce", "oz.", "ounces"); /** * Standard unit: Kilograms */ public static final Units KILOGRAM = new Units("kilogram", "kg.", "kilograms"); /** * Standard unit: Grams */ public static final Units GRAM = new Units("gram", "gm.", "grams"); } // class Units
The text block problems are based closely on HW 3 from Samuel A. Rebelsky's spring 2005 section of Grinnell College's CSC 152, available at http://www.cs.grinnell.edu/~rebelsky/Courses/CS152/2005F/Homework/hw.03.html.
The grocery store problems were inspired by Henry Walker's first supplemental problem for section 2 of CSC 207 in Fall 2014, available at http://www.cs.grinnell.edu/~walker/courses/207.fa14/suppl-prob.shtml.