Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / General / January 2006

Tip: Looking for answers? Try searching our database.

Overloading abstract methods

Thread view: 
Rhino - 07 Jan 2006 00:56 GMT
I have a question about abstract classes but I'm going to need to give you
some of the background before the question makes much sense. Please bear
with me.

I've decided that my project, which writes my resume in a variety of
different formats - HTML, ASCII, PDF for starters - would benefit from an
abstract class. I'm calling this abstract class ResumeWriter. There will be
one class for each each of the resume formats and each of these will extend
ResumeWriter so I'm calling them ResumeWriterHtml, ResumeWriterAscii and
ResumeWriterPdf. In other words, ResumeWriterHtml extends the abstract
ResumeWriter and writes the HTML version of the resume and so on.

The ResumeWriter abstract class includes:
- a number of concrete getters, one for each of the things that the
ResumeWriterHtml, ResumeWriterAscii, and ResumeWriterPdf classes will get
from the ResourceBundle that supplies the data to all of the classes whose
names start with 'ResumeWriter'
- several abstract write methods, one for each portion of the resume, e.g.
writeCandidateInformation(), writeEmploymentHistory(),
writeSoftwareSkills(). In the subclasses, ResumeWriterHtml et. al., each of
the abstract methods will be implemented to write that portion of the resume
in the appropriate format, e.g. the writeSoftwareSkills() method will write
software skills information in HTML format in ResumeWriterHtml and in PDF
format in ResumeWriterPdf and so on.

I'm reasonably confident that this is a sensible way to do this from a
design point of view and the code already works so I know that this is a
workable approach.

Now, here's where I run into problems. In some of the ResumeWriter
subclasses, the write() methods display slightly different data than in
others. For instance, the ResumeWriterAscii class displays only the heading
"EmploymentHistory" and then an array called "EmploymentHistoryData" but the
ResumeWriterHtml displays additional information, specifically some text
that serve as  column titles for the different aspects of each job, namely
"From/To", "Employer", "Role", and "Main Tools Used" for those columns of
the HTML table that displays the employment history.

This suggests to me that I need _two_ abstract methods called
writeEmploymentHistory(); one has only two arguments, EmploymentHistoryText
and EmploymentHistoryData, while the other version also has the same two
arguments plus four additional arguments, FromToText, EmployerText,
RoleText, and MainToolsText. Now, I know that this is simply overloading the
writeEmploymentHistory() method and that this is perfectly fine in an OO
program.

The problem for me is that I apparently have to implement _both_ versions of
writeEmploymentHistory() in each class that subclasses ResumeWriter, even
though only one of the two methods will be used in any given subclass of
ResumeWriter. And that makes me uneasy: I find myself wondering if I am
doing the right thing when I implement multiple versions of the same method
in a given subclass even though I will only use one of them. Implementing
methods that I'm not going to use doesn't seem right somehow.

Can someone who is stronger in OO Analysis and Design help me figure out if
I am doing the right thing here? If not, what should I be doing differently?

One other small issue related to the main problem. In the version of the
overloaded method that I am NOT using, it seems to me that the easiest thing
to do is just leave the method body empty with a comment that it is not
being implemented because it is not appropriate for this subclass. However,
if I do that, I get compiler warnings that the parameters for that method
are never used. I know I could disable those warnings in the compiler but
I'd rather avoid that and just put some sort of do-nothing code in the
method body to satisfy the compiler that the parameters are used, even if
the use is trivial. What is the most trivial thing I can do with each
parameter to satisfy the compiler that the parameter is being used? I'm
looking for something that would be harmless if it were executed and that
would be more-or-less harmless looking to anyone who sees the code, i.e.
something that seems to be obvious do-nothing code.

Rhino
Chris Smith - 07 Jan 2006 01:47 GMT
> I've decided that my project, which writes my resume in a variety of
> different formats - HTML, ASCII, PDF for starters - would benefit from an
> abstract class. I'm calling this abstract class ResumeWriter. There will be
> one class for each each of the resume formats and each of these will extend
> ResumeWriter so I'm calling them ResumeWriterHtml, ResumeWriterAscii and
> ResumeWriterPdf.

Okay, you're only the seventeen gazillionth person to do this, but I'm
picking on you.

Why?!?  Would it have killed you to call them HtmlResumeWriter,
AsciiResumeWriter, and PdfResumeWriter?  Have you ever asked a neighbor
to borrow her mower-lawn?

Anyway, just ignore me.  Had to get that off my chest.

> This suggests to me that I need _two_ abstract methods called
> writeEmploymentHistory(); one has only two arguments, EmploymentHistoryText
[quoted text clipped - 8 lines]
> though only one of the two methods will be used in any given subclass of
> ResumeWriter.

There's no problem with giving an object more than it needs.  What you
want is, most fundamentally, one method called writeEmploymentHistory,
which takes as parameters all of the information that might be useful
for writing an employment history.  Which information you choose to make
use of in each subclass is up to you.

> One other small issue related to the main problem.

Actually, it turns out I think this is more related to the problem than
you may think.

> In the version of the
> overloaded method that I am NOT using, it seems to me that the easiest thing
> to do is just leave the method body empty with a comment that it is not
> being implemented because it is not appropriate for this subclass.

If you decide on this design against my recommendations, the better
thing to do would be to throw an exception... an unchecked one, since
this is a programming error.

> However, if I do that, I get compiler warnings that the parameters for
> that method are never used. I know I could disable those warnings in
> the compiler but I'd rather avoid that

Why?  Disable that warning.  That warning is counterproductive to good
programming technique.  It will cause you to do a worse job than if you
leave it enabled.

If the warning only warned about unused parameters for an entire
hierarchy of types in unpublished APIs, then I could see its uses.  What
it's doing, though -- warning you for not using a parameter in one
method when you ARE using it within that method signature in the type
hierarchy -- is counterproductive, and encourages poor code... like this
throw away "trick the compiler" nonsense you're asking for.

I know it sounds like rehashing the same thing, but you're really
providing a perfect example now of why you should NOT turn on random
warnings just because some compiler vendor saw fit to give you the
option (and leave it OFF by default).  I imagine I could really make
your life miserable by patching your copy of Eclipse to add an optional
warning if you use more than 16 characters in an identifier.

> and just put some sort of do-nothing code in the
> method body to satisfy the compiler that the parameters are used, even if
> the use is trivial.  What is the most trivial thing I can do with each
> parameter to satisfy the compiler that the parameter is being used?

Is this Java 1.5?  If so, @SuppressWarnings("unused").

If it's not Java 1.5, and if you can't be dissuaded from doing this,
then I guess you could declare a temporary variable of the same type,
assign the value of the parameter to the parameter to that temporary
variable, and then assign the temporary variable back to the parameter.  
Like this:

   public void myMethod(int unusued)
   {
       int tmp = unused;
       unused = tmp;
   }

(The second bit is to prevent another warning about an unread local
variable.)

Then again, with any luck Eclipse will soon gain a warning for assigning
to a parameter (which WOULD be a good one to leave on) and you'll
trigger that one.  Or its control flow will get smarter and you'll have
to get more complex to trick it.  Eventually, someone might get wise
enough to use escape analysis for that warning, and then you'll be
really snookered.

Do you care about the standard output and error streams?  I guess you
could print the values there, and that would sure take care of it... but
you'd get weird output if you ran the app from the command line.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

VisionSet - 07 Jan 2006 02:38 GMT
> > ResumeWriter so I'm calling them ResumeWriterHtml, ResumeWriterAscii and
> > ResumeWriterPdf.
[quoted text clipped - 7 lines]
>
> Anyway, just ignore me.  Had to get that off my chest.

LOL, I felt that from this side of the pond!
They would at least sort nicely in (my) IDEs list's of classes.

--
Mike W
Roedy Green - 07 Jan 2006 04:15 GMT
>Okay, you're only the seventeen gazillionth person to do this, but I'm
>picking on you.
[quoted text clipped - 4 lines]
>
>Anyway, just ignore me.  Had to get that off my chest.

the advantage of doing that weird way is javadoc listings will put the
three classes side by side.
Signature

Canadian Mind Products, Roedy Green.
http://mindprod.com Java custom programming, consulting and coaching.

Rhino - 07 Jan 2006 04:57 GMT
>>Okay, you're only the seventeen gazillionth person to do this, but I'm
>>picking on you.
[quoted text clipped - 7 lines]
> the advantage of doing that weird way is javadoc listings will put the
> three classes side by side.

And that was my basic motivation for naming things that way, as it turns
out.

One other reason is something I heard in school many years ago: a teacher
described the ISO standards as putting things like dates, which comprised
several parts, into order on the basis of largest things to smallest things,
e.g. the ISO standard is to put years first, then months, then days. To me,
the abstract class, ResumeWriter is the "larger" thing and the specific type
of document being produced is the "smaller" think; therefore, ResumeWriter
goes first, followed by the type of document, thus giving us things like
ResumeWriterHtml or ResumeWriterAscii.

