Project 1: Classes, Inheritance, and Abstraction
(Return to homepage)
In Project 1, we'll explore how class hierarchies, abstraction, and interfaces make up aspects of
Object-Oriented design.
The program we'll be writing will simulate a little world with interactive buildings, like hotels and
restaurants, that share common interfaces but have individually unique behaviors. Buildings are hierarchical
in several ways, such as by location (country, city, street) or by people (owner, company, renter). As a
real-world software example, when using Yelp to find restaurants, filters like price, location, and
operating hours are used to "traverse" the branches of this imaginary hierarchy until dinner is found.
Your task is to implement a pre-designed class hierarchy of interactive buildings. All buildings share some
common properties - for example, all buildings have a location (Building::getLocation) and all stores sell products (Store::getProducts). Buildings also have unique properties, depending on their type
- for example, hotels are rentable (Rentable::registerRental) while homes are not.
This explainer document (and future explainers) will be less thorough than the Project 0 explainer. The implied meaning of each method implementation is
reflected by their names and corresponding unit tests. Learning how to analyze code and the intentions of
the original developer is a skill that will benefit you greatly when contributing to an existing project (as
opposed to starting from scratch).
Ready? Of course you are. Get the Project 1 starter code
here! (jUnit dependencies included)
The project code should work out-of-the-box for IntelliJ (running and debugging), but if you're having
trouble getting it working, try these IntelliJ setup instructions.
Interfaces
- Business: Describes a business with a company name.
- Residential: Describes a building with occupants.
- Rentable: Describes a building which can be rented.
Classes
lib.Pair: Generic container class for storing two variables: left
and right. The two variables can be different types. Commonly used for
associating two
values, such as a name (String) and a number (int).
world.Building: Top-level abstract class describing a building. A building has a String location.
-
Store: Extends Building, implements Business. Abstract class describing a Store. A
Store has an inventory of HashMap<String, Integer> supplies that
track the current items the Store has in stock. Methods are available for supplying the store
(adding to inventory), listing products (items and quantities: lib.Pair),
and making purchases (items and quantities: lib.Pair).
-
Supermarket: Extends Store. The supermarket is a simple storefront which
sells exactly what its inventory (supply) contains.
- getProducts(): Inherited from Store. Returns a list of
products (as pairs, name and quantity) that reflects the store inventory. Hint: you
can iterate over this.supplies.keySet() to get names and
quantities.
- purchase(Collection<Pair<String, Integer>>):
Inherited from Store. The supermarket should attempt to fulfill the order described
by the collection (a list of pairs, products and quantities) as best as possible.
Purchsed items are subtracted from the store's supplies. If the requested item does
not exist,
nothing is purchased. If there are not enough items to fulfill the order, fulfill as
many as available.
Returns a collection (list of pairs, products and
quantities)
describing the items fulfilled. You may assume that each order will only request a
given product once, e.g. there will never be two requests for a single product, but
there can be one request for two of a product.
-
Restaurant: Extends from Store. More sophisticated than the supermarket,
the Restaurant still depends on this.supplies for tracking
inventory, but has a bank of recipes for which its product listing "menu" is derived from.
- learnRecipe(String, Collection<Pair<String,
Integer>>): Adds a recipe to the restaurant, with the name and
list of required ingredients as pairs of item and quantity.
- getProducts(): Inherited from Store. Returns a list of
food items and quantities that the restaurant can create from recipes,
using existing
supply inventory. Each recipe is considered independently when calculating
quantity available. All recipes known to the Restaurant should appear in the
returned collection; recipes that cannot be produced with existing inventory should
report a quantity of zero.
-
purchase(Collection<Pair<String, Integer>>):
Inherited from Store. The restaurant should attempt to fulfill the food order
described
by the collection as best as possible, in the order that each request is received.
Fulfilling an order requires deducting, from supplies, the raw ingredients specified
by
the recipe. Partial fulfillment behaves the same as with the supermarket
(best-effort).
Returns a collection (list of pairs, products and
quantities)
describing the items fulfilled. You may assume that each order will only request a
given recipe once, e.g. there will never be two requests for a single product, but
there can be one request for two of a product.
Home: Extends Building, implements Residential. Homes are single-occupancy and will
only accept a single occupant at a time.
-
moveIn(String): Move in an occupant, if the home is presently empty.
Otherwise, do nothing.
-
moveOut(String): Move out an occupant, if the occupant is present.
Otherwise, do nothing.
Hotel: Extends Building, implements Residential, Rentable, Business. Hotels are
multi-occupancy businesses that take rentals, with no occupant capacity limit.
-
registerRental(String): Registers a rental under the provided
name, allowing the named occupant to move in.
-
endRental(String): Unregisters the rental for a name. The occupant
is no longer allowed to move in, and is evicted if present.
-
moveIn(String): Move in an occupant, if they have a rental.
-
moveOut(String): Move out an occupant, if they are present.
New Toys
As part of Project 1, you'll be exposed to several Java standard library data structures that you can
take advantage of in your program. As a "client" of the Java libraries, you are generally not concerned
with underlying implementation - instead, you should focus on understanding the interface provided.
Below is a brief description of some of the library classes you may use; there are some examples present
in the starter code as well.
-
HashMap<K, V>: A Java dictionary that provides a lookup table, associating
keys to values (official
documentation).
- put(k, v) assigns a key to a value
- get(k) retrieves a value, or null if there is no value associated
with the key.
- containsKey(k) checks if there is a value associated with the key.
- keySet() returns an iterable set of keys that have associated
values.
-
HashSet<V>: A set of unique objects of type V. (official
documentation).
- add(v) adds a value to the set
- contains(v) checks if the value exists in the set using equals()
with the key.
- remove(v) removes a value from the set
- Collection<T>: [Interface] A group of objects is a Collection (official
documentation). You should use the ArrayList<T> concrete
class throughout the project wherver Collections are expected, which has an array-like interface
with methods like get(i), set(i, v), and size() (official
documentation).
When something is iterable, it supports the Iterable
interface and can supply an Iterator.
Something which is Iterable will support the for each loop construct. For example, the snippet below iterates
over all keys of a HashMap<String, Integer> named this.supplies:
for (String item : this.supplies.keySet()) {
Integer quantity = this.supplies.get(item);
// do something
}
Running Your Code
As this project has quite a few moving parts, it will take some effort to get the entire project to
compile, and IntelliJ sometimes won't run the test suite if there are project errors. Thus, the project
is optionally split into two parts:
Phase 1: Implement Supermarket and Restaurant. Tested by test.Phase1Evaluation.
Phase 2: Implement Home and Hotel.
Tested by test.Phase2Evaluation.
Phase 1 should be completed before Phase 2, so you can test Phase 1 with the run script runPhase1.sh and Phase 2 with the script runPhase2.sh
(which actually includes a complete integration test, involving Phase 1). Alternatively, if you enjoy
the IntelliJ debugging experience, write all the necessary method stubs to get the project to compile.
As always, .bat scripts are included for Windows, too.
A run script for the entire project test suite is included as run.bat
(Windows) or
run.sh (Mac/Linux). This will compile your entire project and produce a run
report
with your estimated grade. Your entire project must compile for the script to work; the
phases will not be individually graded, so you must submit a project that will fully compile.
Submission Instructions
See the submissions page for details. Have questions or need help? Post on Piazza!
Critical Analysis Questions
These questions aren't graded, but are similar to those you may see on an exam. You are welcome to
discuss
these with classmates offline on on Piazza.
- What additional flexibility do interfaces provide over abstract classes?
- The use of type Store to reference both Supermarket
and Restaurant is an example of
polymorphism. In your own words, describe polymorphism as a concept. What are other examples of
polymorphism in this project?
- In Project 0, the interface Collection is used often as an argument or
return
value (by design), rather than a concrete collection such as ArrayList.
Why
might be this the case?
- Building and Store are both abstract, but
nothing changes runtime-wise if they are made concrete (non-abstract). Why might they have been
designed/declared abstract?
- A single end-to-end integration test (black-box test) can achieve full code coverage (lines tested)
with
much less work than having to write several unit tests. Why, then, are unit tests valuable?
- Java does not permit member variables in interface definitions, but does permit member variables in
abstract classes. Why might the Java designers introduce this restriction on interfaces?
- If all Java classes inherit from Object, then classes could have all their variables be of type
Object
and avoid templated types entirely (generics - see lib.Pair). If this is
the
case, what value do templated types (generics) bring to class definitions?
(Return to homepage)