This is an assignment to the Software Architecture class at the Technische Hochschule Nürnberg.
In this assignment, we will refactor and extend the app we started to implement in the last assignment.
- Create a fork of this repository (button in the right upper corner)
- Clone the project (get the link by clicking the green Clone or download button)
- Import the project to IntelliJ
- This assignment requires at least JDK11 or newer! Make sure that you have JDK11 installed on your machine and the environment variables
JAVA_HOME
andJDK_HOME
set accordingly.
As already explained in the lecture a singleton is an object which is accessible in your whole application at any time.
To avoid having to create a new instance of the API proxy again and again, we want to implement a singleton that holds an instance of the OpenMensaAPI
interface we implemented last time.
Side note: depending on who you ask, Singletons are a design flaw in themselves. That might be true or not but keep in mind that Singletons should be used carefully. (Especially when they are used in multi threading applications where they may lead to unpredictable behavior.)
The following UML shows one possibility how this may be accomplished:
Afterwards you can use the singleton like this:
OpenMensaAPIService svc = OpenMensaAPIService.getInstance();
OpenMensaAPI apiInstance = svc.getApi();
apiInstance.getMeals(...)...
Remark: IntelliJ is capable to generate singletons for you. Check the "Kind" dropdown in the class creation dialog
Until now we were only able to filter for vegetarian food. This time we want to extend the filter capabilities of our app. The following wireframe shows the new layout of our app:
JavaFX offers two different controls to implement a drop-down:
ChoiceBox<T>
ComboBox<T>
The difference is that a ChoiceBox<T>
only calls toString()
on each element it is representing while it is possible to add a CellFactory
to a ComboxBox<T>
instance to define a custom behavior how to display every item.
As we only need to display static strings to distinguish between different filter options a ChoiceBox<T>
is totally sufficient for this assignment.
Additionally to the already implemented vegetarian filter of the last assignment we want to introduce the following filtering choices:
- All - no elements are filtered (the default)
- No pork - show all meals except those containing pork (for Nürnberg, this is coded as category "Schwein")
- No soy - show all meals except those containing soy (for Nürnberg, this is coded on the notes as "mit Soja")
To implement the filter mechanism we could just extend the existing logic by adding some more if-else
statements.
This approach isn't very flexible and results in a huge code statement which isn't readable any more so that's not what we want.
The strategy pattern is always a good choice if you have different implementations giving you the same kind of result (e.g. all implementations return a list of meals).
The following UML is meant as an implementation advice. You don't have to implement it this way but it might result in less code (and save you time). It is also a nice application of inheritance to avoid duplicates and reduce the amount of code.
Additional explanations: To avoid duplicate code (DRY - don't repeat yourself!) you can extract the code to iterate over the list of meals and collect matching meals to an
abstract
base classFilterBase
where you pass every meal to anprotected abstract
method which decides if the meal should be included or not. TheNoFilter
is handled separately since this strategy does not filter anything.
The remaining problem is how to get an instance of the currently required strategy. This is where the factory pattern comes into play.
A factory's responsibility is to instantiate and manage instances of different implementations of the same base class or interface.
There are different options to control which implementation is returned by the factory (e.g. enums, string keys, dumb integers, environment variables and a few more).
To get the currently selected item from a ChoiceBox<T>
, a ChoiceBox<T>
instance exposes the getSelectionModel()
method.
The selection model has two methods getSelectedIndex()
and getSelectedItem()
(you can also use .getValue()
as a shortcut).
The selected item should be used to select the corresponding strategy by passing the string to the getStrategy(...)
method of the MealsFilterFactory
.
- Declare the interface
MealsFilter
- Implement the
NoFilter
class - Optionally: implement the
FilterBase
class (it's totally fine for now to implement all filters without an base class) - Implement the
CategoryFilter
that either includes or excludes meals where the given categories match - Implement the
NotesFilter
that excludes meals where given terms match on the notes (eg. "soja" to exclude dishes "mit Soja") - Implement the
FilterFactory
class
Remark: again the given UML for the FilterFactory is just a recommondation. You don't have to stick to this draft.
Side note: you could implement the getStrategy(String key)
method by doing a switch
on the key
parameter but it's also possible to solve it with a Map
instance which holds the known keys and corresponding implementations and is initialized in a so called "static constructor":
public abstract class MyHelper {
private static final String ANY_CONSTANT;
//static constructor
static {
ANY_CONSTANT = "Hello, World!";
}
}
After implementing all filters and the OpenMensaAPIService
singleton the only thing left is to add an onAction
handler to the ChoiceBox
which does:
- selecting the right filtering strategy
- applying the filter
- updating the
ListView
to display the filtered meals
Keep in mind that a proper fallback if no valid filter is selected might be a good idea.