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

  1. 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.
  2. 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".
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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?)
  8. Note that some attributes and operations are underlined. What does this mean? If in doubt, look at the corresponding Java code.
  9. 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.
  10. 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...)
  11. 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)


Home : Teaching : Courses : Sdm 

Informatics Forum, 10 Crichton Street, Edinburgh, EH8 9AB, Scotland, UK
Tel: +44 131 651 5661, Fax: +44 131 651 1426, E-mail: school-office@inf.ed.ac.uk
Please contact our webadmin with any comments or corrections. Logging and Cookies
Unless explicitly stated otherwise, all material is copyright © The University of Edinburgh