DPSD - Project 1.1
Requirements
Part A
In this assignment you will continue to build a Pizza Configuration API. Please do write up notes in your Lessons Learned from the prior version. You may want to add/adjust them as you receive feedback from Project 1.0 and work on this next iteration.
To get you started for the lessons learned based on your first version of your Project 1, I've given you some areas to consider:
- How does your solution use class/object relationships?
- How does your solution use packages and access modes?
- What strategies of OOD did you use to design your core classes?
- Are your core classes independent and self-contained? In other words, do they demonstrate good encapsulation?
- What are the advantages and what are the challenges of using the generic OptionSet/Option classes rather than coding an PizzaSize class, or a MeatChoices class or enumeration, ...?
- What are the advantages and what are the challenges of using arrays rather than a Collection class?
- How is Serialization useful? What aspects of your design presented a challenge and how did your solution address them?
Now on to the next version of Pizza Configuration!
I would like you to expand your proof of concept, so we will update our
code for pizza configurations using interfaces and abstract classes,
also add a custom exception handler to enhance your design.
Note: Do you know what an API is? Please look this up and be prepared to discuss in class.
For expanding your proof of concept please consider the following
requirements; we will expand your existing design with these options:
- Define a set of methods in an interface (as the API) that can be used to exercise the functionality of the existing class set.
- Design the start of an exception handler to handle at least 5 exceptions and that
attempts to fix errors. Use the Factory design pattern for handling the exceptions. You will expand on this in the next version to handle custom FileIO exceptions too.
Your Deliverable:
Design and code classes for these requirements and write a driver
program to exercise your API and test the exception handler. Test your
code adequately.
Concepts you will need to know.
- Object Theory
- Exception Handling
- Abstract Classes
- Interfaces
- Container Classes
- Wrapper classes (Design Pattern)
- Factory Pattern (Design Pattern)
Plan of Attack - Part A
Refactoring refers to making changes in your design and code after it is written to better meet requirements that have changed or to better implement a good design. It can be a simple as changing attribute names, or completely re-organizing your code. You may have changes you need to make from Project 1.0 in addition to the ones listed below.
Use your IDE and please
complete this step before approaching this unit.
- Change your PizzaConfig and OptionSet classes to use the ArrayList class instead of
basic arrays.
- There is another opportunity in this iteration of Project 1 to use another collection class - where is it?? Once you figure that out, code it to use a LinkedHashMap.
Step 1: Writing API Design and Construction
Here is what you are being asked to do:
- You must build an API for the pizza configuration classes.
This API will be used to create a Pizzeria.
- Your API must use a custom exception handling mechanism to make your module more reliable.
Here are some of the main the considerations for developing your API:
- You will to use Interfaces and abstract classes to implement your API.
- You have a model package (this is a component) that has the pizza-configuration classes.
This component represents the data used by your your API and is called "the Model" in this write-up. Each top-level pizza-configuration object contains an list of OptionSet objects, which have their respective
Options.
- The methods in OptionSet and
Option class protected. Only your PizzaConfig class and appropriate methods in that class are public.
- We are now trying to provide a set of methods that act as a API for accessing the
data and associated functionality in the model package.
- In this case, your "users" are the programmer creating a pizzeria, and the person who sets
up the configuration for the pizzeria; no one is able to order pizza (yet!).
- Now let's decide on methods that should be exposed in the Interface.
For your implementation you will implement these six methods (or ones that are very similar):
void createPizzeria(String pizzeriaName, ArrayList pizzaConfig)
-
This method will create the pizzeria with the pizza configuration(s) created by your list of
Test objects. If you did not implement a hierarchy of Test cases, you will need to do this now.
Note that this method does not return the object. Question: how will you handle duplicates?
Note: this is a temporary method only to give you the ability to add a PizzaConfig until
we properly implement the file-reading part.
void configurePizzeria(String filename);
-
Given a text file name, this method will build an instance of your pizzeria configuration. This
method does not return the object.
For now, make this a stub (a printline() will suffice); you will implement file I/O in the next
version of your API.
-
void printPizzeria(String pizzeriaName);
-
This function searches the Model and prints the properties of a given pizzeria. In essence this would print the menu for the
pizzaria, but for now just show the data; since this is a POC (proff-of-concept), there is no need to print in a pretty way.
void updateOptionSetName(String pizzeriaName, String optionSetname, String newName);
-
This function searches the Model for the entry specified by the model name for a given OptionSet and sets the name of OptionSet to newName.
-
void updateBasePrice(String pizzeriaName, double newPrice);
-
This function searches the Model for the entry specified by the pizzeria name and updates the base price to the value specified by newPrice.
-
void updateOptionPrice(String pizzeriaName, String optionName, String Option, double newPrice);
-
This function searches the Model for the entry specified by the pizzeria name and updates the Option in the given OptionSet to have the price specified by newPrice.
Here are a few considerations for structuring the code:
- It is important for us to NOT expose the pizza-configuration instance(s). The user
of the API (a programmer and/or person setting up configurations)
creates pizzerias through the API and all the
required coding functionality is provided through the API.
- So how do we organize the code so we can encapsulate access to pizza configurations?
- First, structure your project to create a new package called wrapper.
- Add two interfaces in your wrapper package:
- One called CreatePizzeria with the
createPizzeria()
,
configurePizzeria()
,
and printPizzeria()
methods in it.
- Another one called UpdatePizzeria with the
updateOptionSetName()
,
updateBasePrice()
,
and updateOptionPrice()
methods in it.
- Add a new abstract class called ProxyPizzerias in the wrapper package.
This class will contain all the implementations of any method declared
in the interfaces.
Note that our ProxyPizzerias class is both an Adaptor and Proxy (more with this in later versions).
package wrapper;
import model.*;
public abstract class ProxyPizzerias
{
private PizzaConfig configs;
// All interface methods are implemented here
}
}
This class contains all the instances of PizzaConfig. The variable configs
can be used for handling all operations on a pizza-configuration as needed by the
interfaces.
- So this raises a question - if we are defining all methods declared
in interfaces in the abstract class ProxyPizzerias then why is
ProxyPizzerias not implementing the interfaces directly?
Instead, it is using inheritance for this purpose:
we want ProxyPizzerias to be a "hidden" container, containing all the data and then we will
expose an empty class for usage of our API. In order to do this, we
will add a new class called PizzeriaConfigAPI in the wrapper package that will have
no lines of code but will always look like this.
package wrapper;
public class PizzeriaConfigAPI
extends ProxyPizzerias implements CreatePizzeria, UpdatePizzeria
{
}
So whenever a new interface has to be added you can simply update
the PizzeriaConfigAPI declaration and write all methods in the abstract class called
ProxyPizzerias. In a nutshell we have encapsulated the access to the pizza-config instances through
the API and also hidden the code (artificially) in the abstract class.
- Next write a driver to test each of the methods.
- Instantiate
a PizzeriaConfigAPI object using a CreatePizzeria reference and instantiate
another using an UpdatePizzeria reference.
- Are you able to create and print the pizza-config objects through the
CreatePizzeria interface?
- Are you able to update one of OptionSet's name and update an Option price for the pizza-config instance created in the previous step?
- If you follow the exact design specified above, you will not be able to update the same
Pizzeria, as the object in
ProxyPizzerias is an instance variable and not declared as a static object. In other words
when you create an instance of PizzeriaConfigAPI (child of ProxyPizzerias), a
new pizza-config is created. Think about it - this API needs to provide access to all pizza configs in existence. So, to work properly, it requires the variable configs to be static so it
can be shared between references to the API.
- At the same time, your calling code cannot "know" that the internal representation is static. Make sure your design and implementation follows this basic tenet of encapsulation.
- This is also and example of what is called "separation of concerns", meaning that at each point
in the designed classes, one class does not know or care about the internals of another. We will
build on this in subsequent versions of our PizzeriaConfigAPI.
- Update your code to use static and see how it responds.
- Make sure to use use polymorphism with your
PizzeriaConfigAPI rather than instantiating it multiple times. In other words, create and use
references of the the parent classes CreatePizzeria and UpdatePizzeria in order to test
that you have the right design. IMPORTANT - this requires that your design be proper,
or it will not work, which is why it is a good test. We will look for this during evalation of
your code.
- This is similar to a design pattern called a "Singleton". After you are finished with the assignment, write up a short explanation how you might update your design to implement the Model API as a Singleton and if this is a better idea (why or why not?) Email this to me before the lecture following the due-date.
Step 2: Defensive mechanisms to make software more self-healing
Our next step to start on a custom exception handler to deal with issues in runtime. This code
will be in a package called exceptions.
By "self-healing" we mean your API code will throw exceptions, catch them, and the exceptions themselves
will at least attempt to fix the issue in order to continue execution.
Your Exception classes and handlers at a minimum should handle and "fix" at least 5 exceptions. Here are some possible examples:
- When validated, the base-price is missing. Note: what does this imply about your design?
- When validated, the name is missing. Note: what does this also imply about your design?
- Trying to add an OptionSet that already exists.
- Trying to add an Option to an OptionSet when it already exists.
Note: you will extend this to handle errors relating to reading in pizza configurations from your
data-file in the next iteration.
Your custom Exception classes should have a mechanism to allow/disallow logging. For logging, you need to investigate how to properly implement Logging in Java.
After you have finished the basic design for your exceptions, investigate the
Factory design pattern and use this pattern to create the exceptions that implement self-healing code for your API.
Plan of Attack - Part B
Note, there is a major design flaw in the system as designed above.
Hint: Reminder: Part A indicates there is
an opportunity to use a LinkedHashMap. What is it?
Note that to loop through elements in a LinkedHashMap you should use
an Iterator. For the ArrayList, you may use any method of access you wish.
Additional notes from our in-class discussions:
Yes, the major design flaw is that the names are plural (Pizzerias and
configs), but the intance variable is declared to be an single PizzaConfig. This will not work -
you need to design this version of the code to handle multiple Pizzerias, not just one.
Finalized Technical Requirement - your Model (set of pizza-configs) should
be saved using LinkedHashMap. The set of OptionSet in each Model and
respective Options should be saved in an ArrayList.
There is still one major flaw in the design - can you spot it?
We will discuss again during class.
Submitting your work:
Please review your work against this checklist before submission:
Program Specifications / Correctness
- No errors, program always works correctly and meets the specification(s).
- The API can be reused as a whole and methods are designed to be reuseable.
- A custom Exception Handler class hierarchy has been implemented. It handles a minimum of 5 exceptions and can be extended for handling file I/O errors in the next iteration.
- The custom Exception Handler uses polymorphism and follows the Factory Pattern for object creation and exception handling.
- Ability to log and configuring the logging is added to your custom Exception classes.
- The API follows the basic design as specified in the requirements.
- Interfaces and Abstract Classes are utilized to add extensibility.
- The Interfaces are used to expose the Model (PizzaConfigs, OptionSet and
Option) and the functionality is meaningful (means the methods in the interface are well thought out/useful in context of application).
- Collections: a LinkedHashMap is used for storing the proper elements in this new version of the
API. The OptionSet and Option objects are each store in an ArrayList.
Readability
- No errors, code is clean, understandable, and well-organized.
- Code has been packaged and authored based on Java Coding
Standards.
Documentation
- The documentation is well written and clearly explains what the code is accomplishing and how.
- Class Diagram is provided and reviewed before coding.
- Final Class Diagram is provided.
- Test results are fully documented.
Code Efficiency
- No errors, code uses the best approach in every case.
- The code is
efficient in both memory and speed, without sacrificing readability and understanding.