One of the most important tasks in designing and implementing object-oriented programs is deciding which classes or objects are responsible for executing which behavior.
Up until now, all of the code you've written has been
in the main
method.
By now, however, the main
method
is starting to get long and tedious to work with.
It's time to refactor your program to make it more object-oriented.
As a program evolves, it is not uncommon to decide that a different way of organizing the classes or methods in the program would make it easier to read, to understand, or to enhance. It is not a good idea to change the structure of existing code at the same time as adding new functionality, because if the program stops working correctly it will be difficult to know where things went wrong. It is better to rearrange or redesign the existing code first, test that it still works correctly, and only then add any new functionality. The restructuring or redesigning phase, without changing the program's behavior, is called refactoring.
The first thing is to create 2 new classes. You could do this using the "New Class" button in BlueJ, but instead we recommend that you download our templates for new classes, which are simple and show the type of class documentation we look for when grading student work.
Download
a
simple template of a "normal" class (one that doesn't have
a main
method), putting it in the folder containing
your project. If you close your project and re-open it, you should
now see the SimpleClassTemplate
class along with your
previous class in the class diagram.
Edit your new class and change its name to something that makes
sense in the context of your project. Since you'll be moving much
of your word counting and textual analysis code here, a name like
WordCounter
or TextAnalyzer
(or something
along those lines) would be better than
SimpleClassTemplate
. Be sure to change the name of
the constructor to match your class name. When you compile your
class, BlueJ will automatically change the file name to match your
class name.
Since these instructions don't know what name you will use, we will
refer to this class as your Analyzer
class.
Download a
Main
class template, close your project, re-open it to see the
class, and rename it to have the same name as your
main
class, but with New
in front of it
(for example, NewWordCounterApp
).
Since these instructions don't know what name you will use, we will
just refer to this class as your NewMain
class.
This first step is similar to the first step in the first lab in this
series: create a WordReader
object, read the first line of
your book, and print some basic information about it.
Analyzer
class:WordReader
object, reading the line, and printing
information.
Since every task that objects of this class will eventually do will
use a WordReader
object, we will make it part of the
"permanent" state of the object — an instance variable.
Find the placeholder instance variable,
int instVar
.
It is the first thing inside the class, just before the constructor.
Replace that instance variable with WordReader reader
.
It should still be private.
Instance variables should be initialized in the
constructor. To initialize the
reader
instance variable, you will construct a
WordReader
object, and to do that you need to know the
filename.
Edit the constructor in your Analyzer
class (which
should have the name you actually gave your class) to take a
file name (a String) as a parameter inside the parentheses.
Edit the javadoc comment that describes this constructor, so that
it says it takes a String as a parameter. (There is an example of
how to document a parameter in the sample method just below the
constructor.)
WordReader
object in the constructor.
Remember that you are initializing an instance variable, not
creating a new local variable:
(If you didn't call your parameterthis.reader = new WordReader(filename);
filename
, use your
parameter name instead.)
main
class into the
constructor. Change any references to reader
to
this.reader
and, if you included the file name in your
output statements, be sure to use your parameter (which might have
the same name, or it might not).
NewMain
class:main
method in your new "main" class.
Add code to construct an object of your new
Analyzer
class. (This will look quite similar to
the code in the original main
method that created
the reader, except that now you are constructing an object of
your new class instead of a WordReader
.)
main
method in your
new "main" class instead of the main
method in your
original "main" class.
Usually a constructor just initializes the object's state; it doesn't start
doing the object's work. In this exercise, you will take some of the code
from the constructor in your Analyzer
class and move it to a
separate method.
Analyzer
class:processFirstLine
. Unlike the
sampleMethod, it does not need to take a parameter and it
won't have a return value, so its return type is
void
rather than int
.
@param
and
@return
lines, since this method has no parameter
and no return value.
NewMain
class:main
method. After the statement
that constructs a new analyzer object, add a call to your
new method to read and process the first line.
Analyzer
class:skipLines
. Keep the placeholder
parameter for now. Like your previous method, this method will
return void
.
main
method to your new method.
It is never a good idea to have hard-coded values in a method if you can avoid it; it is better to make the method more general-purpose.
Modify your new method to take the number of lines as a
parameter, giving the parameter a more meaningful name (like
lines
or numLines
). Then change all
the references to your hard-coded number to refer to the
parameter instead.
main
method referred to any
variables that were declared in code above it (which you did not
copy). In this case, you would need to provide the type
information for those variables.
NewMain
class:main
method to call your
new method to skip lines. You will have to pass a number as a
parameter — it could be the number you used before, or a
different number.
The next step after skipping a bunch of lines is to read the next line and
print information about it. That is essentially the same as what
processFirstLine
already does!
Analyzer
class:processFirstLine
so that
you could use it to process any line.
NewMain
class:main
method to use the new method name
when printing and processing the first line. Add a second call
to the same method after skipping lines to print information
about the next line in the file after the ones you skipped.
Analyzer
class:getAverages
. Keep the placeholder
parameter for now. Like your previous methods, this method will
return void
.
main
method to your new method.
main
method referred to any
variables that were declared in code above it (which you did not
copy). In this case, you would need to provide the type
information for those variables.
NewMain
class:main
method to call your new method. You
will have to pass a number as a parameter — it could be
the number you used before, or a different number.
Analyzer
class:wordAnalysis
. Like your first method, this
method takes no parameters and will return void
.
main
method to your new method: the code
that reports how many words are in the file, that reports the
number of distinct words, the number of words that are used
just once, and the number of long words. (Optional: if you
want, you could make the number of characters that defines a
"long" word a parameter, rather than hard-coding it in this
method.)
main
method referred to any
variables that were declared in code above it (which you did not
copy). In this case, you would need to provide the type
information for those variables.
NewMain
class:main
method to call your new method.
This method, like the one above (and several to come), uses the list of distinct words. Rather than get that list over and over, we could get it once in the constructor and store it as an instance variable.
ArrayList
instance variable
that will represent the full list of distinct words. You can
copy and paste from your "word analysis" method, but just copy
the type and variable name; don't construct the object in the
list of instance variables. Remember to make the new instance
variable private.
this.instanceVarName
), not a new local
variable.
Analyzer
class by copying
and pasting the
sample method again. Give it a meaningful name, such as
analyzeBaseWord
. This method will take a
String
, the base word, as a parameter. (The
examples in the original lab were "house", "time", or "cat".)
Like the other methods in this class, this
method will return void
.
main
method to your new method. Remove all hard-coded references to
the base word, and replace them with references to the
parameter.
main
method to call this method,
passing in the appropriate base word as a parameter.
Analyzer
class by copying and
pasting the
sample method again. Give it a meaningful name, such as
measureCharacterImportance
. This method will take a
String
, the character's name, as a parameter.
Like the other methods in this class, this
method will return void
.
main
method to your new method.
Modify the code so that instead of counting how often 4 - 6
different characters are mentioned, it just counts how often 1
character is mentioned. Get that character's name from the
parameter.
main
method to call this method 4 -
6 times,
passing in a different character name each time.
By now, you have figured out the pattern for bringing code from your old,
monolithic main
method to your new class that provides the
same functionality in smaller, more coherent chunks. Apply those
techniques to create two more methods:
Remember to update the javadoc comments for each new method and to call the
method from your new main
method.
Ideally, a main
method does very little: just constructs a
couple of objects and calls a couple of methods to get everything going.
Then the objects take over. In this case, the main
method
constructs a single object and then calls a bunch of methods. An
alternative would be to create a new analyzeBook
method in the
Analyzer class that calls all of the individual methods, and then replace
all of those method calls in main
with a single call to
analyze book
. Note that the method would need a set of
parameters representing all of the parameters that were sent to
individual methods.
Once you have your program working for 1 book, try it with other books.
Download 2 more books, if you hadn't already. (The directions for doing
this are in the original Textual Analysis
mini-lab.) Edit your main
method to construct three
Analyzer objects, not just one, passing each the name of a different book.
Ask all three objects to analyze their book contents.
Look at the results to see if you can find differences that are indicative of real differences between the books. Obviously the names of the chief characters will be different, but are there other differences, especially in the number of distinct words, the number of singletons, the number of long words, and the most-frequently used words (which pronouns are most frequently used, for example) that indicate a difference in the target audience or main topics of the books?
YourName_WordCounting
).
(Do
this from the Mac Finder or Windows Explorer, not from within BlueJ.)
This will help whoever grades it
when they receive a dozen or more projects with similar names.