Then again, I could persuade myself that I've really got three parts in the
name, not two, and by that reasoning, the names should be WriterResumeHtml
and WriterResumeAscii. Something tells me that would cause a lot of raised
eyebrows too :-)

Well, I'm not going to please everyone so I'm going to concentrate on
pleasing myself. :-)

Rhino
Chris Smith - 07 Jan 2006 05:29 GMT
> Well, I'm not going to please everyone so I'm going to concentrate on
> pleasing myself. :-)

As I said, you can ignore that.

Hope the rest of the response was helpful.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 07 Jan 2006 16:24 GMT
>> Well, I'm not going to please everyone so I'm going to concentrate on
>> pleasing myself. :-)
>
> As I said, you can ignore that.

I wasn't trying to be defensive, I just wanted to make the case for the
approach I was using :-)

> Hope the rest of the response was helpful.

It certainly was!

Rhino
P.Hill - 12 Jan 2006 04:01 GMT
Roedy Green wrote about putting modifiers on the end of a class name.
> the advantage of doing that weird way is javadoc listings will put the
> three classes side by side.

VisionSet wrote:
> They would at least sort nicely in (my) IDEs list's of classes.

Just to pick this slightly old thread. Let's see, something that can
group things together. How about a package!  Now that was simple enough.

If you didn't want them all in their own package, I'd wonder what
the list of other things were in the package that is making it so
cluttered that you can't see the three classes.

Also IDEs with inheritance hierarchies are cool tools also.

Yeah, I'm with Chris, adjectives in English go _before_ the noun!

"Scoping" modifiers in names are for programming in assembly no Java or
C++

-Paul
Rhino - 07 Jan 2006 05:11 GMT
>> I've decided that my project, which writes my resume in a variety of
>> different formats - HTML, ASCII, PDF for starters - would benefit from an
[quoted text clipped - 13 lines]
>
> Anyway, just ignore me.  Had to get that off my chest.

See my explanation in a followup to Roedy's first reply. It probably won't
persuade you but, then again, maybe it will :-)

>> This suggests to me that I need _two_ abstract methods called
>> writeEmploymentHistory(); one has only two arguments,
[quoted text clipped - 17 lines]
> for writing an employment history.  Which information you choose to make
> use of in each subclass is up to you.

Okay, I guess I can buy that as a valid approach. But is it good design? If
I applied that same argument to other classes in the J2SE, then I'd probably
never overload _any_ method: I'd just combine all overloaded methods into a
single big method that had all possible parameters. I'd expect to get
criticized on the grounds that I'm discarding the benefits of overloaded
methods by doing that. Isn't that what you're proposing here? Or is this a
"special case" where that is okay?

>> One other small issue related to the main problem.
>
[quoted text clipped - 39 lines]
>
> Is this Java 1.5?  If so, @SuppressWarnings("unused").

Yes, it is Java 1.5.

> If it's not Java 1.5, and if you can't be dissuaded from doing this,
> then I guess you could declare a temporary variable of the same type,
[quoted text clipped - 17 lines]
> enough to use escape analysis for that warning, and then you'll be
> really snookered.

I'm actually not as determined to write do nothing code as you may think. I
was hoping someone could suggest a way to avoid that, first and foremost.
But I wanted a suggestion for the least offensive way of doing it if I had
to do it.

You make a good case for using the compiler switch to simply ignore unused
variables, at least in this case. I suppose I was looking for some bragging
rights in saying that I am not ignoring any warnings or errors and have an
absolutely warning-free compile. But I agree that this is just going to lead
to poor code in this case. And, as you say, the next version of the compiler
will probably find ways to detect any little tricks that I play on the
current version of the compiler and make me change the code eventually.

> Do you care about the standard output and error streams?  I guess you
> could print the values there, and that would sure take care of it... but
> you'd get weird output if you ran the app from the command line.

Interesting. That's exactly what I came up with on my own: I just did a
System.out.println() of each parameter. I'm not even worried about the code
executing because the unused method simply isn't called anywhere within its
class. Of course, if I subclassed that class, there'd be the risk that the
subclass could execute it.

Rhino
Chris Smith - 07 Jan 2006 05:40 GMT
> > There's no problem with giving an object more than it needs.  What you
> > want is, most fundamentally, one method called writeEmploymentHistory,
> > which takes as parameters all of the information that might be useful
> > for writing an employment history.  Which information you choose to make
> > use of in each subclass is up to you.

> Okay, I guess I can buy that as a valid approach. But is it good design? If
> I applied that same argument to other classes in the J2SE, then I'd probably
[quoted text clipped - 3 lines]
> methods by doing that. Isn't that what you're proposing here? Or is this a
> "special case" where that is okay?

Actually, you can overload or not... it doesn't matter all that much.  
The change in design is that you provide one point of implementation for
subclasses to customize the behavior of the superclass.  And yes, it is
good design.  For example, here's something with overloads.

public abstract class ResumeWriter
{
   public abstract void writeEmploymentHistory(
       String employmentHistoryText,
       String employmentHistoryData,
       String fromToText,
       String employerText,
       String roleText,
       String mainToolsText);

   public final void writeEmploymentHistory(
       String employmentHistoryText,
       String employmentHistoryData)
   {
       writeEmploymentHistory(
           employmentHistoryText, employmentHistoryData,
           null, null, null, null);
   }
}

You still need only override the method that receives all the data, and
just use what you need.

I'm not exactly clear on how you call this method, though.  I would
think that from the calling context, you'd just forget about what kind
of resume you're writing, and pass all the information you've got.  Let
the subclass sort out what it uses and what it doesn't use.  In that
case, you'd never use the overload anyway.  The change here isn't just
to reuse the same method from two contexts... it's to design your code
as much as possible to stop caring about the context in the first place.

> > Is this Java 1.5?  If so, @SuppressWarnings("unused").

> Yes, it is Java 1.5.

So that's the solution to the unused parameter warning, then, eve if you
do leave it on.  You'll have to deal with that regardless of which
design decision you've made.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 07 Jan 2006 16:31 GMT
>> > There's no problem with giving an object more than it needs.  What you
>> > want is, most fundamentally, one method called writeEmploymentHistory,
[quoted text clipped - 50 lines]
> to reuse the same method from two contexts... it's to design your code
> as much as possible to stop caring about the context in the first place.

For what it's worth, I've decided not to overload the method after all; I'll
just have a single version of writeEmploymentHistory() and pass it all the
parameters it will ever need, even if some parameters don't get used in some
subclasses of ResumeWriter.

>> > Is this Java 1.5?  If so, @SuppressWarnings("unused").
>
[quoted text clipped - 3 lines]
> do leave it on.  You'll have to deal with that regardless of which
> design decision you've made.

You sold me on using the @SuppressWarnings annotation. I'd been meaning to
start using annotations in the near future anyway and your note gave me the
impetus to read up about the basics. I really like that I can apply
annotations in a very narrow scope, such as a single method of a given
class, rather than having to turn off the unused variable detection for all
classes throughout my IDE.

Rhino
Chris Smith - 07 Jan 2006 16:55 GMT
> You sold me on using the @SuppressWarnings annotation. I'd been meaning to
> start using annotations in the near future anyway and your note gave me the
> impetus to read up about the basics. I really like that I can apply
> annotations in a very narrow scope, such as a single method of a given
> class, rather than having to turn off the unused variable detection for all
> classes throughout my IDE.

Yep.  I'd like still narrower scope, though... especially for
@SuppressWarnings("unchecked").  This particular annotation ought to be
able to be applied to a statement, rather than just a method or other
declaration.  Except, of course, that this isn't what annotations are
really designed for.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Chris Uppal - 08 Jan 2006 12:16 GMT
> For what it's worth, I've decided not to overload the method after all;
> I'll just have a single version of writeEmploymentHistory() and pass it
> all the parameters it will ever need, even if some parameters don't get
> used in some subclasses of ResumeWriter.

That's not too bad, but it would be better still not to try to work out what
"all the parameters it will ever need" are.  Just pass[*] it the source of
/all/ the data and let the individual implementations pull what they require
out of it.

(by "pass" I don't necessarily mean pass as a parameter -- since your raw data
is held as part of the object's state, the writeEmploymentHistory() method is
entitled to use whatever bits of that it happens to need)

   -- chris
Rhino - 08 Jan 2006 18:50 GMT
>> For what it's worth, I've decided not to overload the method after all;
>> I'll just have a single version of writeEmploymentHistory() and pass it
[quoted text clipped - 13 lines]
> is
> entitled to use whatever bits of that it happens to need)

This is an intriguing idea but I think it would be counterproductive in my
case.

