>>Let's continue our study of aggregation by examining two common patterns for aggregation.
Well first off, what does aggregation mean? Well it means that one class has a data field
from another class. So an example of aggregation is that our String ArrayList that we created
in our last few presentations aggregates String class. This is how we show it in UML. There's
an open diamond and a line between the two. The diamond goes on the side of the class
that has the data element in it.
Now for our first type of aggregation, the type I just showed you, the relationship is
aggregation because we have many String objects that are stored in a single String ArrayList.
Now ArrayList and arrays are very typical of the types of classes that will involve
this kind of aggregation relationship, because you have many elements of the same type that
are combined into a single unit. Virtually every significant program you're going to
write has this form of aggregation. We've done many, many examples already, but I'll
do a little bit more on an example later.
Our second pattern for aggregation is something that's a little bit newer. It's data fields
of different types being combined into an object. So let's think for example, of writing
a menu for a restaurant. So we're going to need to have food items and they're going
to go on a menu. Now a food item needs a name, a price, a description, and the common allergens
in this food. Now, there actually is a whole literature on what allergens have to be identified,
and these are the kind of things you have to do research on when you're writing programs.
When you're a computer scientist, it's not enough to know just how to program and how
to design software--you have to know how to get knowledge out of the world and out it
into your programs, because if you don't do it in this case, people's lives could be at
risk from allergies.
So let's take a look at what our data should look like. Name would be a String, price would
be a double of course, the description would be a String. The allergens I'm doing in kind
of an interesting way. I'm making an array of Booleans for allergens. Now what I'm doing
with wheat, milk, and nut, is that I'm making those indices into the array. So for example
if wheat was assigned the value of zero allergens of zero would tell you whether you had or
did not have a wheat allergy. Notice also I have a constant for the number of allergens.
Now the reason I made that a constant is that I know I'm going to have to put more allergens
in later, because I haven't included all the common categories yet. Wheat, milk, and nuts,
notice are static. That is their class data. The reason is because they're constants. Now
UML doesn't have a formal symbol for constants, but what it does have is a code convention
that you can use, or at least I use the code convention from Java of making them in all
caps. That's kind of a wink to people who are reading the design that these things are
probably constants.
This type of aggregation is very rarely shown in UML for a couple of reasons. Aggregation
has to be a relationship between classes. It can't be a relationship between a class
and a primitive data type. So for example, the relationship between the class food item
and the Boolean really can't be documented this way. The other thing is that knowing
that a class aggregates a String is really not useful information. Strings are everywhere
in Java programs. There are very few classes that don't aggregate a String one way or another.
And so we don't tend to put those details in UML. Remember, this is one of the cool
things about UML--is you're allowed to pick and choose what's important and useful to
have documented. Of course that also puts some pressure on it, because it means you
have to make conscious, reasoned choices instead of just being able to rely on automatic behavior.
Let's talk a little bit about our implementation plan for this class. We certainly need to
write constructors, and we want to remember to cascade them. Then we'll write some accessors.
Now accessors are usually just a single return statement. Everybody usually loves writing
accessors. Then we're going to write some mutators. Now for the time being, most of
our mutators are going to be just a single line, but that isn't really how mutators work
in general. We don't have a lot of good strategies for handling errors at this point, and so
we've kind of been ignoring the fact that all these mistakes can happen. Once we have
better strategies for handling errors, which, by the way, unfortunately will be next semester,
then you can put more data into your mutators. And really check and make sure the data is
legal before you store your object. That's a very important responsibility in enforcing
the contract.
So for constructors, there are going to be two of them that we'll write. One will take
the name, the price, and the description, and just assume there are no allergens. And
the other one will set all of the fields. So we have name, price, description, and then
wheat, nuts, and milk for the three allergies that could occur. Now remember that when we
write cascaded constructors, we write the one with the most parameters first, and then
once we've written that one we call it for the other one. The constructor on top is the
constructor for the most parameters--so that's the one we do first. Name, price, and description
are just set in a very straightforward way. Let's look a little bit about how allergens
are set. First you have to construct the array for the allergens, new Boolean of number of
allergens. That's where that other constant gets used. Then we'll say allergens of wheat
is wheat, allergens of milk is milk, and allergens of nut is nut. So wheat, milk, and nut are
going to have values like zero, one, two. Now it doesn't really matter which one has
which value, but it is important that they're not the same and that the values are zero,
one, and two. To create the other constructor, the one that has only three parameters, notice
we've passed those three parameters in and then passed in faults for the other parameters.
Now to finish the implementation we're going to need to create accessors. Now in this particular
example all of our fields would need accessor methods. That isn't always true, but it's
a pretty common pattern. So for example an accessor would be something like hasWheat,
which returns a Boolean. We're also going to need to create mutators. Now most of this
data is not going to need a mutator. So things like names and ingredients and descriptions,
these very rarely change on a menu. I can think of restaurants that have had the same
descriptions on the same menu since I was a little kid. So we really don't need to create
mutators for that. What we do need to create mutators for, however, is prices. [laugh]
Because prices do change and believe me, there's nothing on a menu from when I was a little
kid that has the same price. Remember that if you don't create a mutator for a particular
field, you're going to force somebody to construct a new object. You don't want to do that all
the time, but on the other hand if changes aren't made very often, that's actually a
better strategy than putting in a whole bunch of mutators that never get used. So, now we
need to design our methods. So for getName for our String we'll have getPrice, which
will return a double, getDescription which will return a String, and setPrice, notice
that's the mutator. Also notice how the signature is different on the mutator. First off, it
starts with set instead of get. Set is something that cues you in that there's a mutator involved,
and there's a parameter price. With accessors you typically don't have parameters and you
always have a non-void return type. With mutators, you tend to have a void return type. Then
we'll have methods like hasWheat which returns a Boolean and hasCommonAllergens which returns
a Boolean. What hasCommonAllergens is going to do is look through all of the possible
allergens and see if there's a possible allergy problem or not. So that's kind of a nice method
to have.
So let's implement a couple of accessors. So public Boolean hasWheat, all it does is
return allergens of wheat. Notice that the person who's using the food item class would
have no idea about this Boolean array trick we're using. That's kind of cool. Now let's
take a look at how hasAllergens can be written. Now I've written this with a for each loop,
and I did that for a particularly good reason. And the reason is--we know that we don't have
all the allergens in that array. We're going to increase the number of allergens and add
other ones in. If I do it with a for each loop instead of doing an indexed loop, what
we can do here is not change this code at all. So I'm sort of looking to the future
of this code, seeing what might need to be done, and trying to write the code in a way
that it won't be changed. If you can learn to do things like that you're going to be
a much better software engineer.
So, what happens in this method is you're stepping through all the possible allergens.
If you find an allergen that's true you return true, because if any allergen is true, there
is an allergen in it. And otherwise if you get to the loop and you haven't returned anything,
then you return false. So notice also this is very concise code. You know most computer
scientists are kind of mathematicians, so we have a tendency to really like concise
things.
For implementing the mutator it's going to be pretty simple. Public void set price, double
price. So all you do is say this.price equals price. Now the next thing we need to do is
design a menu. So what a menu will be mostly is just an ArrayList of food items. So this
is that first form of aggregation again. This is what it would look like in UML. Our menu
is aggregating food items. Implementing the menu is going to be relatively simple, because
we can just use the methods that are available in the ArrayList. So the ArrayList is going
to do the really hard work for us, and all we have to do is choose the arguments correctly.
So you can kind of get into the groove of programming this and it can be very quick
and efficient. So here's a partial design for the class.
We have private data, which is the menu, which is an ArrayList of food items. And then we
have some useful methods, like adding a food item to the menu, removing a food item with
the given name from the menu, reordering items on the menu, updating prices, and then find
without allergens, which is going to return an array of all the food items that don't
have any allergens. And then findWithoutWheat, that would return a food item array with all
of the items that don't have any wheat in them. Those are questions that people with
allergies tend to ask at restaurants, and so that's a useful method to have around.
So now let's implement the addToMenu method. Public void, addToMenu, food item, item into
position. Now all we have to do here is add to the menu, the position and the item. So
we're using an ArrayList method to do the hard work for us. Notice we're not having
to worry about how the ArrayList was implemented or any of that stuff, we're just using the
exterior part of the class. So this is pretty quick and easy. You've probably heard me say
that it's not a good idea to have just one line in a method, but this is an exception.
Because here, what's happening is the ArrayList is doing the hard work for us. So there's
really a good piece of work for us being done, it's just that all we had to do was call it
from the ArrayList.
Now let's talk about one like findWithoutWheat. Now this returns an array. Since it returns
an array we know the sixe can't be returned, so this has to be a perfect sized array. This
means we have to use our usual strategy for dealing with perfect sized arrays. Count the
number of items that don't have wheat, construct the array to the perfect size, and then put
the items without wheat into the array. Here's the code for findWithoutWheat. Public food
item array, findWithoutWheat. Now remember our strategy. We need to figure out how many
elements don't have wheat in the menu ArrayList, then construct the array and then move the
items in. The way I counted how many elements didn't have wheat was I started a counter
at zero, and then I used a for each loop to step through the menu one food item at a time.
If that particular food item has wheat, then I increment count. Once you get to the end
of that for loop, then I constructed the array. So we have a food item array, result is new
food item of count. So now we know that our array is constructed to the perfect size.
Now we have to go back through our menu again and actually take the food items that don't
have wheat in them and put them into the array. So for food item f menu, so we're stepping
through our menu one food item at a time. If F has wheat, we put result of count to
F and increment count. And of course when we get to the end of the method, we return
result.
Let's make some observations about this code. Classes that feature an ArrayList to aggregate
another class are very common. Methods in these classes are often going to use the ArrayList
operations to perform tasks. So we're letting the ArrayList do the hard work, and all we're
doing is sort of borrowing its stuff. We also are going to have classes is the group together
fields of different types, and they have a very simple pattern. Now writing these classes
is so simple you can almost do it mechanically. These two types of classes frequently work
together, just like they did with our menu and food item classes. So this is a common
pattern for software to take. Keep programming!