Change is inevitable. No matter how much you like your software right now, it’s probably going to change tomorrow. And the harder you make it for your software to change, the more difficult it’s going to be to respond to your customer’s changing needs. In this chapter, we’re going to revisit an old friend, try and improve an existing software project, and see how small changes can turn into big problems. In fact, we’re going to uncover a problem so big that it will take a TWO-PART chapter to solve it!
Fresh off the heels of selling three guitars to the rock group Augustana, Rick’s guitar business is doing better than ever—and the search tool you built Rick back in Chapter 1 is the cornerstone of his business.
We’ve talked a lot about good analysis and design being the key to software that you can reuse and extend... and now it looks like we’re going to have to prove that to Rick. Let’s figure out how easy it is to restructure his application so that it supports mandolins.
Take a close look at the new Instrument
class that we created:
Abstract classes are placeholders for actual implementation classes.
Instrument is an abstract class: that means that you can’t create an instance of Instrument
. You have to define subclasses of Instrument
, like we did with Mandolin
and Guitar
:
The abstract class defines behavior, and the subclasses implement that behavior.
We made Instrument
abstract because Instrument
is just a placeholder for actual instruments like Guitar
and Mandolin
. An abstract class defines some basic behavior, but it’s really the subclasses of the abstract class that add the implementation of those behaviors. Instrument
is just a generic class that stands in for your actual implementation classes.
Mandolins and guitars are similar, but there are just a few things different about mandolins... we can capture those differences in a MandolinSpec
class:
It’s OK if you don’t know anything about mandolins, or didn’t figure out the different properties in the MandolinSpec class. The main thing is that you realized we probably need a new class for mandolins and their specs. If you came up with using an Instrument interface or abstract class, all the better!
What do you think about this design? Will it do what the customer wants it to do? How flexible is it? Do you think software designed like this will be easy to extend and maintain?
_____________________________________________________________________
_____________________________________________________________________
_____________________________________________________________________
It looks like all that work on design back in Chapter 1 has paid off; it took us less than 10 pages to add support for mandolins to Rick’s search tool. Here’s the completed class diagram:
Now that you’ve added abstract classes, subclasses, and a new kind of association, it’s time to upgrade your UML and class diagram skills.
We can start off by creating a new class, Instrument
, and making it abstract. Then we put all the properties common to an instrument in this class:
Next we need to rework Guitar.java
, and create a class for mandolins. These both extend Instrument
to get the common instrument properties, and then define their own constructors with the right type of spec class:
With the instruments taken care of, we can move on to the spec classes. We need to create another abstract class, InstrumentSpec
, since so many instruments have common specifications:
With InstrumentSpec
coded up, it’s pretty simple to write the GuitarSpec
class:
After seeing GuitarSpec
, MandolinSpec
is pretty simple. It’s very similar, with the addition of a member variable to reference the mandolin’s style (like “A” or “F” style), and a slightly different matches()
method:
All that’s left is to update the Inventory
class to work with multiple instrument types, instead of just the Guitar
class:
At this point, you’re ready to try out Rick’s improved app. See if you can update FindGuitarTester on your own, and see how things are working with these design changes.
You’ve made some MAJOR improvements to Rick’s app
You’ve done a lot more than just add support for mandolins to Rick’s application. By abstracting common properties and behavior into the Instrument
and InstrumentSpec
classes, you’ve made the classes in Rick’s app more independent. That’s a significant improvement in his design.
Great software isn’t built in a day
Along with some major design improvements, we’ve uncovered a few problems with the search tool. That’s OK... you’re almost always going to find a few new problems when you make big changes to your design.
So now our job is to take Rick’s better-designed application, and see if we can improve it even further... to take it from good software to GREAT software.
One of the best ways to see if software is well-designed is to try and CHANGE it.
If your software is hard to change, there’s probably something you can improve about the design. Let’s see how hard it is to add a couple of new instruments to Rick’s app:
If ease of change is how we determine if our software is well-designed, then we’ve got some real issues here. Every time we need to add a new instrument, we have to add another subclass of Instrument
:
Then, we need a new subclass of InstrumentSpec
, too:
Then things start to really get nasty when you have to update the Inventory
class’s methods to support the new instrument type:
It looks like we’ve definitely still got some work to do to turn Rick’s application into great software that’s truly easy to change and extend. But that doesn’t mean the work you’ve done isn’t important... lots of times, you’ve got to improve your design to find some problems that weren’t so apparent earlier on. Now that we’ve applied some of our OO principles to Rick’s search tool, we’ve been able to locate some issues that we’re going to have to resolve if we don’t want to spend the next few years writing new Banjo
and Fiddle
classes (and who really wants to do that?).
Before you’re ready to really tackle the next phase of Rick’s app, though, there are a few things you need to know about. So, without further ado, let’s take a quick break from Rick’s software, and tune in to...
Did you get this? You should have asked this as the question for the answer in OO CATASTROPHE: Objectville’s Favorite Quiz Show.
Suppose you’ve got an application that has an interface, and then lots of subclasses that inherit common behavior from that interface:
Coding to an interface, rather than to an implementation, makes your software easier to extend.
Anytime you’re writing code that interacts with these classes, you have two choices. You can write code that interacts directly with a subclass, like FootballPlayer
, or you can write code that interacts with the interface, Athlete
. When you run into a choice like this, you should always favor coding to the interface, not the implementation.
By coding to an interface, your code will work with all of the interface’s subclasses—even ones that haven’t been created yet.
Why is this so important? Because it adds flexibility to your app. Instead of your code being able to work with only one specific subclass—like BaseballPlayer
—you’re able to work with the more generic Athlete
. That means that your code will work with any subclass of Athlete
, like HockeyPlayer
or TennisPlayer
, and even subclasses that haven’t even been designed yet (anyone for CricketPlayer
?).
“What is_______________________?”
We’ve talked a fair bit about encapsulation already, in terms of preventing duplicate code. But there’s more to encapsulation than just avoiding lots of copy-and-paste. Encapsulation also helps you protect your classes from unnecessary changes.
Anytime you have behavior in an application that you think is likely to change, you want to move that behavior away from parts of your application that probably won’t change very frequently. In other words, you should always try to encapsulate what varies.
It looks like Painter
has two methods that are pretty stable, but that paint()
method is going to vary a lot in its implementation. So let’s encapsulate what varies, and move the implementation of how a painter paints out of the Painter
class.
“What is_______________________?”
You already know that the one constant in software is CHANGE. Software that isn’t well-designed falls apart at the first sign of change, but great software can change easily.
The easiest way to make your software resilient to change is to make sure each class has only one reason to change. In other words, you’re minimizing the chances that a class is going to have to change by reducing the number of things in that class that can cause it to change.
When you see a class that has more than one reason to change, it is probably trying to do too many things. See if you can break up the functionality into multiple classes, where each individual class does only one thing—and therefore has only one reason to change.
You’re ready to tackle Rick’s inflexible code now
With a few new OO tools and techniques under your belt, you’re definitely ready to go back to Rick’s software, and make it a lot more flexible. By the time you’re done, you’ll have used everything you’ve just learned on OO Catastrophe, and made it easy to change Rick’s application, too.
Ever wished you were just a bit more flexible? When you run into problems making changes to your application, it probably means that your software needs to be more flexible and resilient. To help stretch your application out, you’re going to do some analysis, a whole lot of design, and learn how OO principles can really loosen up your application. And for the grand finale, you’ll see how higher cohesion can really help your coupling. Sound interesting? Turn the page, and let’s get back to fixing that inflexible application.
Loaded up with some new OO principles, we’re ready to tackle making Rick’s application well-designed and flexible. Here’s where we left off, and some of the problems we’ve discovered:
Frank: Yeah, it’s a pain, but I don’t see any way to get around it. We have to let Rick’s clients search for each different type of instrument somehow.
Jim: I still don’t see why we can’t have just one search() method that takes in an InstrumentSpec. Wouldn’t that cut down on all those different versions of search()?
Joe: Well, it would, but we still don’t have any way to return multiple types of instruments. If the client provides a GuitarSpec, it’s never going to match a BanjoSpec or MandolinSpec. So the list returned from search() will always have only the type of instrument that the client’s spec is for.
Jim: Because we can’t instantiate InstrumentSpec, right? It’s an abstract class, so we have to create a MandolinSpec, or a BanjoSpec, or whatever.
Frank: So maybe that’s the problem... besides, shouldn’t we be coding to an interface like InstrumentSpec, not an implementation like GuitarSpec or BanjoSpec?
Joe: Hmmm. I hadn’t thought about that, but you’re right; we really should be focusing on the interface, and not all those implementation classes.
It seems pretty clear that there’s a problem with the way we’re handling searches for Rick’s clients. We could make InstrumentSpec
a concrete class, but would that solve all our problems?
Let’s take what we’ve figured out about turning InstrumentSpec
into a concrete class, and see if it makes the design of Inventory
any better.
Even though search()
is looking better, there are still some real problems with all the instrument subclasses, and the addInstrument()
method in Inventory
.
Remember, we originally made Instrument
abstract because each instrument type was represented by its own subclass:
But the reason you usually create a subclass is because the behavior of the subclass is different than the superclass. In Rick’s application, is the behavior of a Guitar
different than that of an Instrument
? Does it function differently in his application than a Mandolin
or Banjo
?
All the instruments—at least from Rick’s perspective—behave the same. So that leaves only two reasons to have subclasses for each instrument type:
If we were writing a system that represented how these instruments played, we might need subclasses to handle behavior like pluck(), strum(), or frail().
Because the Instrument
class represents a concept, and not an actual object, it really should be abstract. So we have to have subclasses for each instrument type.
Each different type of instrument has different properties, and uses a different subclass of InstrumentSpec
, so we need an instrument-specific constructor for each type of instrument.
This looks like another case where we’re coding to an implementation instead of an interface. So this isn’t a good reason to keep Instrument abstract.
These seem like pretty good reasons (well, at least the first one does), but we’re ending up with lots of extra classes that don’t do much... and that makes our software inflexible and difficult to change. So what do we do?
Remember the second step in writing great software, from back in Chapter 1:
Since Rick’s app already does what it needs to do (Step 1), we’re ready to try and make his software more flexible.
Apply basic OO principles to add flexibility.
Joe: Yeah, you’re talking about encapsulating what varies, right?
Frank: Exactly! And we know that the properties for each instrument are what varies in the application.
Jim: I thought we’d been over this; that’s why we have all those subclasses of Instrument, like Guitar and Mandolin. So we can represent the differences between each instrument.
Frank: But that really didn’t help... and besides, the behavior of each instrument doesn’t vary, so do we really need subclasses for each one?
Joe: So you’re saying we would make Instrument a concrete class, instead of being abstract, right? And then we can get rid of all those instrument-specific subclasses.
Jim: But... I’m totally confused. What about the properties that vary across each instrument?
Frank: What about them? The Instrument class has a reference to an InstrumentSpec, and all the property differences can be handled by those classes. Look:
One of the hardest things you will ever do is to let go of mistakes you made in your own designs. In Rick’s search tool, it doesn’t make sense to have separate Instrument
subclasses for each type of instrument. But it took us almost 30 pages (and 2 parts of Chapter 5) to figure that out. Why?
Because it seemed to make sense at the time, and it’s HARD to change something you thought was already working!
Code once, look twice (or more!)
Keep looking over your designs when you run into problems. A decision you made earlier may be what’s causing you headaches now.
It’s easy to rip apart someone else’s code, but you’ve got to learn to look at your own code, and identify problems. This is also where peer review, having fellow programmers look at your code, can really be a lifesaver. Don’t worry if you have to make changes; a better-designed application will save you tons of time in the long run.
Design is iterative... and you have to be willing to change your own designs, as well as those that you inherit from other programmers.
Let’s kill all those instrument-specific subclasses:
We also probably need a new property in each instrument to let us know what type of instrument it is:
Joe: But we just did that... we made Instrument concrete, and got rid of all the instrument-specific subclasses.
Jill: Actually, I think that’s really only the first step. What really varies in Rick’s software?
What varies in Rick’s app?
Frank: We’ve gone through this already: the properties for each instrument are what vary.
Jill: So can we encapsulate them somehow?
Joe: We already have: we used the InstrumentSpec class for that.
Frank: Wait a second, Joe. We used InstrumentSpec because those properties were used by both clients and instruments. So that was more about duplicate code...
Jill: Yes! That’s my point... the properties inside InstrumentSpec vary, too. So maybe we need to add another layer of encapsulation.
Joe: So since the properties of each instrument vary, we should pull those out of InstrumentSpec? It’s almost like double-encapsulation or something.
Jill: Sort of... we encapsulate the specifications common across client requests and instruments from the Instrument class, and then we encapsulate the properties that vary from the InstrumentSpec class.
This really isn’t an OOA&D term, so don’t be surprised if your professor looks at you funny if you use it in class.
Let’s look at the layer of encapsulation we already have, and then see how we can add a little more encapsulation to get those properties that vary out of the InstrumentSpec
class.
Since some of these properties vary, we want to move them out of the InstrumentSpec
class. We need a way to refer to properties and their values, but not have those properties hardcoded into the InstrumentSpec
class. Any ideas for how we could do that?
What type(s) do you think you could use to represent properties and access their values, but not have to change your InstrumentSpec class to support new properties?
_____________________________________________________________________
_____________________________________________________________________
_____________________________________________________________________
By encapsulating what varies, you make your application more flexible, and easier to change.
What did you come up with on the last page to store properties? We decided that using a Map
would be a great way to handle various types of properties, and still be able to easily add new properties at any time:
Anytime you see something that varies, you should look for a way to encapsulate. In the case of InstrumentSpec
, we realized that the properties of an instrument vary.
When you have a set of properties that vary across your objects, use a collection, like a Map, to store those properties dynamically.
You’ll remove lots of methods from your classes, and avoid having to change your code when new properties are added to your app.
Let’s take one last look at how our new Instrument
and InstrumentSpec
classes work in practice. Here’s where we are with the design right now:
If you were accessing a guitar, and wanted to know who built it, here’s how you could do that:
We’ve almost got ourselves a great piece of software. Let’s follow through on our new design ideas, starting with a new enumerated type for each instrument type:
With the changes to Instrument
and InstrumentSpec
, our Inventory
class starts to get much simpler:
We’ve made a ton of changes to Rick’s application... and it’s easy to forget what we’ve been working towards. Look at the class diagram below, though, and see how much simpler Rick’s application is now:
Rick’s software looks a lot better than it did way back at the beginning of this chapter—and it sure looks better than when we added all those subclasses for banjos and mandolins. But we’ve still got to make sure his search tool actually works! So let’s update our test class, and check out how searches work with the new version of Rick’s software:
Be sure you’ve added all the instruments shown on the last page to your initializeInventory()
method in FindInstrument.java
, and then compile all your classes. Now you’re ready to take Rick’s software for a test drive...
...well, almost. First, you need to figure out what a search based on the current version of FindInstrument
should return. Here’s the set of preferences that Rick’s current client has supplied:
Based on those specs, look over the instruments shown on the last page, and write in which guitars, mandolins, and banjos you think Rick’s search tool should return:
How easy is it to change Rick’s software?
Is Rick’s software really well-designed?
And what the heck does cohesive mean?
You may not realize it, but we’ve already talked about cohesion in this book. Remember this?
Cohesion is really just a measure of how closely related the functionality of the classes in an application are. If one class is made up of functionality that’s all related, then it has only one reason to change... which is what we already talked about in OO CATASTROPHE!
Here are the classes we talked about when we made sure each class had only a single reason to change:
So have our changes to Rick’s software resulted in high cohesion? Are our objects loosely coupled? And can we make changes easily? Let’s take a look:
Great software is usually about being good enough.
It’s hard to know when to stop designing software. Sure, you can make sure that your software does what it’s supposed to do, and then start working on increasing the flexibility and cohesion of your code. But then what?
Sometimes you just have to stop designing because you run out of time... or money... and sometimes you just have to recognize you’ve done a good enough job to move on.
If your software works, the customer is happy, and you’ve done your best to make sure things are designed well, then it just might be time to move on to the next project. Spending hours trying to write “perfect software” is a waste of time; spending lots of time writing great software and then moving on, is sure to win you more work, big promotions, and loads of cash and accolades.
Make sure the customer is happy
Before you ever leave a project, you always want to make sure your software does what it’s supposed to do.
Make sure your design is flexible
Once you’ve got functionality down, move on to making good design decisions, using solid OO principles to add flexibility.
If you’ve done both of these things, it may just be time to move on... to the next project, the next application, even the next chapter!