In the first pair of assignments you identified methods and moved them into the most appropriately associated classes. In doing so, you may have noticed that some classes support functionality (methods) that are not appropriate for all instances of the class. Moreover, these classes support data attributes that are not used by all instances of the class.
This is an issue of cohesion. Specifically, these classes (tips for identifying them are given later) exhibit low (poor) cohesion by representing multiple (admittedly related) concepts by combining all attributes and methods used by each concept.
This pair of assignments asks that you improve the code base by splitting each class exhibiting low cohesion into multiple highly cohesive classes. Doing so in the context of Java will require identifying common methods for each subset of these new classes and then introducing a new interface for each logical grouping of these methods (more on this below).
You must identify those classes with low cohesion and then split these
classes into separate classes exhibiting high cohesion. Since each of
these new classes will introduce a separate type, you may need to "root"
them at a single type (as defined by an interface
) to
satisfy Java's type checking rules. You will
not introduce any additional classes beyond those used to improve cohesion
(e.g., you will not use inheritance in this pair of assignments). This
refactoring is quite likely to increase the amount of code through the
duplication of some methods; this is expected and ok at this time.
Based on the original source code, there are likely two categories of
classes with low cohesion. The first category consists of those classes
that depend on ActionKind
or EntityKind
. The
second category depends on your final distribution of the methods in
the original Functions
class.
Kind: The original source code uses ActionKind
and
EntityKind
to allow each Action
instance
and each Entity
instance to play one of potentially many
roles (this mimics a tagged union). You are to eliminate these
*Kind
classes (enums) (the programmatic implications
of doing so are discussed in
Programming Assignment 2)
by splitting Action
and Entity
into
multiple new classes.
Other: Review all of the classes with an focus on cohesion. Does a class contain data that is not used by all instances of the class (i.e., each "kind" uses only subsets of the data)? Does a class contain methods that do not support the primary role of instances of the class (e.g., static methods that are used to create instances or parse files, but that are not actually part of the functionality provided by the instances)?
As before, you are encouraged to develop both the UML design document and the code refactoring at the same time. You are further encouraged to implement the refactoring incrementally so that your refactored program executes properly at each step. You will submit the design document before the final refactoring submission to allow for feedback on your design that can then be incorporated into your refactoring.
The following are some tips on approaching the introduction of interfaces to support splitting classes.
Note: A class should very rarely implement an interface only to then define a method required by the interface to do nothing at all. A class should not implement an interface and then define a method required by the interface to raise an exception indicating that the method is not supported.
Your introduction of interfaces for this project must be meaningful. It is insufficient to define a single interface with all methods that are then only partially implemented by each of the classes.
First, copy the original class to each of the new classes (each defining a single role). This can be easily done in the UML editor.
In each new class, eliminate each data attribute not used by this class and each method not supported by this class. (For this project, you can examine how instances playing this role are created as a hint about which data attributes are actually used.)
Change the original class into an interface declaring only those methods shared by every new class.
Group the new classes into sets with similar functionality. Introduce additional interfaces as appropriate (see below).
Examine the original uses of the objects (before this change) to determine which methods are used by client code. Can the client code still access that method based on the reference type? Will it be able to do so if you change the type to one of the interfaces that you have already introduced?
Your "design document" will consist only of an updated UML diagram.
Copy the UML diagram from the prior design assignment to
Interfaces.graphml
. Then update this copy to reflect the
new classes and the interfaces.
You can add an interface to the UML document by adding a class and then, under the UML tab in properties, setting the Stereotype field to "interface". For each interface that a class implements, be sure to draw an appropriate arrow (dashed line with open triangle head) from the class to the interface. If an interface extends another interface in your design, then be sure to connect them with an appropriate arrow (solid line with open triangle head).
Your submission of your design document will consist of the following files.