I've gone ahead and reworked the resume classes that we discussed in a
thread just before Christmas. I was persuaded by several of the people who
responded, including you, that I should take advantage of the fact that each
version of the resume would have several common elements and create an
abstract class that recognized that; then use subclasses of the abstract
class to generate each specific format of the resume, e.g. HTML, PDF, ASCII.

All of the information for the resume is coming from a ResourceBundle.
(Currently, I am only creating an English-language resume but I thought I'd
use ResourceBundles in case I decided to generate other languages as well;
that would be dead easy since I'd only have to copy my English
ResourceBundle and then translate the relevant bits of in the second
ResourceBundle.)

I realized that each of the resumes executed _exactly_ the same code to get
the desired information, such as my name, from the ResourceBundle,
regardless of the format of the resume being generated. I also realized that
each resume _wrote_ that information in its respective file _differently_
from the other files. For example, the HTML version of the resume would
write this to display my name:

<h1>RHINO</h1>

while the ASCII version of the resume would write this left-justified:

RHINO

and the PDF version of the resume would write this centered:

RHINO

As a result I created the abstract class, ResumeWriter, with _concrete_
methods to _get_ the contents of the resume from the ResourceBundle and
_abstract_ methods to _write_ those contents in the desired format.
Therefore, ResumeWriter looks like this (I'm including only the code needed
for the first bit of the resume; that's all I need for illustrating my
point):

-------------------------------------------------------------------------------------------------------------------
public abstract class ResumeWriter{

   public String getCandidateNameData(ResourceBundle locList) {

       String candidateNameData = locList.getString("CandidateNameData");
       return candidateNameData;
   }

   public String[] getCandidateAddressData(ResourceBundle locList) {

       String[] candidateAddressData = (String[])
locList.getObject("CandidateAddressData");
       return candidateAddressData;
   }

   public String getJobTargetText(ResourceBundle locList) {

       String jobTargetText = locList.getString("JobTargetText");
       return jobTargetText;
   }

  public String getJobTargetData(ResourceBundle locList) {
       String jobTargetData = locList.getString("JobTargetData");
       return jobTargetData;
   }

