SDM Lab 5: Refactoring, round-tripping and introducing design patterns
(updated 28/10/19)
This page gives brief notes and instructions on the guided lab session. It
will have omissions and bugs - you need to listen as well as read!
Introduction
The main aim of this lab is to let you practice refactoring, which is
improving the design of existing code. We'll explore Eclipse's capability
to support this activity. One reason for refactoring is to introduce a
design pattern. Here modelling can be helpful to understand what's going
on; so we'll also have a look at the facilities in Papyrus for keeping a
UML model and a Java project in sync, known as "round-tripping". This is an
example of a bidirectional transformation, to be discussed later.
You are welcome to work individually or in small groups, as you prefer -
but as always, if you work in a group, make sure you all understand
everything done.
Note: You can either use the papyrus version of Eclipse, or the
modelling edition: it shouldn't matter much today.
Getting started: loading Java code
We're going to play with a very simple Java project I found on github:
https://github.com/JosipRebrnjak/Quiz-Java-Swing.
It's a very simple quiz application; for added interest, it's in Croatian.
Use menus: File, import, Git, project from git. Give the URI you find on
the project's git page. Accept all defaults. (I have a github account and
Eclipse pre-filled my details in one of the boxes: I don't think
you will need one, but if you do, it's a good thing to have anyway...)
Check that this very simple project builds correctly and that you can run
it (right click on Main.java, select Run as Java Application). How well you
score may depend on how well you speak Croatian... Read the code and try to
understand roughly how it works.
Basic refactorings
Look in class Main at the variable called ant. That's not a great name for
English-speaking readers. Pick a better name, and use the Rename option
from the Refactor menu to change it. Note that the name changes everywhere.
Feel free to change other names too, based on your understanding of the
code (and to translate the Croatian to English if you wish!)
Automatically relating Java and UML
- Read about the Papyrus Software Designer component here. Install
it. The method I used was the Help -> Install new software one; it takes a
while, and you have to restart Papyrus at the end, so you may want to start
it installing and then read about it, rather than the other way round.
Don't worry about it being unsigned.
- Create a new Papyrus project. Select the src directory of the Java
project. Click the tree-like icon in the top tool bar, whose hover text is
"Reverse Java code into current model".
- You may be disappointed that no diagram appears! However, you will see
in the model explorer that you do have a large collection of UML model
elements created. Click on a few and look at their properties, e.g., see
what types attributes have.
- To make a diagram, create a new class diagram (if you didn't when you
set up the Papyrus project). Right click in it and select Filters, then
Synchronized with model.
- If you did this just under Root Element, you probably got a boring
diagram containing only packages, not classes. You can show the contents of
a package inside a UML package symbol, but since here we have many layers
of packages before we reach classes, that leads to a cluttered diagram. You
can do it by dragging the packages from the model explorer to the diagram,
if you wish.
- It's probably better to open the packages in the model explorer all
the way down to package questions (a bit more interesting than main) and
then create a new class diagram at that level. If you then right click in
that and select Filters, you'll see classes.
- To show the features of the classes, select a class, right click,
select Filters, select Show contents, tick the things you want shown. (I
suggest just Attributes and Operations. To think about: what are the
"nested classifiers" it offers you?)
- Note that some attributes and operations are underlined. What does
this mean? If in doubt, look at the corresponding Java code.
- At your leisure, explore what else Papyrus can do for you: we don't
have time to go into depth in the lab. For example, you'll see that
QuestionChange has an attribute of type Question, which contradicts the
rule I gave you that attributes were only for things of types you weren't
developing (because relationships between classes in your design should be
shown graphically, e.g. by associations - they aren't yet, here). If you
select that attribute, though, right click and pick Utils, Create
Association from Property, you'll get the appropriate association, which we
really ought to be showing instead, created in the model explorer. To get
the association displayed, drag the new model element from the model
explorer to the pane where the class diagram is displayed, and it will leap
into existence.
- If you right click in a class diagram and choose Designer, then
Generate Code, what happens...? (Maybe try generating C++ code rather than
Java code. Is this a good way to get a Java application translated into
C++? Have an explore...)
- You might like to create a new, very simple Papyrus project with a
very simple UML class diagram, and try the Generate Code option from there,
to understand better. When you create the project, choose Browse Registered
Profiles and Papyrus Java, and then right click on the root element (in the
Model Explorer) and choose Import -> Import Registered Package and Java
Types, and this will allow you to choose standard Java types for the
attributes in your class diagram. (If you don't do this, you may find that
Generate Java Code does nothing!)
Complicating the design
Now for this lab, we're going to return to just using the Java source code,
but an interesting
challenge for you for later is to try this
exercise again and see whether you can do the refactoring at the UML level
and get Papyrus to update the Java code for you automatically...
In class Main, look at the percentages method. It's responsible for the
progress bar that shows you you are 12 percent, 24 percent etc. through the
quiz. Before we go any further, rename this method to progress using the
refactoring menu.
Now, suppose you're undecided about whether this is a good way to show
progress. Perhaps some users might prefer to see the number of questions
they've answered, or even a running total of their score. How can we permit
this?
In looking into this, you may notice that the number of questions to be
asked, 8, is hard-coded into the Main class. Let's (naively) fix that.
Select one of the 8s, and choose Extract Constant from the Refactor menu;
call the new constant NUMBER_OF_QUESTIONS. Look at what happens.
A key part of refactoring is that you make SMALL changes to your
program, testing after each change. Ideally you should have a proper test
suite; today, you can just test by running the program and checking that it
behaves as you expect.
Next, notice that nothing other than NUMBER_OF_QUESTIONS is ever passed to
the progress method (that used to be called percentages before you renamed
it). So take that argument out, and use NUMBER_OF_QUESTIONS internally to
its code.
Make an int attribute, say progressDisplayOption, of class Main, and use
the value of this to control which branch of an if statement inside method
progress is taken. One branch should be the current behaviour of that
method. In the other branch, use method setString of JProgressBar to
display a string like "2 out of 8" after the user has answered 2 of the 8
questions.
Introducing a design pattern
Suppose that such was the success of your enhanced progress display options
that it turns out you may need several more. This is going to get messy,
and we don't want to modify the Main class every time we add a new progress
display method. To improve matters we will use the Strategy design
pattern.... but we're getting ahead of ourselves.
First, factor out everything to do with displaying progress into a new
class ProgressDisplay. You'll need to give it an appropriate constructor...
what will the constructor have to take as argument? Your ProgressDisplay
class will have a method progress, and you'll remove this method from Main.
Test that you still have all the functionality, including both kinds of
progress display, and that your Main class no longer refers to a
JProgressBar at all.
Next, make two subclasses of ProgressDisplay, say PercentageProgressDisplay
and NumberProgressDisplay. They each implement progress differently.
ProgressDisplay itself becomes an abstract class, that does not implement
progress.
But how will your code decide which class to create an instance of? Do we
need to be able to switch between the two kinds of display at run time?
Think about this! (There are different answers... and the question about
new in the sample exam paper is related. I'll leave this open here... do
something, and think about the implications...)
Over to you...
A bad smell in this code is the long stream of ifs in the actionPerformed
method of class Main. Can you improve this design and refactor the code
appropriately?
Going further
I picked a very simple Java project to base this lab on... but now that
you've seen these tools on a simple example, you might like to try them out
on something less trivial. Perhaps you have some Java project you've built
in some other course, or you could go back to the Borg project, or pick
some other open source project...
This page is maintained by
Perdita Stevens (
perdita@inf.ed.ac.uk
)