Just as in the real world, characters (people and animals) have both state (i.e., data / variables, e.g., where they are in the world and their mood), and are capable of actions (i.e. methods, e.g., walking, eating, etc.). We want our computational objects to encapsulate both data and functions (called methods when they are associated with an object).
For this project, you will have to edit a design document and refactor a given code base. In doing this project, you will get practice with basic UML diagrams and gain experience using methods to specify an object's behavior.
For this project you will be provided the code for a virtual world program
and a UML diagram of the classes used to structure this program. The program,
as given, is written in a non-object-oriented style like that seen in
CSC 101. In particular, the vast majority of the functionality is defined
as static
methods in the Functions
class (with a
few methods defined in EventComparator
, Point
,
and VirtualWorld
). You should take some time to skim the
provided code to get a basic sense of how it is organized. (You might
question the quality of this design (which is great!), especially if you have
some experience with object-oriented design; you should, however, note that this
is a perfectly valid approach (though some parts are intentionally structured
for later improvements) that might actually serve you well in a language that
does not directly support object-oriented programming, such as C in CPE 357.)
Overall, your goal is to move most of the methods (but not all of them) out of Functions.java
and into the appropriate classes (encapsulation) and to make instance variables private (data hiding).
You must identify the behavior associated with each class (i.e.,
the behavior exhibited by instances of the class) and move that behavior
from the standalone static methods in Functions.java
to
(static or non-static, as appropriate) methods defined within the class.
For this assignment, you will not add new functionality (aside from some
accessor-getter/mutator-setter methods, only as needed).
It is tempting to just look at the names of the methods, but the biggest hint on where to place the methods will be the parameters and which passed in object is directly using its data the most.
You are encouraged to develop the UML design document first. When you start refactoring the code, you are encouraged to implement the refactoring incrementally so that your refactored program executes properly at each step.
Functions.java
to the appropriate class
For this project we will use the IntelliJ IDE. There is some overhead in setting up the IntelliJ project, but after setup it is relatively intuitive to use. Follow these setup instructions to clone the git project into intelliJ and run the project. (If you would like to use another IDE that is allowed, though I cannot gaurantee the same refactoring tools / shortcuts I will demo in class will be available in these other IDEs. Additionally, I won't provide setup support for other IDEs, but you are welcome to get help from your peers).
The provided source code relies on the processing.org API for the graphical interface. To use this API external to the Processing environment, you will need the processing-experimental.jar file for both compilation and execution (included in the starting files for the project GitHub repository).
The world should look (more or less) like this Video, though each run will have a bit of randomization changing how it runs.
Start by getting a copy of the UML diagram of the project as-is. This is available in the source code you downloaded: ForestProj.drawio
The provided UML diagram was created using the online diagram editor draw.io.
Before doing anything else, make a copy of the file and call it Methods.drawio
. This Methods.drawio
file is the one you will be editing as part of this design assignment. To open the file for editing, navigate to draw.io and select File --> Import from --> Device. Then choose the Methods.drawio
file in the file dialog that appears.
To edit the list of attributes and methods for a class, double-click in the class and edit by typing.
Please take a moment to load the given diagram into draw.io and take a look at the general structure of the project. We will be using this code base for the rest of the quarter so please take the time to make friends with it.
Your first task is to modify the UML such that the static methods
in Functions
class are moved to their appropriate classes. Most (but not all) should be turned into instance methods when moved (meaning you will remove the static
modifier).
As you are working on this, you will want to refer to the actual code to decide where methods belong. For the
next part of this assignment you will in fact refactor the code based on your UML design.
In general, this assignment will take some careful thought and design, so take the time to read the provided code and to plan your actions before making modifications (some tips for identifying methods are given later in this document). Read, think, design, and then code.
Note that good design is somewhat subjective. Even so, for the vast majority
of the methods, there is a single "correct" class into which the method should
be moved. For a small number of methods, one could reasonably argue in favor
of a few different classes. For example, there are multiple classes with which
one could reasonably associate the adjacent
method. Do your best
to make reasonable decisions based on the design discussion in lecture and
document these decisions in your design document.
With the exception of some constant (static final
) values,
all data attributes should be private
and, when possible,
final
. (Point
is the exception to this since
each value acts as a constant value akin to an integer. You can leave x and y public in Point
.)
Methods should also be private
unless public
access is necessary (i.e., it is used outside of the defining class).
For this project, every method should be either private
or public
(it is often better to avoid the default of
package-protected).
To mark something (a method or variable) as private in the UML, change the +
symbol next to the variable or method name to a -
symbol. To mark something as an instance method (as opposed to static), remove the static
after the method name.
The provided source code uses objects to hold data; these objects have no methods (no behavior). Your task for this assignment is to identify the behavior associated with objects (instances) of a class and move this behavior from standalone functions to methods defined within the class.
Reasonable steps for this assignment include:
Run the program. You can use -fast
, -faster
,
-fastest
on the command-line to increase the rate at which
actions are executed. You can use the arrow keys to shift the view of
the virtual world.
Read (skim) the source code.
View the provided UML diagram to see the name of each class and the data stored within instances of each class.
Identify methods that interact with the data stored within an object (and identify the object's class).
Match behavior to classes.
When looking for good matches of functions with classes, consider the following hints:
If the attribute of an object is accessed directly via a dot (e.g., entity.name), then either the function in which this access appears should be defined in the object's class, or the access should be done via an accessor method.
If a method determines its behavior based on the "kind"
(e.g., the the various values of ActionKind
or
EntityKind
)
of object it is manipulating, then this method should likely
be moved into that object's corresponding class (do not
introduce separate classes at this time).
There are some methods that may remain as static methods, but that are reasonably moved into another class (these are often considered "utility" methods derived from decomposing a problem into simpler methods).
There are some methods that will reasonably remain as static
methods in the Functions
class because there are
not yet appropriate classes for them.
Additional, programming-specific, tips are given in the latter half of this document for when you actually move the methods in the source code.
The "design document" deliverable is the Methods.drawio
UML diagram (this is a great way to plan your course of action before moving and refactoring a messy code base) and a design txt
file.
In a plain text file (named DESIGN.txt
) you should
list each method that you feel could be reasonably placed in multiple
classes and note 1) the class you selected and 2) the reason for that
selection. You do not need to add many functions to this file - I will go over which ones you may want to include in class.
Now that your design is done, you need to actually make these changes in your code base. You must refactor the methods from the Functions
class
to move them into the appropriate classes as previously discussed. As
each method is moved, you will need to make modifications to the code
that uses the method.
Your refactoring should mirror the work done for your design document (UML diagram).
Your refactoring must not add or remove any functionality. Your refactoring may add accessor/mutator methods, but only as needed. The resulting program must work as before.
It is not sufficient to simply move the static methods from
Functions
to the other classes and then continue to invoke
them as public static
methods. For instance, if you determine that a
method works primarily on data within an Entity
object, then
the method must be made non-static and the explicit Entity
argument will be replaced by the implicit this
. This
modification will necessitate appropriate changes to the invocation of
the method.
As an example, moving the following (fake) method into Entity
will change it as shown.
class Functions { public static void turnAround(Entity entity, int numRotations) { ... entity.id ... } ... // invocation of turnAround turnAround(entity, 20); }
becomes
class Entity { public void turnAround(int numRotations) { ... this.id ... } } ... // invocation of turnAround entity.turnAround(20);
Move one method at at time, and get to code to compiler after each method you move before moving another. This will significantly decrease the stress of refactoring. Additionally, commit your code after every few methods you've moved. Make sure the code compiles and you've commited your changes each time you take a break from working on the project.
You can use the compiler to help you with your refactoring. In particular, as you make changes, the compiler will flag now invalid uses of moved methods. This serves two purposes. The first, and arguably most important, is gaining an understanding of the error messages that the compiler reports and the reasons for such error messages. Nobody enjoys seeing error messages, but quickly interpreting and addressing such errors will improve your workflow.
The second purpose for using the compiler as an aid is that it can quickly identify all parts of a code base affected by a change. This is incredibly beneficial when working with unfamiliar code. (Many IDEs also provide similar support even without explicitly compiling.)
Consider the following more specific tips.
WorldModel
relies on Entity
). Start
your refactoring by moving methods into those classes that depend on
the fewest other classes.
private
. Compile the program
to determine which methods attempt to access these private attributes. If you are using IntelliJ, explore using the
code > Generate...
feature to add getters and setters as needed.
Refactor > Move
feature. Though this feature will not
make all of the necessary modifications, it will help with the task.
Be ware that overuse of this feature may interfere with the learning
objectives; but using this feature after manually moving a few methods
will save you time.
static
), remove the
target object from the parameter list (and change all uses within the
method to this
, implicitly or explicitly). Compile the
program to determine where the method was invoked.
With the exception of some constant (static final
) values,
all data attributes should be private
and, when possible,
final
. (Point
is the exception to this since
each value acts as a constant value akin to an integer.)
Methods should also be private
unless public
access is necessary (i.e., it is used outside of the defining class).
For this project, every method should be either private
or public
(it is often better to avoid the default of
package-protected).
Your submission will consist of both part 1 and part 2 files by the listed due date. Make sure all of the files in the src folder in addition to the design documents (Methods.drawio and DESIGN.txt) are on Github. Check online to make sure the files show up in your repository on the GitHub website, including Methods.drawio.
src
folder and all contained filesMethods.drawio
— the UML diagramDESIGN.txt
— plain text document justifying methods not moved and
placement of ambiguous methods