   public abstract void writeCandidateAndTarget(String candidateNameData,
String[] candidateAddressData, String jobTargetText, String jobTargetData);

-------------------------------------------------------------------------------------------------------------------

The ResumeWriterHtml class looks like this (with most of the irrelevancies
omitted):

-------------------------------------------------------------------------------------------------------------------
public class ResumeWriterHtml extends ResumeWriter implements
ResumeConstants, ResumeGeneratorConstants {

   private final String CLASS_NAME = getClass().getName();

   private PrintWriter pw = null;

   /**
    * Creates an HTML version of a resume.
    *
    * @param strOutfile the name of the file which will contain the HTML
version of the resume
    * @param locList the ResourceBundle which contains the data that will
be written on the resume
    */
   public ResumeWriterHtml(String strOutfile, ResourceBundle locList) {

       /* Delete the old version of the output file. */
       ResumeUtilities.deleteFile(strOutfile);

       /* Open the output file. */
       this.pw = ResumeUtilities.openOutputFile(strOutfile);

       /* Write the head of the page. */
       writeHead();

       /* Get and write the candidate name, address, and job target
information. */
       String candidateNameData = getCandidateNameData(locList);
       String[] candidateAddressData = getCandidateAddressData(locList);
       String jobTargetText = getJobTargetText(locList);
       String jobTargetData = getJobTargetData(locList);
       writeCandidateAndTarget(candidateNameData, candidateAddressData,
jobTargetText, jobTargetData);

   }

   /**
    * Writes the resume lines that show the candidate name, address, and
job target in HTML format.
    *
    * @param candidateNameData the name of the candidate
    * @param candidateAddressData the address of the candidate
    * @param jobTargetText the job target text, typically "Job Target"
    * @param jobTargetData the job target data
    */
   @Override
   public void writeCandidateAndTarget(String candidateNameData, String[]
candidateAddressData, String jobTargetText, String jobTargetData) {

       this.pw.println("<BODY>");
       this.pw.println("<table width=\"580\">");
       this.pw.println("<tr><td>");
       this.pw.println("<h1>" + candidateNameData.toUpperCase() + "</h1>");
       this.pw.println("<p>"); //$NON-NLS-1$
       for (String candidateAddressLine : candidateAddressData) {
           if (candidateAddressLine.indexOf("@") > -1) {
               this.pw.println("<a href=\"mailto:" + candidateAddressLine +
"\">" + candidateAddressLine + "</a><br>");
           } else {
               this.pw.println(candidateAddressLine + "<br>");
//$NON-NLS-1$
           }
       }
       this.pw.println("</p>");
       this.pw.println("<h4>" + jobTargetText + ": " + jobTargetData +
"</h4>");
       this.pw.println("<br>");
       this.pw.println("</td></tr>");
   }

-------------------------------------------------------------------------------------------------------------------

See? I use the concrete methods from the abstract ResumeWriter class to get
the data from the ResourceBundle and don't need to implement them within my
subclasses at all, then I implement the abstract writeCandidateAndTarget()
method within the subclass so that it writes the information in HTML. Class
ResumeWriterPdf looks identical to ResumeWriterHtml except that its
implementation of method writeCandidateAndTarget() writes PDF format;
however, it is using the exact same concrete methods to get the data from
the ResourceBundle and those methods don't need to be overridden in the
subclasses of ResumeWriter.

If I followed your suggestion, I'd basically be eliminating those concrete
getters from the abstract class and having to implement that code in each of
the subclasses of ResumeWriter, which would mean duplicating the getter code
in each and every one of the subclasses. That would be a definite step
backwards from the current design.

The only quibble is that some implementations of some of my abstract write
methods don't need all of the data that the method wants. For instance, in
some cases, the implementations of writeEmploymentHistory() need only
EmploymentHistoryText and EmploymentHistoryData but in other
implementations, they need FromToText, EmployerText, RoleText, and
MainToolsText. That's what prompted this thread in the first place.

Chris's remarks got me thinking that I was better to define all the
parameters that writeEmploymentHistory() would ever need in a single
abstract definition of the method rather than making several overloaded
abstract methods, one for each valid combination of parameters that the
overloaded method might need. It seems easier to simply ignore superfluous
parameters than to implement each of the overloaded methods, even if all but
one of the overloaded versions of the method contains only an empty
implementation.

Going through this thought process again has made me change my mind! As I
wrote the preceding paragraph, I realized that it still bothered me to pass
parameters that were being ignored and that this bothered me more than
having overloaded versions of the writeEmploymentHistory() method. I created
an abstract two-parameter version of writeEmploymentHistory() in
ResumeWriter to complement the six-parameter version and implemented it in
each of my three subclasses of ResumeWriter. In each of the subclasses, I
did a proper implementation of the version I was actually using and an empty
implementation of the version I wasn't using. The empty implementation got
an @SuppressWarnings("unused") annotation and a "//not used in this
subclass" comment; the proper implementation got neither. Then, I made sure
that I adjusted the invocation of writeEmploymentHistory() to pass only the
appropriate number of parameters, two or six, none of which were null.
Frankly, I like this approach better overall, although I could certainly
live with the no-overloaded methods approach in a pinch.
Raymond DeCampo - 08 Jan 2006 21:32 GMT
> Going through this thought process again has made me change my mind! As I
> wrote the preceding paragraph, I realized that it still bothered me to pass
[quoted text clipped - 11 lines]
> Frankly, I like this approach better overall, although I could certainly
> live with the no-overloaded methods approach in a pinch.

Why do your writeEmploymentHistory() methods require any parameters, if
all the data is available via the superclass?

HTH,
Ray

Signature

This signature intentionally left blank.

Rhino - 09 Jan 2006 05:09 GMT
>> Going through this thought process again has made me change my mind! As I
>> wrote the preceding paragraph, I realized that it still bothered me to
[quoted text clipped - 15 lines]
> Why do your writeEmploymentHistory() methods require any parameters, if
> all the data is available via the superclass?

Good point!

I'm invoking all of the getters from the constructor of each ResumeWriter
subclass, e.g. ResumeWriterAscii. Here's a short fragment from the actual
ResumeWriterAscii:

---------------------------------------------------------------------------------------------------------
public ResumeWriter(ResourceBundle locList) {

...
       String employmentHistoryText = getEmploymentHistoryText(locList);
       String[][] employmentHistoryData =
getEmploymentHistoryData(locList);
       writeEmploymentHistory(employmentHistoryText,
employmentHistoryData);

...
-----------------------------------------------------------------------------------------------------------

I suppose I could very easily change the class so that employmentHistoryText
and employmentHistoryData are class variables, then simply remove the
parameters from the invocation of writeEmploymentHistory().

But isn't it generally bad form to make something a class variable unless it
really needs to be a class variable because it is used all over the class?
In my case, employmentHistoryText and employmentHistoryData are used only in
the constructor and in writeEmploymentHistory().

Wouldn't that suggest the two variables really ought to be local variables?
That's my gut hunch anyway. But maybe that's just me overcompensating for my
first several years of programming! I was writing mainframe COBOL and COBOL
doesn't have local variables: everything is a global variable. Once I
discovered that local variables existed in some languages, I've tended to
try to use a local variable whenever possible, perhaps to excess.

Do you think I should be making the variables that hold my getter results
class variables rather than local variables?

Rhino
Chris Smith - 09 Jan 2006 06:37 GMT
> public ResumeWriter(ResourceBundle locList) {
>
[quoted text clipped - 4 lines]
>         writeEmploymentHistory(employmentHistoryText,
> employmentHistoryData);

Danger, Will Robinson!  You're calling a non-final and non-private
method from a constructor, here.  That's a very dangerous thing to do,
and best avoided.  It means that any fields of the subclass will be
uninitialized -- even if they are compile-time constants! -- when
writeEmploymentHistory is called.  For example, consider a subclass:

public class HTMLResumeWriter extends ResumeWriter
{
   private String docType;

   public HTMLResumeWriter(String docType, ResourceBundle locList)
   {
       super(locList);
       this.docType = docType;
   }

   public void writeEmploymentHistory(String text, String[][] data)
   {
       ...
   }
}

docType will be null inside of writeEmploymentHistory.

> But isn't it generally bad form to make something a class variable unless it
> really needs to be a class variable because it is used all over the class?
> In my case, employmentHistoryText and employmentHistoryData are used only in
> the constructor and in writeEmploymentHistory().

It's good form to make something an instance field if it is logically a
property of any object of that class.  Rules of thumb that rely on
things like how or where something is used are necessarily approximate,
and prone to give wrong suggestions sometimes.  In this case, I see two
possible designs, and you have your choice between them:

Option 1: ResumeWriter is conceived of as a tool.  You could create one
without giving it any specific information about any particular person's
resume.  It has a method to write a resume, and all necessary
information is passed in there.  Much like a printer, the ResumeWriter
is a general-purpose tool that may be used to create any number of
resumes, and the details of the resumes it writes depend on how it's
used, not how it was built.  In that case, the resume information should
be passed around as a parameter, because it's not logically a property
of the writer.

Option 2: A ResumeWriter is a one-shot object that's used to write a
specific resume.  It's created to write one resume and then discarded.  
It's more like a print job than a printer, in that it is a transient
thing that is created for and works with a specific set of data, and a
new one is created for the next data set.  In this case, the resume
that's being written definitely is a property of the ResumeWriter, and
it should be a field.

Both options are fine.  Each gives you different tools for handling
additional requirements if the application becomes more advanced in the
future.  So far, you seem to have chosen option 2, and that's fine.  In
that case, you should store the information about the resume in fields,
and provide protected APIs for subclasses to access it.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 09 Jan 2006 14:32 GMT
>> public ResumeWriter(ResourceBundle locList) {
>>
[quoted text clipped - 28 lines]
>
> docType will be null inside of writeEmploymentHistory.

Okay, so what are you saying I should do to prevent these problems? I know
the compiler will not let me make writeEmploymentHistory() (within
ResumeWriterAscii) a private method because it is public in the abstract
class and the implementation can't have less access than the abstract
version. I know the compiler will only let me make the method public or
protected in the abstract class. I _can_ make writeEmploymentHistory() final
within ResumeWriterAscii; is that what you are suggesting? I don't plan on
subclassing ResumeWriterAscii or its siblings any further so I don't have a
problem with making the writeXXX() implementations final.

With regards to your docType example, I wasn't aware that docType would be
uninitialized in that case. Frankly, it rather surprises me that docType
would be uninitialized in this case, given that docType is a class variable
in the class. Why exactly does Java behave that way? Would I be safe in
assuming that a compiler warning would bring this to my attention?

Am I correct in thinking that you are warning me against setting a class
variable based on a value passed in the parameter of a constructor?

My actual ResumeWriterAscii class, and its siblings, pass only two values to
their constructors: a String containing the name of the file that is to be
written and a ResourceBundle that contains the data used by the class. Both
of these values are not assigned to any class variables, are used only in
the constructor and are not passed explicitly to any other method of the
class. I also have several class variables in ResumeWriterAscii: all but two
of them are constants used throughout the class for formatting. The other
two class variables are:

   private StringUtils stringUtils = null;
   private PrintWriter pw = null;

They get set to useful values in the constructor and are then used
throughout the class. I'm not going to have any problems with any of that,
am I? The compiler is not giving me any errors or warnings anywhere in my
project.

Hmmm, just out of curiousity, I made a class variable called strOutfile
which was defined as a private String and initialized to null. Then, within
my constructor, I added this line:

   this.strOutfile = strOutfile;

I also added this line to writeEmploymentHistory():

   System.out.println("strOutfile: " + this.strOutfile); //$NON-NLS-1$

The compiler flags 'strOutfile' within my constructor signature ['public
ResumeWriterAscii(String strOutfile, ResourceBundle locList)'] and gives me
this warning: "The parameter strOutfile is hiding a field from type
ResumeWriterAscii." That's fair enough since the strOutfile value passed in
via the constructor will inevitably clobber the value of the strOutfile
class variable. But I get no warnings or errors about strOutfile being
uninitialized in writeEmploymentHistory() even though it now parallels the
docType example you gave. I assume that's why you brought that to my
attention: since the compiler gives no warning, I can easily fail to notice
this problem.

>> But isn't it generally bad form to make something a class variable unless
>> it
[quoted text clipped - 33 lines]
> that case, you should store the information about the resume in fields,
> and provide protected APIs for subclasses to access it.

You're right; I do think of my ResumeWriter subclasses as Option 2.

Could you elaborate on what you mean by your last sentence? Are you saying
that I am doing the right thing in storing the values obtained by the
getters in local variables and then passing them to  their respective
writers? Do the implementations of the writeXXX methods which appear in my
ResumeWriter subclasses constitute the protected APIs that you are
recommending? Or are you proposing something else?

Rhino
Chris Smith - 09 Jan 2006 17:18 GMT
> Okay, so what are you saying I should do to prevent these problems? I know
> the compiler will not let me make writeEmploymentHistory() (within
[quoted text clipped - 5 lines]
> subclassing ResumeWriterAscii or its siblings any further so I don't have a
> problem with making the writeXXX() implementations final.

I mean that any method you call from a constructor should be either
private or final in the class where you call it.  Since you need to
override writeEmploymentHistory, that precludes the possibility of
calling writeEmploymentHistory from the constructor.  An alternative
design is:

public class ResumeWriter
{
   private ResourceBundle locList;

   public ResumeWriter(ResourceBundle locList)
   {
       this.locList = locList;
   }

   public void writeResume()
   {
       String employmentHistoryText = ...;
       String[][] employmentHistoryData = ...;

       writeEmploymentHistory(
           employmentHistoryText, employmentHistoryData);
   }
}

There are now two steps to writing a resume: first, create a
ResumeWriter, and then call its writeResume method.  That's better,
anyway.  It's surprising when writing the resume -- which is the main
purpose of a ResumeWriter -- is done as a side-effect of creating the
object.  If other people were using this API, it would take them time to
get accustomed to that little quirk, and it prevents the code from
reading well.  There is no mental model of the ResumeWriter class that
makes it logical for it to write the resume in the constructor.

Note that this paves the way for the changes suggested elsewhere.  What
needs to happen now is:

- your getEmploymentHistoryText and getEmploymentHistoryData methods
made protected, so that they can be accessed by the subclass, and
modified so that they get locList from the instance field rather than a
parameter.

- All parameters removed from writeEmploymentHistory.  Implementations
of writeEmploymentHistory in the subclass should call
getEmploymentHistoryText and getEmploymentHistoryData directly if they
need that information.

> With regards to your docType example, I wasn't aware that docType would be
> uninitialized in that case. Frankly, it rather surprises me that docType
> would be uninitialized in this case, given that docType is a class variable
> in the class. Why exactly does Java behave that way?

It's a quirk of the implementation.  The superclass constructor runs
before the subclass constructor... and a polymorphic call to a method
(like writeEmploymentHistory) from the superclass constructor can
therefore reach the subclass before its constructor is called.  The
typical way to avoid this is to make sure that all methods called from a
constructor are private or final, so that they can't be overridden.

> Would I be safe in
> assuming that a compiler warning would bring this to my attention?

In Eclipse, I assume?  I don't believe that warning exists.

> Am I correct in thinking that you are warning me against setting a class
> variable based on a value passed in the parameter of a constructor?

I'm confused by your use of the term "class variable".  When most people
say "class variable" or "class field", they mean a static field.  My
example didn't use any static fields (although it would probably be
incorrect to set them from a constructor, if you did have one).

My warning was against calling polymorphic (that is, non-final and non-
private) methods from a constructor.

> I also have several class variables in ResumeWriterAscii: all but two
> of them are constants used throughout the class for formatting. The other
[quoted text clipped - 6 lines]
> throughout the class. I'm not going to have any problems with any of that,
> am I?

Well, in the code you posted, writeEmploymentHistory is going to be
called BEFORE the constructor for ResumeWriterAscii... so you very well
might have problems.  Both variables will be null -- and not because you
set them to null above, but because all reference variables will be
null.

> The compiler is not giving me any errors or warnings anywhere in my
> project.

As I said, I don't think Eclipse has a warning for this one.

> Hmmm, just out of curiousity, I made a class variable called strOutfile
> which was defined as a private String and initialized to null. Then, within
[quoted text clipped - 12 lines]
> via the constructor will inevitably clobber the value of the strOutfile
> class variable.

The warning you're getting is not related to this problem.  It's just
saying that you're hding a field with a parameter.  To get rid of it,
just rename the parameter.

> I assume that's why you brought that to my
> attention: since the compiler gives no warning, I can easily fail to notice
> this problem.

Yep.  I'm starting to wonder how hard it would be to write code to
detect this, and contribute the code to Eclipse.  I'll look into that
today.

> Could you elaborate on what you mean by your last sentence? Are you saying
> that I am doing the right thing in storing the values obtained by the
> getters in local variables and then passing them to  their respective
> writers?

No, I'm saying that all the information should be stored in instance
fields of the object.  I'm given more detailed suggestions about that
may be more helpful.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 11 Jan 2006 17:34 GMT
>> Okay, so what are you saying I should do to prevent these problems? I
>> know
[quoted text clipped - 34 lines]
>    }
> }

Okay, I can see some virtues to making ResumeWriter a concrete class rather
than an abstract one. For example, locList becomes an instance variable so I
don't need to pass it to each method in the concrete ResumeWriter.  But I'm
not clear from your fragment about a few things:
- Am I correct in assuming that my getters will still exist as separate
methods in the concrete versio of ResumeWriter? You're not proposing that I
should take the work done by the getters and move them into the
writeResume() method are you?
- Are you proposing that I write a single version of
writeEmploymentHistory(), presumably the six-parameter version,  or two
versions, one that has two parameters and one that has six parameters? - Am
I correct in understanding that the implementation(s) of
writeEmploymentHistory need to be complete? Since ResumeWriter is not an
abstract class any more, I believe I have to make each implementation
concrete. But what format will my implemenation write: HTML or ASCII or PDF?

If you can answer these last two questions, it will go a long way to helping
me understand exactly what you're proposing.

> There are now two steps to writing a resume: first, create a
> ResumeWriter, and then call its writeResume method.  That's better,
[quoted text clipped - 12 lines]
> modified so that they get locList from the instance field rather than a
> parameter.

Okay, so you do mean for me to leave the getters as standalone concrete
methods. Good, that makes sense to me.

> - All parameters removed from writeEmploymentHistory.  Implementations
> of writeEmploymentHistory in the subclass should call
> getEmploymentHistoryText and getEmploymentHistoryData directly if they
> need that information.

So all the values obtained from my getters become instance variables. Okay,
that's fine. Should these instance variables be public, protected or
private?

>> With regards to your docType example, I wasn't aware that docType would
>> be
[quoted text clipped - 22 lines]
> example didn't use any static fields (although it would probably be
> incorrect to set them from a constructor, if you did have one).

Sorry, that was my fault for misusing the terminology. When I used the term
'class variable', I should have said 'instance variable'; I got the two
terms muddled.

> My warning was against calling polymorphic (that is, non-final and non-
> private) methods from a constructor.

Okay, I'll watch for that from now on.

>> I also have several class variables in ResumeWriterAscii: all but two
>> of them are constants used throughout the class for formatting. The other
>> two class variables are:
>>
>>     private StringUtils stringUtils = null;
>>     private PrintWriter pw = null;

Again, I've mischaracterized these as class variables when they are actually
instance variables. Sorry!

>> They get set to useful values in the constructor and are then used
>> throughout the class. I'm not going to have any problems with any of
[quoted text clipped - 35 lines]
> saying that you're hding a field with a parameter.  To get rid of it,
> just rename the parameter.

Sorry, that was a red herring.

>> I assume that's why you brought that to my
>> attention: since the compiler gives no warning, I can easily fail to
[quoted text clipped - 14 lines]
> fields of the object.  I'm given more detailed suggestions about that
> may be more helpful.
Chris Uppal - 10 Jan 2006 12:02 GMT
> Option 1: ResumeWriter is conceived of as a tool.  You could create one
> without giving it any specific information about any particular person's
[quoted text clipped - 6 lines]
>
> Both options are fine.

Though I'd argue that (1) is in general inferior to (2).  It depends on cases,
of course, but usually (2) is simpler to use and think about, easier to
implement, and more flexible.  So I'd recommend (2) as the norm, with (1) as an
alternative to use in the (relatively rare) cases where it turns out to be more
appropriate.

   -- chris
Chris Smith - 11 Jan 2006 05:30 GMT
> Though I'd argue that (1) is in general inferior to (2).  It depends on cases,
> of course, but usually (2) is simpler to use and think about, easier to
> implement, and more flexible.  So I'd recommend (2) as the norm, with (1) as an
> alternative to use in the (relatively rare) cases where it turns out to be more
> appropriate.

Funny.  I was thinking exactly the opposite; i.e., that (1) is the more
normal and the easier to think about... hence my numbering is "1".

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Chris Uppal - 11 Jan 2006 11:18 GMT
[me:]
> > Though I'd argue that (1) is in general inferior to (2).  [..]
>
> Funny.  I was thinking exactly the opposite; i.e., that (1) is the more
> normal and the easier to think about... hence my numbering is "1".

You astonish me!

   -- chris
Rhino - 11 Jan 2006 17:36 GMT
> [me:]
>> > Though I'd argue that (1) is in general inferior to (2).  [..]
[quoted text clipped - 3 lines]
>
> You astonish me!

I suppose one man's normal is another man's bizarre - and vice versa :-)

Rhino
Chris Uppal - 11 Jan 2006 18:43 GMT
[I have to leave the mess of attributions in place here -- I've trimmed a few
inessentials]

> > > Funny.  I was thinking exactly the opposite; i.e., that (1) is the
> > > more normal and the easier to think about... hence my numbering is
> > > "1".
> > You astonish me!
> I suppose one man's normal is another man's bizarre - and vice versa :-)

Indeed.  What surprised me was not that /someone/ would prefer to (1) to (2) --
which is inevitable -- but that /Chris Smith/ would.  I'm still puzzled...

