联系方式

  • QQ:99515681
  • 邮箱:99515681@qq.com
  • 工作时间:8:00-23:00
  • 微信:codehelp

您当前位置:首页 >> Python程序Python程序

日期:2020-10-22 08:36

Assignment 1
ippo 2020-2021
coupling java
Object cohesion
class ippo
unit test
hashmap constructor
method
exception
encapsulation
accessor mutator
intelliJ
model
Don’t panic! This document may look long, but it includes the material to help you understand the
underlying concepts, as well as the assignment exercises themselves. It should guide you step-by-step
though a fairly realistic Java application over a period of several weeks. The preparation in section 2
should take no longer than about an hour and we recommend that you do this as soon as possible to
check that everything is working correctly before you start on the following sections. A good solution
should be possible with no more than about one day’s work (eight hours), although you may need to
spend longer if you don’t have much previous programming experience. We strongly recommend that
you work on the assignment steadily over this time, rather than leaving it until the final week - this will
give you an opportunity to get help via the Piazza1
forum if you run into unexpected difficulties.
It is possible to obtain a good mark without attempting all of the tasks (?). Especially if
you don’t have a lot of previous experience, don’t spend an excessive amount of time on the
more difficult tasks. Certain tasks are essential for the various different grades though - so you
should pay careful attention to the marking criteria (appendix A).
1 The Application
For this assignment, you will be working with an application which displays images of the Munros.
These are the 282 highest mountain tops in Scotland and are named after Sir Hugh Munro who first
catalogued them. You can see the full list and hear the pronunciation of the Gaelic names on the Walk
Highlands2 web page (you will not be tested on the pronunciation).
The application itself is quite simple, but it will
give you some realistic experience with:
? Using “professional” development tools.
? Creating applications by “composing”3 objects
of different classes.
? Understanding and using classes created by
others, as well as the standard Java libraries.
? Integrating these with your own code.
? Working with remote services.
? Working with graphical user interfaces.
? Documenting and testing your code.
The images will be downloaded from various web sites, including Wikipedia4
, and some of my own
Photographs on the IPPO site.
1https://piazza.com/ed.ac.uk/fall2020/ippo
2http://www.walkhighlands.co.uk/munros/pronunciation
3
i.e. connecting together existing objects in different ways to create different applications.
4https://en.wikipedia.org/wiki/Munro
Revision: 20.18 06/10/2020
Assignment 1 (2)
2 Preparation
? [1] Before starting on the assignment, make sure that you have a working set of development tools,
and that you understand how to use them, by following the Getting Started tasks.
Real Java applications are constructed from large collections of classes. And most of the code will
usually be provided by other people - either by someone else working on the same (large) project, or as
part of a library or framework imported from elsewhere (such as the JavaFx graphics framework). To
give you a realistic experience for this assignment, we have provided a library containing some useful
classes for downloading and displaying images. You will be extending some of these, and writing new
classes which interact with them to produce an application for viewing photographs of the Munros.
To help you get started, we have also provided some template files for the application . . .
? [2] You should start by creating and linking a git repository containing the starter files. This is a
similar process to the creation of the simple repository in the Git Task:
1. Accept the assignment by selecting your UUN in the GitHub List5
. Remember to check your
UUN carefully and make a note of the repository URL.
2. Download and unzip the Zip file containing the template project.
3. In a terminal window, change in to the resulting ippo-assignment1 directory, initialise the
repository and add the downloaded template files:
? cd ippo-assignment1
? git init
? git add *
? git commit -m "template"
4. You can then push these initial files to your remote repository6
:
? git branch -M main
? git remote add origin url-of-your-repository
? git push -u origin main
The url-of-your-repository should be the full URL, ending in .git.
At the assignment deadline, we will pull your code directly from the repository for marking.
We will also check periodically that you are following good software engineering practice by
making regular updates. So you should remember to commit and push your changes to the
repository as you are working on the assignment - don’t wait until you think it is absolutely
“correct”, or “finished”.
You can now . . .
? [3] Import the template into a new IntelliJ project (in the same way as the Simple Example), and run
the sample application using Tasks ippo run from the Gradle panel.
This should display an interface which allows you to view a selection of images, similar to the one on
the first page. Ask for help on Piazza if you have problems getting this to work.
5https://ease.groups.inf.ed.ac.uk/cgi/ippo/2020/cgi/git/register/1
6Until October 2020, the default branch on GitHub was master, rather than main.
Paul Anderson 06/10/2020
(3)
If you see the error message “class file has wrong version” it is probably because
you are using the wrong version of the JDK - try going into the project settings and
changing the JDK version to 14.
Adding the project to IntelliJ and executing it with gradle will have created some temporary configuration
and cache files. You can see that these files have not been committed to git:
? git status
You could now add and commit these files, but you almost certainly do not want to include these in
your git repository - they are not “source” files and will be regenerated automatically when they are
needed.
? [4] Instead, you should . . .
1. Tell git to ignore these by creating a plain text file called .gitignore7
containing the following:
/.gradle/
/.idea/
/gradle/
/build/
gradlew
gradlew.bat
Be careful to include the correct punctuation in the file patterns. In the IntelliJ interface, you will
notice that the ignored files are shown in a different colour.
2. Finally, you will need to commit and push the .gitignore file itself:
? git add .gitignore
? git commit -m "ignore temporary files"
? git push -u origin main
When you make changes to your assignment, you should add the changed files and commit/push
them in the same way (with an appropriate comment). You may like to explore the VCS menu which
allows you to perform the git operations from the IntelliJ interface.
You now have a clean template project in your own git repository and you are ready to start work on the
assignment . . .
3 Understanding the Classes
The demonstration application is structured using three main classes:
? A View class which handles the interface (displaying the images, and detecting user input).
? A Service class which retrieves a picture from the remote service.
? A Controller class which manages the other classes and determines the overall behaviour of the
program.
3.1 Using Different Implementations
A well-designed application will allow classes to be easily replaced with different implementations. To
illustrate this, our library provides:
7https://git-scm.com/docs/gitignore
Revision: 20.18 06/10/2020
Assignment 1 (4)
? Two different View implementations - using buttons or menus.
? Two different Service implementations - Wikipedia and the IPPO photographs.
? Two different Controller implementations - one which allows specific images to be viewed, and
another which displays a random image and asks the use to guess the appropriate name (a “quiz”).
You can browse the Javadoc for these classes on the assignment web page. Notice that each of these
(View, Service, Controller) has several implementations and an interface8
specification which defines the
methods that each implementation must provide.
The library provides a mechanism for you to experiment easily with different combinations of these
implementations by changing values in a “property file”:
? [5] Experiment with the different properties:
1. Open the project file src main resources properties app.properties . Lines starting with # are
explanatory comments which are ignored by the system.
2. Change the View from ButtonView to MenuView and re-run the application. Notice how the
interface has changed but the other components remain the same.
3. Change the Controller from SimpleController to QuizController. The application
will show a random Munro. You can guess the name and the application will tell you whether you
are correct or not. Choosing New will display another Munro.
4. Change the Service from IPPOService to WikiService (this example may be clearer if
you change back to the SimpleController) and re-run the application. The application will
fetch the images from Wikipedia, so there will be different images for each mountain (and there
will be a slower response).
5. Reset the property file back to the original values for the following exercises.
This kind of flexibility is an important property of a good object-oriented design, and you
should think carefully about this when you come to create your own class design.
3.2 How Does This Work?
Figure 1
9
is a sequence diagram10 which shows a typical sequence of interactions between the objects:
? A simple Controller creates the View and Service objects.
? It then calls the View to add buttons for the required subjects to the interface. For each subject,
the View returns an identifier which can be used to identify that selection.
? Control then transfers to the View start() method to display the interface and wait for user
input.
? When the user makes a selection in the interface, the View calls the Controller’s select()
method, passing the identifier for the selected item.
? The Controller then calls the Service to fetch a picture for the corresponding subject.
? When the picture is returned, the Controller calls the View to display it.
? [6] Look at the provided code for SimpleController.java11 and follow the code using the
8The term interface here refers to a “Java interface” which has nothing to do with the “user interface”.
9
Icons from Flaticon licensed under Creative Commons BY 3.0.
10A sequence diagram is used to show the interactions between objects, and the order in which the interactions occur.
11ippo-assignment1/src/main/java/ippo.assignment1/controller/SimpleController.java
Paul Anderson 06/10/2020
(5)
SimpleController
Bu!onView
IPPOService
start()
getPicture
(subject)
picture
addSelection(…)
addSelection(…)
select(id)
showPicture
(picture)
new()
new()
id
id
Loop
start
Figure 1: A sequence diagram for the simple version of the PictureViewer.
above description.
Notice that the getPicture() method allows us to specify an index (as the second argument). In this
case, we always use a value of 1 which returns the first picture for the given subject. Some services
may have more than one image for the same Munro, and we could use different values for the index to
retrieve these other images.
We are now ready to write some actual code!
4 Writing a Controller
The property file makes it easy for us create and experiment with a new controller. Let’s start just by
making a small extension to the SimpleController . . .
4.1 Adding an Extra Option
The supplied class provides just three possible selections.
? [7] Let’s add a fourth one:
1. Copy the SimpleController.java class to BigController.java, and modify the code
to add a fourth option.
2. Change the property file to use BigController instead of SimpleController in the properties
file, and run the application to try out your controller.
3. Keep BigController.java for your submission.
Look at the IPPO page to find Munros which are available on the IPPOService.
Remember to change all occurrences of SimpleController in the new source file to BigController
Revision: 20.18 06/10/2020
Assignment 1 (6)
If you copy and paste the class using IntellIJs’ project menu, it will automatically rename these for you,
but remember to edit the comments so that they are meaningful as well!
4.2 Using a HashMap
You will notice that it is very tedious to add an extra option: you need to add the selection to the view,
and an extra clause to the conditional statement to test for it. In this case, the interface label is the same
as the search string (which may not always be the case), so you have to specify exactly the same name
in two places as well (which is a potential source of errors).
We can improve this by using a HashMap. If you are unsure about these, read the note on HashMaps,
and/or the book section on HashMaps before continuing.
? [8] Create a version of the controller which uses a HashMap to store the Munro names corresponding
to each selection identifier:
? Copy BigController.java to HashMapController.java to create a second new version
of the controller, and change the contents of the new file to match12
.
? Add the following import statement:
import ippo.assignment1.library.utils.HashMap;
? Declare a HashMap to store the correspondence between the selection identifier and the name.
You will need to think carefully about the required type for the HashMap.
? Make sure that you initialise the HashMap to an empty map, otherwise you will get an exception
(NullPointerException) when you attempt to access it.
? Write a method addSubject() which takes the name of a Munro, (a) adds a selection to the
interface, and (b) adds a corresponding entry to the HashMap (the method will be very short).
? You should now be able to rewrite the select() method very simply, and you should only need
to add one line (to the start() method) to add each extra Munro.
? Change the property file to test your HashMapController.
? Keep HashMapController.java for your submission.
You must use the IPPO implementation of the HashMap (above) which includes some code
to be used when testing your submission. Do not import java.Util.HashMap, or
Java.Util.* directly.
4.3 Reading Properties
Having the names of the Munros “hard-wired” into the source code means that they cannot be changed
without recompiling the code. And the user cannot change them without access to the source code either.
We could avoid this by specifying the list of Munros in the property file. For example:
controller.subjects = Beinn a’ Bheithir, Creag Meagaidh, ...
? [9] Create a third version of the controller which reads the list of Munros from the property file:
1. Copy the HashMapController.java to PropertyController.java and modify this
to read the list of Munros from the controller.subjects property in the property file.
12Do not edit the BigController file – you will need to submit both BigController and HashMapController.
Paul Anderson 06/10/2020
(7)
2. Configure the property file to use this controller. The property value above is available by default
from the library, so your application should display these Munros by default.
3. Set a new value for this property in your own property file. This this should override the default
and display your own list of Munros.
4. Keep PropertyController.java for your submission.
The (static) get() method of the Properties class can be used to retrieve the string value of the
property - look at the Javadoc for ippo.assignment1.library.utils.Properties. Having
obtained this string, you will need to (a) split the string at each comma to create a list (array) of names;
(b) remove any leading or trailing spaces from each name; and (c) iterate through the list adding each
item. Look through the Javadoc13 for the standard Java class String to find some helpful methods for
(a) and (b).
Notice that your code should be capable of reading an arbitrary number of Munros from the property
file. Make sure that it is not restricted to a fixed number.
5 Proxies
In this section, we will look at another way of composing objects. Let’s start with a motivating example:
imagine that we are dealing with a very unreliable service, which often returns an error, but usually
succeeds if the call is repeated a few times. We could handle this by simply having a loop which retries
failed calls a number of times. But where should we do this? If we include this code in the controller,
then we would have to duplicate the code in every controller. If we include it in the service, then we
would have to duplicate the code in every (potentially) unreliable service. Of course, in this case, there
isn’t much code involved, so this might not be significant. But in a real application, it may be. We can
solve this in a general way using a proxy class:
Look at the code for the RetryProxy in figure 2. The constructor for this requires that we specify
some base service object. The RetryProxy object saves this, and whenever we attempt to retrieve a
picture from the RetryProxy, it calls the base service repeatedly until it succeeds or it reaches some
maximum number of attempts. The RetryProxy itself conforms to the Service interface, so we can
use a RetryProxy wherever we can use any other service. This means that we can take any service
and “wrap” it with one of these proxy objects to create a version of the service which is more reliable.
The version of the RetryProxy in the library also has a constructor with no parameter (not shown
above). If this is called, the base service is determined from the properties file. This means that the
following properties will configure the application to use IPPOService service with an automatic
retry of any failed calls:
service = RetryProxy
proxy.retry.service = IPPOService
Of course, if you run the application with this configuration, it will be difficult to see any difference
because the service usually behaves reliably. But we can use another proxy class to illustrate that this is
working: The UnreliableProxy adds random errors to an existing service. Try this configuration
and notice the random failures:
service = UnreliableProxy
proxy.unreliable.service = IPPOService
13https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.
html
Revision: 20.18 06/10/2020
Assignment 1 (8)
private Service baseService = null;
public RetryProxy(Service baseService) {
this.baseService = baseService;
}
public Picture getPicture(String subject, int index) {
Picture picture = baseService.getPicture(subject, index);
int attempt = 1;
while (!picture.isValid() && attempt<=maxAttempts) {
picture = baseService.getPicture(subject, index);
++attempt;
}
return picture;
}
Figure 2: The core of the RetryProxy class.
getPicture
(subject)
picture
picture
picture
** fail **
IPPOService UnreliableProxy RetryProxy
getPicture
(subject)
getPicture
getPicture (subject)
(subject)
Figure 3: Recovering from a simulated unreliable service.
Now that we have an unreliable service, we can compose the two proxies to demonstrate that the
RetryProxy is indeed working! You can turn on the debugging messages for the various services
to display the sequence of events in the console:
service = RetryProxy
proxy.retry.service = UnreliableProxy
proxy.unreliable.service = IPPOService
proxy.debug = true
Figure 3 shows the sequence diagram for a typical interaction.
5.1 Writing a Cache Proxy
As well as being unreliable, network services can also be slow. In general, we can’t do anything about
this. But real applications often access the same data repeatedly, and we can improve the observed
performance of a slow service by simply keeping a local copy of any picture that we download and using
Paul Anderson 06/10/2020
(9)
this local copy if the same subject is requested again. This is called caching. Using a cache to reduce
the remote access in this way can also be cheaper for services which charge for access.
? [10] Write a cache proxy for the PictureViewer application:
1. You have seen how the UnreliableProxy can be used to create an unreliable version of a
service for testing. Similarly, the SlowProxy can be used to create a slow version of a service.
Configure the properties to create a slow version of IPPOService and notice the reduced
response time.
2. CacheProxy.java is a skeleton for a proxy which simply forwards all requests to the base
service unchanged. Configure the property file to use this proxy together with the SlowProxy.
You should not notice any difference in performance (i.e. it should still be slow).
3. Edit the CacheProxy.java and declare a HashMap to store the cache14. The values in the
cache will be the downloaded pictures. But you may need to think carefully about the keys – two
requests should only be considered the same if both the subject and the index are both the same.
The question of when two objects should be considered “the same” is not always easy.
You may like to read the note on Equality.
4. Now modify the getPicture() method: if the requested picture is in the cache, return the copy
from the cache; if the picture is not in the cache, download it from the base service and store it
in the cache before returning it. Test your cache with the SlowProxy – the first request for a
particular picture should be slow, but all subsequent requests for the same picture should be very
fast.
5. Keep CacheProxy.java for your submission and the demonstration.
Notice that the cache is only held in memory – if the application is restarted, then the contents of the
cache will be lost. A real application would probably store a copy of the cache on disk.
6 Testing
Beginning programmers sometimes think that “testing” is a chore which happens after the code has all
been written. But when you come to write larger programs, you will find it incredibly useful during
development to have a good test suite - this allows you to “refactor” and modify your code without
worrying whether your changes have broken some other part of the program. When you are designing
and coding a new class, you should think carefully about how it might be tested – otherwise it is easy to
create designs which are unnecessarily difficult to test.
JUnit is a standard framework for writing tests in Java. Both BlueJ and IntelliJ understand JUnit test
files and can run these in a special way which automatically records any failed tests.
6.1 Running JUnit Tests
The assignment template includes a Gradle task to run the JUnit tests, and CacheProxyTest which is
a simple JUnit test for the CacheProxy. This test checks that the proxy returns exactly the same object
whenever it is called with the same name and index – which is what we would expect from a working
cache proxy.
? [11] Experiment with this test:
1. Run the JUnit test with the Gradle task: Tasks ippo test . If your CacheProxy is correct, this
14Remember to import the HashMap from ippo.assignment1.library.utils, and not Java.Util
Revision: 20.18 06/10/2020
Assignment 1 (10)
test should pass.
2. If you (temporarily!) edit your CacheProxy so that it does not keep the copies in the cache,
and re-run the tests, the CacheProxyTest should fail. The test messages printed in the IntelliJ
console can be difficult to read, but you can view a formatted report by copying the URL which
appears in the console, and pasting it into a browser.
3. Remember to re-edit your CacheProxy so that it is working again (test it), and keep the source
file for your submission.
Notice that tests such as these can all be executed automatically. This is much easier than
having to manually run the application and watch the response time (which would simply not
be feasible if you had 100s or 1000s of tests).
6.2 Understanding the Cache Proxy Test
Look at the CacheProxyTest source file. You may find it helpful to sketch a sequence diagram - the
way in which the objects are configured for this test is interesting, and you may have to think carefully
to understand what is happening:
Notice that no view class is necessary, since we are not displaying anything - we are only calling the
service and making sure that it returns the “right” picture. But we do need a base Service object for
CacheProxy to call on. Rather than using a separate service class, the test class itself implements the
Service interface and provides a getPicture() method. When we create the cache proxy, we tell
it to use the test object as the base service by specifying this as the parameter to the constructor.
This means that whenever the cache attempts to call the base service, it will call the getPicture()
method of the test object. This method simply returns a new (empty) picture object every time it is
called. If CacheProxy returns a picture object which is equal to one that has been returned previously,
then it must have been cached. If it returns a new picture object, then it must have come directly from
the base service. Because the test class has control of the base service methods, we could also include
other code in the getPicture() method to control or record other properties of the picture. We say
that the CacheProxyTest object is acting as a mock service object.
The example equalityTest() simply makes two calls to CacheProxy object (with the same subject
and index) and confirms that the same picture object is returned both times. If the pictures were not
being cached, then we would expect two different objects to be returned (even though they may have the
same subject and index), and the test would fail.
6.3 Introducing Bugs
The BuggyProxy is a version of the cache proxy which contains some bugs. Some of the bugs will be
different for each student, and you must enter your student UUN by editing the build.gradle file:
// the ’uid’ is used when running the BuggyProxy
// you must replace the ’NONE’ with your own user-id (eg. ’s12345678’)
def uid = ’NONE’
Then you can configure the application to use this proxy by setting the following properties:
service = BuggyProxy
proxy.buggy.service = IPPOService
Now you can use the menu Tasks ippo (run buggy proxy) to simulate a proxy with a range of different
Paul Anderson 06/10/2020
(11)
bugs:
? A - No bug.
? B - Does not cache any images.
? C - An obvious bug.
? D - A bug which is different for different students.
? E - A more obscure bug which is different for different students.
? F - Throws an exception after some images have been displayed.
? G - “Hangs” after some images have been displayed.
? [12] Try running the application with these different cases to observe the effect.
Remember that the bugs may not all be obvious from the image - the bug may involve the image subject
(displayed at the top of the window), or even the index, which may not be obvious at all in this particular
application. You will have to interrupt the non-terminating case (G) using the square red button in the
top right of the IntelliJ interface.
Do not spend too long attempting to identify the bugs in cases D and E - these may be quite obscure.
6.4 Testing the Buggy Proxy
? [13] Create a JUNit test file for BuggyProxy:
1. Copy the CacheProxyTest.java class to BuggyProxyTest.java, and change the contents
so that it tests the BuggyProxy instead of the CacheProxy. You will need to add an
appropriate import statement.
2. If you run this test for the various cases with Tasks ippo (test buggy proxy) , it should pass on A
(because this does not introduce any bugs) and it should fail on B (because the BuggyProxy
does not cache any images in this case). But your test code will probably not detect the bugs in
the remaining cases.
6.5 Writing a New Test
? [14] Create an additional test for the BuggyProxy:
1. Extend the BuggyProxyTest to include an extra test for case C.
2. Your tests now should fail for this case, but make sure that they still pass in case A, and fail in
case B.
3. Keep the BuggyProxyTest.java for your submission and the demonstration.
? [15] You might like to try adding tests for some of the remaining cases - but these can be difficult
(especially E, F and G), and are not essential - so you should only do this if you have time after
completing the other tasks.
It is important that your classes can be tested independently from the rest of your application. When
your tests are run for marking, they will not have access to any of your other code. So . . .
Before submitting, check carefully that you do not have any unnecessary imports, or other
dependencies. For example:
? Do not depend on additional properties in the property file.
? Do not import additional files of your own.
Revision: 20.18 06/10/2020
Assignment 1 (12)
7 Readability
In the “real world”, it is not sufficient for code to just “work” - it also has to be easy for other people
to read and understand, easy to extend later, and sufficiently clear that there are no hiding places for
obscure bugs. Clear, readable code is a core requirement of the IPPO course - code which is difficult
to read will not pass, and higher marks require particularly clear and well-structured source code. So,
before you submit . . .
? [16] Look at the notes on code Readability and use this to review the readability of your code.
8 Worksheet
For a higher mark (see appendix A), you will need to demonstrate your understanding of the code, and
your ability to explain it clearly, by submitting a worksheet with answers to a few questions. Some of
these questions are quite challenging, so only attempt them if you are confident that you have completed
the other tasks well:
1. Explain why the controllers are hard to test in the same way that you tested the CacheProxy (i.e.
without relying on external services or views). Suggest how you might make a small modification
to the controller class to make this easier.
2. Explain why it is difficult to “compose” the RandomProxy and CacheProxy. i.e. to use these
class implementations to create a system which both fetches images from a random service and
caches the results. Suggest how you might change the way in which the properties are used to
make this composition possible.
? [17] If you think that you can provide good answers to these questions, create a document containing
your answers:
1. Make sure that your name and student number are clearly visible at the top of the worksheet.
2. Make sure that the answers are clearly numbered.
3. Make sure that your answers are clear and concise, with a good standard of English.
4. Convert the document to PDF format15 with the name worksheet.pdf.
5. Place the PDF document in the top-level directory of your project.
It is important that the name (worksheet.pdf), format (PDF), and location (top level directory)
of the worksheet are all correct - otherwise we will assume that you have not submitted
this task.
9 Finally . . .
The assignment files will be automatically extracted from your Git repository precisely on the
assignment deadline. Changes made after the deadline will not be included.
? [18] Remember to add/commit/push all of your files (to the main git branch), so that they appear
in your remote repository. In particular, the following files will be required for the marking:
(a) BigController.java
(b) HashMapController.java
15It is good practice not to depend unnece

版权所有:留学生编程辅导网 2021,All Rights Reserved 联系方式:QQ:99515681 电子信箱:99515681@qq.com
免责声明:本站部分内容从网络整理而来,只供参考!如有版权问题可联系本站删除。