   -- chris
Chris Smith - 11 Jan 2006 20:10 GMT
> Indeed.  What surprised me was not that /someone/ would prefer to (1) to (2) --
> which is inevitable -- but that /Chris Smith/ would.  I'm still puzzled...

I don't know what to say.  I suppose I'm just weird.

I almost included a warning against "option 2", saying that if you do it
that way, it would be better to call the object Resume instead of
ResumeWriter, since one of its two purposes is to organize the data that
belongs to a resume.  Then I'd suggest collapsing the data into a single
concrete Resume class and factoring out the format-specific writing
parts into a different class hierarchy.  Once that's done, we're back at
option 1.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Chris Uppal - 12 Jan 2006 13:11 GMT
> I almost included a warning against "option 2", saying that if you do it
> that way, it would be better to call the object Resume instead of
[quoted text clipped - 3 lines]
> parts into a different class hierarchy.  Once that's done, we're back at
> option 1.

And the format-specific stuff would be stateless.  Or rather, it would be
stateless with respect to the Resume -- it might still have state for things
like the output stream, page numbers/header/footers, a stack for keeping track
of any nested constructs, etc.

That's pretty much the design I suggested to Rhino before Christmas, so I'm not
going to claim there's anything very much wrong with it ;-)

   -- chris
Chris Uppal - 10 Jan 2006 11:50 GMT
> Going through this thought process again has made me change my mind! As I
> wrote the preceding paragraph, I realized that it still bothered me to
> pass
> parameters that were being ignored and that this bothered me more than
> having overloaded versions of the writeEmploymentHistory() method.

I think that the underlying problem here is that you are making the same
decision in two different places, and that is mucking-up the structure of your
code.

Basically, what you have is a framework for generating CVs.  Your framework
code -- in the abstract ResumeWriter superclass "drives" the process, and the
"pluggable" code (implemented as concrete subclasses of ResumeWriter)
implements the actual formatting.  That's fair enough as far as it goes (though
I'll mention that it has a distinctly procedural flavour to my mind [*]).  But
the problem comes when you try to decide /what/ data to print.  You are
attempting to make that decision in the driver (which is not unreasonable), but
then you hit the problem that that decision can only be made with knowledge of
the capabilities of the concrete document format (HTML has tables, ASCII text
does not).  So although you /want/ your driver to abstract away from the
details of the concrete representation, in this case it cannot do so.  So you
end up with the "knowledge" about how the employment history is represented in
two different places.  In one place (in the driver) you select which of the two
methods to invoke (the simple or complete versions).  In another place (in the
various subclasses) you duplicate that knowledge by the choice of which of the
two method to provide a real implementation for.

([*] I can talk more about that if you are interested.)

In this case, that's (IMO) because you are trying to make the "driver" do too
much.  If it did not know about the details of /what/ data was shown in an
employment history then there would be no problem.  More specifically still, if
it did not /choose/ what data to display and pass that to the implementation
method, then there would be no problem.  I understand that the reason you have
been lead to this position is to avoid the code duplication that would
otherwise occur, but that is leading you astray (I think).  Code duplication is
not -- in itself -- bad.  It's only bad when code is duplicated that is
/necessarily/ the same in both places.  That's to say if one version changed
then the other /must/ change too.  And in this case, the code duplication is
accidental rather than necessary -- it's only an accident that (say) the PDF
generator will display the same data as the HTML generator.  So the fact that
the PDF generator and HTML generator use the same accessors to find the
employment history is not in itself an indicator of a problem.  (Although there
is probably scope for factoring out some utility code which can be shared
between them).

Just because there is common code in (some of) the concrete subclasses does
/not/ mean that you must move that code into the driver.  Certainly you should
consider whether it is appropriate to do so, but that decision should be based
on whether that code should reasonably be expected to live in the driver --
whether it is part of the design of the driver-- the question is not whether it
/can/ but whether it /must/.

Oh, by the way, I would take the large number of parameters (six) to this
method to be another hint that the design is ill-balanced.  If the driver code
were not trying to do too much, then it would not be extracting all that data
from the raw input, and so would not need to pass it to the writing methods.

   -- chris
Andrew McDonagh - 10 Jan 2006 19:38 GMT
>>Going through this thought process again has made me change my mind! As I
>>wrote the preceding paragraph, I realized that it still bothered me to
[quoted text clipped - 55 lines]
>
>     -- chris

I'd say the problems comes from trying to use inheritence to solve a
problem that delegation is better suited to.

From whats been posted, I'd say have the concrete drivers classes as
strategy objects and the abstract base class being a normal concrete
driving class, would be a better design.

Each strategy class then gets the specific data it needs from the single
'model'? concrete class.

as in...

driver::createResume() {
  for each Strategy
     strategy.createResume(this);
}

my 2 cents

Andrew
Oliver Wong - 10 Jan 2006 22:19 GMT
> I'd say the problems comes from trying to use inheritence to solve a
> problem that delegation is better suited to.

   Yes, I agree. It seems like the only thing Rhino's abstract super class
does is provides a unified way of reading the data. That class should be
renamed into "ResumeData" or something like that, and then passed into the
resume-writing objects. The resume-writing objects query the DataReader to
get the information they want, and write whatever they want. A quick sketch
of the design might be something like:

<pseudocode>
class ResumeData {
 public ResumeData(File resumeFile) {
   /*
    * Read in the data, populate the fields of this class.
    */
 }

 public String getName() {
   return this.name;
 }

 public EmploymentHistory getEmploymentHistory() {
   return this.employmentHistory;
 }
}

interface ResumeWriter {
 public void write(ResumeData data);
}

class PDFResumeWriter implements ResumeWriter {
 public PDFResumeWriter(File outputFile) {
   this.outFile = outputFile;
 }

 public void write(ResumeData data) {
   openReadersOn(this.outFile)
   getDataFrom(data);
   writeDataTo(this.outFile);
 }
}
</pseudocode>

   - Oliver
Chris Smith - 11 Jan 2006 05:37 GMT
>     Yes, I agree. It seems like the only thing Rhino's abstract super class
> does is provides a unified way of reading the data.

The other thing (the more important thing, in fact) that the abstract
superclass is doing is to call methods to write the pieces of the
resume.  A method like "writeResume" would exist in the parent, and it
calls overridden methods to write each section in turn.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Oliver Wong - 11 Jan 2006 15:41 GMT
>>     Yes, I agree. It seems like the only thing Rhino's abstract super
>> class
[quoted text clipped - 4 lines]
> resume.  A method like "writeResume" would exist in the parent, and it
> calls overridden methods to write each section in turn.

   But presumably each type of resume (the PDF, HTML, etc.) might have a
different set of sections (I believe Rhino mentioned he doesn't want the
employment history in the ASCII version or something like that), so the
generic writeResume() wouldn't know what set of methods to call anyway, and
would thus probably just be abstract and body-less. In which case, again, I
think it makes more sense to define an interface with a single writeResume()
method.

   - Oliver
Chris Smith - 11 Jan 2006 15:56 GMT
>     But presumably each type of resume (the PDF, HTML, etc.) might have a
> different set of sections (I believe Rhino mentioned he doesn't want the
[quoted text clipped - 3 lines]
> think it makes more sense to define an interface with a single writeResume()
> method.

If that's the case, then it changes things of course.  What was really
said, though, is that the employment history should look different
depending on the type of resume.  Different formats would need different
information fields... but the list of sections doesn't change.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 11 Jan 2006 18:22 GMT
>>     But presumably each type of resume (the PDF, HTML, etc.) might have a
>> different set of sections (I believe Rhino mentioned he doesn't want the
[quoted text clipped - 11 lines]
> depending on the type of resume.  Different formats would need different
> information fields... but the list of sections doesn't change.

You're right Chris; Oliver has misunderstood: each "section" of the resume
(name, job target, employment history, etc. ) appears in every format of the
resume. The exact appearance of a given section will vary slightly in some
resumes though, as you (Chris) has understood.

For instance, the HTML version will look (roughly!!) like this (picture a
table to contain the data):

EMPLOYMENT HISTORY

From/To    Employer    Role                Main Tools
2003/2005 IBM            Programmer    Java, DB2
Developed programs for XYZ department

2001/2003 Foo Corp    Programmer    COBOL, IMS
Developed programs for ABC department

The ASCII version will be very similar but omit the "From/To Employer Role
Main Tools" heading and, of course, it won't be drawn in a table.

The PDF version will also omit the "From/To..." heading but each job will be
a separate box.

So, the content of the Employment History is essentially identical in each
case; only the presence or absence of that one heading distinguishes the
resumes. But the layout of the data is quite different in each case.

Rhino
Chris Smith - 11 Jan 2006 18:46 GMT
> For instance, the HTML version will look (roughly!!) like this (picture a
> table to contain the data):
[quoted text clipped - 10 lines]
> The ASCII version will be very similar but omit the "From/To Employer Role
> Main Tools" heading and, of course, it won't be drawn in a table.

I had misunderstood slightly, then.  As far as I can tell (correct me if
I'm wrong), the table headers are not really resume-dependent data.  You
don't need to pass them into anything.  Hard-coding English strings for
the table headers is still perhaps a bad idea, but just have your HTML
subclass get them from a ResourceBundle instead of trying to have the
ResumeWriter superclass know about them.  The only data you should make
available via the superclass is that which is specific to a single
resume.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 11 Jan 2006 23:58 GMT
>> For instance, the HTML version will look (roughly!!) like this (picture a
>> table to contain the data):
[quoted text clipped - 13 lines]
>
> I had misunderstood slightly, then.

Not as much as Oliver though :-)

> As far as I can tell (correct me if
> I'm wrong), the table headers are not really resume-dependent data.  You
> don't need to pass them into anything.  Hard-coding English strings for
> the table headers is still perhaps a bad idea, but just have your HTML
> subclass get them from a ResourceBundle instead of trying to have the
> ResumeWriter superclass know about them.

I'm not sure what you mean by resume-dependent data. The table headers,
specificially the text strings "From/To", "Employer", "Role", and "Main
Tools", already come from a ResourceBundle since I may want to change their
precise wording; for example, I may want to change "Main Tools" to "Main
Tools Used" and I'd like to only have to change the ResourceBundle, not the
coding in the ResumeWriter class or ResumeWriter subclass. At present, while
ResumeWriter is an abstract class - you haven't yet sold me on the virtues
of making it a concrete class - I have a getter for each of those 4 Strings.
Those then are the 4 "extra" Strings that distinguish the two versions of
writeEmploymentHistory().

I _could_ modify the ResumeWriterHtml class so that its
writeEmploymentHistory() method gets the 4 extra Strings directly; then the
abstract ResumeWriter would only need the two-parameter
writeEmploymentHistory() method and the subclasses would only need to
implement that one version of the method. Then the writeEmploymentHistory()
method would be self-sufficient in ResumeWriterAscii and ResumeWriterPdf,
i.e. it wouldn't need anything but what was passed in via its parameters,
plus any existing instance variables in the class. However, the
writeEmploymentHistory() method in ResumeWriterHtml would need to get those
4 supplemental fields directly. That's not necessarily bad but I liked the
fact that all of the information was being obtained in the concrete getters
of ResumeWriter; that consistency would be gone if ResumeWriterHtml starts
doing its own gets against the ResourceBundle. Is is bad design to do some
of the getting at the subclass level and some at the abstract class level?

> The only data you should make
> available via the superclass is that which is specific to a single
> resume.

Sorry? I don't follow. That seems to be exact opposite of what I'm doing
now; _all_ of the data I'm writing in the ResumeWriter subclasses was
supplied by getters in the ResumeWriter class. The only data that is
specific to one resume is the 4 extra Strings and you just finished telling
me to get them directly in the subclass, not the superclass.

Also, I'm really eager to see your reply to my remarks of 11/01/2006 12:34
PM; I had some key followup questions but it looks like you've missed them
in this increasingly packed thread. Those answers will help me understand
why ResumeWriter should be concrete instead of abstract and make it clear
whether I should have one or two versions of getEmploymentHistory().

Rhino
Chris Smith - 12 Jan 2006 06:57 GMT
> I'm not sure what you mean by resume-dependent data.

I mean data that might conceivably change between two resumes that are
both printed with your program.

> The table headers,
> specificially the text strings "From/To", "Employer", "Role", and "Main
> Tools", already come from a ResourceBundle since I may want to change their
> precise wording; for example, I may want to change "Main Tools" to "Main
> Tools Used" and I'd like to only have to change the ResourceBundle, not the
> coding in the ResumeWriter class or ResumeWriter subclass.

Great.  I'm saying you don't need to provide that information to your
ResumeWriter subclass via the ResumeWriter interface.  Instead, have the
subclass that needs that information look in a common location for the
ResourceBundle, just as you'd handle an average internationalization
problem.  You don't want accessors in ResumeWriter to get the headers,
or parameters to writeEmploymentHistory(), or anything like that.

> At present, while
> ResumeWriter is an abstract class - you haven't yet sold me on the virtues
> of making it a concrete class

If I said to make it concrete, I mistyped.  You should not make
ResumeWriter concrete.  (If you're referring to my comment to Chris
Uppal... ignore it; Chris and I disagree anyway, and that's a good
enough reason to not worry about making more changes than you need to.  
Even if you took my advice, you'd have to do more than JUST make
ResumeWriter concrete.)

> I _could_ modify the ResumeWriterHtml class so that its
> writeEmploymentHistory() method gets the 4 extra Strings directly; then the
[quoted text clipped - 10 lines]
> doing its own gets against the ResourceBundle. Is is bad design to do some
> of the getting at the subclass level and some at the abstract class level?

I see the two as fundamentally different.  One of them is getting
information that needs to go into this resume.  The other is getting
some parameter regarding the FORMAT of the resume.  The latter belongs
in the format-specific subclass.

> > The only data you should make
> > available via the superclass is that which is specific to a single
[quoted text clipped - 5 lines]
> specific to one resume is the 4 extra Strings and you just finished telling
> me to get them directly in the subclass, not the superclass.

I mean one resume... not one type of resume.  That is, ResumeWriter
should provide data that's specific to John Smith's resume from London.

> Also, I'm really eager to see your reply to my remarks of 11/01/2006 12:34
> PM; I had some key followup questions but it looks like you've missed them
> in this increasingly packed thread. Those answers will help me understand
> why ResumeWriter should be concrete instead of abstract and make it clear
> whether I should have one or two versions of getEmploymentHistory().

Okay, it looks like the confusion came because I forgot the word
"abstract" in the sample code I posted in the parent of that article.  I
never intended for ResumeWriter to be concrete.

Signature

www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation

Rhino - 12 Jan 2006 20:07 GMT
>> I'd say the problems comes from trying to use inheritence to solve a
>> problem that delegation is better suited to.
[quoted text clipped - 39 lines]
> }
> </pseudocode>

Forgive me for taking so long to respond to this suggestion as I have; I've
been trying to understand and use the suggestions of Chris Smith and Chris
Uppal and have only now gotten around to your remarks.

Your main observation makes a lot of sense: "It seems like the only thing
Rhino's abstract super class does is provides a unified way of reading the
data." That is exactly the case: I _am_ using my abstract superclass to get
data from my ResourceBundle via a bunch of getters; the write methods are
all abstract and each one is implemented differently in the classes that
write the Resumes.

Your suggestions for a redesign follow logically from that observation. I
think it makes perfect sense to make the abstract ResumeWriter into a class
called ResumeData that contains only the getters and handle the writing via
a different mechanism, an interface. I looked at your pseudocode and found
myself somewhat unclear over exactly what you meant in some places; at first
glance, it seemed you'd left a fair bit to my imagination and I have an
(over?) active imagination :-)

But I knocked together the code you described and got it to work after a bit
of trial and error. I even think I understand it. So now I'm going to try to
describe it back to you (and anyone else who is still watching this thread)
and make sure that I have written the code as you intended and that I
understand how it works correctly.

Okay, let's start with ResumeData. I simply copied abstract class
ResumeWriter to a new concrete class called ResumeData and deleted all of
the abstract writeXXX methods.

Here is a brief excerpt of the class with the package statement and all Java
and Javadoc comments removed for brevity:

------------------------------------------------------------------------------------------------------------
import java.util.ResourceBundle;

public class ResumeData {

   private ResourceBundle resourceBundle;

   public ResumeData(ResourceBundle resumeResourceBundle) {
       this.resourceBundle = resumeResourceBundle;
   }

   public String getCandidateNameData() {
       String candidateNameData =
this.resourceBundle.getString("CandidateNameData");
       return candidateNameData;
   }

   public String[] getCandidateAddressData() {
       String[] candidateAddressData = (String[])
this.resourceBundle.getObject("CandidateAddressData");
       return candidateAddressData;
   }
}
------------------------------------------------------------------------------------------------------------

Now, I'm afraid that the only interfaces I've written for myself contain
only constants; I've never written any methods in my interfaces before. But
I dug into some reference material to find out the rules for methods in
interfaces and found that they are always public and abstract, just like the
writeXXX methods had been in the abstract ResumeWriter class. Now, to
prevent any confusion with other parts of this thread, I decided to name the
interface that you call ResumeWriter in your pseudocode
"ResumeWriterInterface"; that should make it crystal clear that I'm _not_
referring to the abstract ResumeWriter class.

So, here's the code for that interface, again with the package statement and
all comments removed:

------------------------------------------------------------------------------------------------------------
public interface ResumeWriterInterface {

   public void write(ResumeData resumeData);
}
------------------------------------------------------------------------------------------------------------

Okay, so now I only need to implement ResumeWriterInterface in each class
that writes a resume. That class will be free to write whatever data it
wants, as long as it comes from ResumeData, in whatever format it likes. It
will be able to choose any subset of that data and therefore write as much
or as little of the information as it wants.

I created a new version of ResumeWriterAscii called ResumeWriterAscii2 to
write the resume this new way.

Here is an excerpt of the code, again omitting the package statement,
comments, for brevity. I've also omitted most of the writeXXX methods since
the ones I've shown are enough to demonstrate what I'm doing so that you can
tell me if I'm doing the right things:
------------------------------------------------------------------------------------------------------------

import java.io.PrintWriter;
import java.util.ResourceBundle;

public class ResumeWriterAscii2 implements ResumeWriterInterface {

   private PrintWriter printWriter = null;

   public ResumeWriterAscii2(String outfile, ResourceBundle resourceBundle)
{

       ResumeUtilities.deleteFile(outfile);
       this.printWriter = ResumeUtilities.openOutputFile(outfile);
       ResumeData resumeData = new ResumeData(resourceBundle);
       write(resumeData);
       ResumeUtilities.closeOutputFile(this.printWriter);
   }

   public final void write(ResumeData resumeData) {

       writeCandidateAndTarget(resumeData.getCandidateNameData(),
               resumeData.getCandidateAddressData(),
               resumeData.getJobTargetText(),
resumeData.getJobTargetData());
       writeEmploymentHistory(
               resumeData.getEmploymentHistoryText(),
resumeData.getEmploymentHistoryData());
       writeEducationAndEmployability(
               resumeData.getFormalEducationText(),
resumeData.getFormalEducationData(),
               resumeData.getRecentEducationText(),
resumeData.getRecentEducationData(),
               resumeData.getEmployabilityText(),
resumeData.getEmployabilityData());
       writeSoftwareSkills(resumeData.getAddendumText(),
               resumeData.getOsText(), resumeData.getOsData(),
               resumeData.getComputerLanguagesText(),
resumeData.getComputerLanguagesData(),
               resumeData.getDatabasesText(),
resumeData.getDatabasesData(),
               resumeData.getWordProcText(), resumeData.getWordProcData(),
               resumeData.getEditorsText(), resumeData.getEditorsData(),
               resumeData.getOtherToolsText(),
resumeData.getOtherToolsData());
       writeOtherExperience(resumeData.getExperienceText(),
resumeData.getExperienceData());
       writeImportantNote(resumeData.getNoteData());
   }

   public final void writeCandidateAndTarget(String candidateNameData,
String[] candidateAddressData, String jobTargetText, String jobTargetData) {

       this.printWriter.println(this.stringUtils.wrap(candidateNameData.toUpperCase(),
ResumeWriterAscii2.DELIMS, ResumeWriterAscii2.LINE_WIDTH,
ResumeWriterAscii2.INDENT1, ResumeWriterAscii2.INDENT2_NORMAL,
ResumeWriterAscii2.BLANK_LINES_BEFORE_0,
ResumeWriterAscii2.BLANK_LINES_AFTER_1));
       for (String candidateAddressLine : candidateAddressData) {
           this.printWriter.println(candidateAddressLine);
       }
       this.printWriter.println(this.stringUtils.wrap(jobTargetText + ": "
+ jobTargetData, ResumeWriterAscii2.DELIMS, ResumeWriterAscii2.LINE_WIDTH,
ResumeWriterAscii2.INDENT1, ResumeWriterAscii2.INDENT2_NORMAL,
ResumeWriterAscii2.BLANK_LINES_BEFORE_1,
ResumeWriterAscii2.BLANK_LINES_AFTER_2));
   }

}

------------------------------------------------------------------------------------------------------------

ResumeWriterAscii2 implements ResumeWriterInterface but doesn't extend
anything. It gets an instance of ResumeData in its constructor, then passes
it to the implemenation of the write() method which was defined in the
interface. The write() method implementation uses the getters in the
ResumeData class to obtain the information that is being written to the
resume. The writeXXX methods in ResumeWriterAscii2 simply write the desired
information to the file in the desired format, ASCII in this case.

The other file writer classes, ResumeWriterPdf2 and ResumeWriterHtml2, would
have identical constructors to ResumeWriterAscii2. Their write() methods
might vary somewhat; for example, ResumeWriterHtml2 will obtain a little bit
more information from ResumeData, namely the "From/To", "Employer", "Role",
and "Main Tools" strings. The writeXXX methods would take whatever data was
obtained in the write() method and write it to the file in whatever format
was desired.

So, have I understood you correctly? Is that basically what you intended?

Assuming it is, I have one followup question.

As I was writing ResumeWriterAscii2, it occurred to me that it could be
defined a little differently and still work just fine; in fact, the code
would actually get just a little more concise. The problem is that I'm not
sure if this alternate approach would be better or worse from a design point
of view. The alternate method is quite simple: make ResumeWriterAscii2
extend ResumeData _and_ implement ResumeWriterInterface.

------------------------------------------------------------------------------------------------------------

import java.io.PrintWriter;
import java.util.ResourceBundle;

public class ResumeWriterAscii2 extends implements ResumeWriterInterface {

   private PrintWriter printWriter = null;

   public ResumeWriterAscii2(String outfile, ResourceBundle resourceBundle)
{

       super(resourceBundle); //new line
       ResumeUtilities.deleteFile(outfile);
       this.printWriter = ResumeUtilities.openOutputFile(outfile);
//      ResumeData resumeData = new ResumeData(resourceBundle); //no longer
needed
//      write(resumeData); //replace with following line
      write(); //new version of the previous line
       ResumeUtilities.closeOutputFile(this.printWriter);
   }

//    public final void write(ResumeData resumeData) { //replace with the
following line
     public final void write() { //new version of the previous line

/* no longer need 'resumeData.' in front of each getter */

       writeCandidateAndTarget(getCandidateNameData(),
               getCandidateAddressData(),
               getJobTargetText(), getJobTargetData());
       writeEmploymentHistory(
               getEmploymentHistoryText(), getEmploymentHistoryData());
       writeEducationAndEmployability(
               getFormalEducationText(), getFormalEducationData(),
               getRecentEducationText(), getRecentEducationData(),
               getEmployabilityText(), getEmployabilityData());
       writeSoftwareSkills(getAddendumText(),
               getOsText(), getOsData(),
               getComputerLanguagesText(), getComputerLanguagesData(),
               getDatabasesText(), getDatabasesData(),
               getWordProcText(), getWordProcData(),
               getEditorsText(), getEditorsData(),
               getOtherToolsText(), getOtherToolsData());
       writeOtherExperience(getExperienceText(), getExperienceData());
       writeImportantNote(getNoteData());
   }

   public final void writeCandidateAndTarget(String candidateNameData,
String[] candidateAddressData, String jobTargetText, String jobTargetData) {

       this.printWriter.println(this.stringUtils.wrap(candidateNameData.toUpperCase(),
ResumeWriterAscii2.DELIMS, ResumeWriterAscii2.LINE_WIDTH,
ResumeWriterAscii2.INDENT1, ResumeWriterAscii2.INDENT2_NORMAL,
ResumeWriterAscii2.BLANK_LINES_BEFORE_0,
ResumeWriterAscii2.BLANK_LINES_AFTER_1));
       for (String candidateAddressLine : candidateAddressData) {
           this.printWriter.println(candidateAddressLine);
       }
       this.printWriter.println(this.stringUtils.wrap(jobTargetText + ": "
+ jobTargetData, ResumeWriterAscii2.DELIMS, ResumeWriterAscii2.LINE_WIDTH,
ResumeWriterAscii2.INDENT1, ResumeWriterAscii2.INDENT2_NORMAL,
ResumeWriterAscii2.BLANK_LINES_BEFORE_1,
ResumeWriterAscii2.BLANK_LINES_AFTER_2));
   }

}

------------------------------------------------------------------------------------------------------------

Naturally, you also need to change the definition of the write() method in
the ResumeWriterInterface() so that it doesn't expect any parameters.

So, is that variation any better or worse than what you originally proposed?
If so why?

By the way, the approach you have suggested inadvertently solves another
problem that I've been struggling with for the last while! I also have an
applet that displays my resume but I was having a heck of a time trying to
figure out how it could take advantage of the original abstract ResumeWriter
class. That's because it doesn't write to a file: it creates GUI components
and displays them in an applet; therefore the writerXXX methods don't apply
to the applet. Since an applet has to subclass JApplet, I was at a loss in
how to use ResumeWriter with the applet. Now that I have a concrete
ResumeData class, I can simply use it to gather the data that I need, then
use the existing methods within the applet to turn that data into GUI
components without having to access the ResourceBundle directly in the
ResumeApplet class.

Therefore, you've killed two birds with one stone, which is pretty good
considering you didn't even know that the second bird even existed :-)

P.S. I'm copying you via email, Oliver, in case you've wandered away from
this thread. I just want to make sure you get my thanks for pointing out an
even better solution to my problem!

Rhino
Oliver Wong - 12 Jan 2006 21:12 GMT
[snip]

> I knocked together the code you described and got it to work after a bit
> of trial and error. I even think I understand it. So now I'm going to try
> to describe it back to you (and anyone else who is still watching this
> thread) and make sure that I have written the code as you intended and
> that I understand how it works correctly.

[code and explanation snipped]

   Looks mostly good to me. However, I'd like to comment on your
ResumeWriterAscii2 class, and in particular, the write() method. Here's the
code you provided:

>    public final void write(ResumeData resumeData) {
>
[quoted text clipped - 26 lines]
>        writeImportantNote(resumeData.getNoteData());
>    }

   Personally, I don't like how writeCandidateAndTarget() and the others
take about a million parameters. I would just pass in resumeData, and let
them extract the data they want, than to pre-extract the data for them, and
then pass all of them in. Otherwise, you're sort of placing knowledge of
what writeCandidateAndTarget() is and isn't interested inside of write().

   Maybe later on, your writeXXX() methods will become a lot "smarter" or
more complex. For example, in your writeEmploymentHistory(), you write a
full description of your responsibilities for your previous 3 jobs (and thus
would need to get these "previous job responsibility" data items from the
ResumeData), but not for any of the ones older than 3. Or perhaps
writeEmploymentHistory() will query other parts of your ResumeWriter, to
find out how much space is left on the page it's writing on, and vary the
detail level it wants to get into accordingly.

   What you could do is make ResumeData become hierarchical. It looks like
you've done a bit of that, because you named some of the getters
"getFooData()" instead of "getFooText()", but it looks like you haven't got
a clear organizational system. For example, why is getExperienceText() not a
part of getExperienceData()?

<example>
int numberOfYears =
resumeData.getExperienceData().getJobNumber(4).getYearsEmployedThere();

/*Use better method names than the above, they were named this way just for
clarity*/
</example>

> ResumeWriterAscii2 implements ResumeWriterInterface but doesn't extend
> anything. It gets an instance of ResumeData in its constructor, then
[quoted text clipped - 3 lines]
> resume. The