3

Problem Description

I have an abstract Paper class that contains common properties of all papers and one or more child classes of paper that add additional information for that type of paper. I then have a HashMap<String, Paper> to store multiple papers.

My application allows the user to update a paper by providing a the pid and then supplying the attributes and values to update. The issue I am having is how do I update the properties on the sub classes when all I have is the super class.

What is the best way/practice to handle this situation?

Class Structure

public abstract class Paper {
    String pid;
    String title;
    String author;
}


public class Publication extends Paper {
    int pages;
}

public class PHDThesis extends Paper {
    String supervisor;
}

My Current Attempt

This is what I currently have** and it works by using instance of; but I feel there should be a better way to do this.

import java.util.*;

public class App {
    public static abstract class Paper {

        private String title;
        private String author;

        public Paper(String title, String author) {
            this.title = title;
            this.author = author;
        }

        public void update(String title, String author) {
            this.title = title;
            this.author = author;
        }
    }


    public static class Publication extends Paper {

        private int pages;

        public Publication(int pages, String title, String author) {
            super(title, author);
            this.pages = pages;
        }

        public void update(String title, String author, int pages) {
            super.update(title, author);
            this.pages = pages;
        }

    }

    public static class PHDThesis extends Paper {

        private String supervisor;

        public PHDThesis(String supervisor, String title, String author) {
            super(title, author);
            this.supervisor = supervisor;
        }

        public void update(String title, String author, String supervisor) {
            super.update(title, author);
            this.supervisor = supervisor;
        }
    }

    public static void main(String[] args) {
        HashMap<String, Paper> papers = new HashMap<String, Paper>();

        papers.put("P001", new PHDThesis("My Super", "My PHD Title", "My Author"));
        papers.put("P002", new Publication(22, "My Pub Title", "My Author"));

        Paper p = papers.get("P001");

        if (p instanceof PHDThesis) {
            ((PHDThesis)p).update("New Title", "New author", "New Super");
        } else if (p instanceof Publication) {
            ((Publication)p).update("New Title", "New author", 33);
        }
    }
}

** reduced test code, actual code is much more complex and better laid out.

MitMaro
  • 5,607
  • 6
  • 28
  • 52
  • I have a feeling this has been asked before but I was unable to find an answer. Please link any duplicates if there are any. :) – MitMaro Mar 11 '12 at 22:20

3 Answers3

3

You can create an object called UpdateBundle with getters for each attribute.

Then the Paper class will have a method update(UpdateBundle) which each child will implement differently.

All you have to do is call that method for each child and they will know how to handle it.

On a separate note, i don't see why the paper class is abstract. You seem to have no abstract methods in it.

public abstract class Paper {
    String pid;
    String title;
    String author;

    public void update(PaperUpdateBundle bundle)
    {
        pid = bundle.getPID();
        title = budnle.getTitle();
        author = bundle.getAuthor();
    }
}


public class Publication extends Paper {
    int pages;

    public void update(PaperUpdateBundle bundle)
    {
       super.update(bundle);
       pages = bundle.getPages();
    }
}

public class PHDThesis {
    String supervisor;


    public void update(PaperUpdateBundle bundle)
    {
       super.update(bundle);
       supervisor = bundle.getSupervisor();
    }
}

public interface PaperUpdateBundle
{
    String getPID();
    String getTitle();
    String getAuthor();
    int getPages();
    String getSupervisor();
}
Savvas Dalkitsis
  • 11,476
  • 16
  • 65
  • 104
  • The Paper class doesn't need to be abstract in the example but in the actual code this is being used in there are abstract methods. – MitMaro Mar 11 '12 at 22:52
  • I also really like this answer. – MitMaro Mar 11 '12 at 22:53
  • I would just make a note of warning - any extra parameters will need to be introduced both in the extending object and in the UpdateBundle. Might not be an issue but could cause maintenance issues. – andy.xyz Mar 11 '12 at 23:22
  • 2
    On the contrary. This requirement will actually enforce type safety and thus reduce any potential bugs introduced by alternative solutions such as pulling data from a map. – Savvas Dalkitsis Mar 12 '12 at 00:15
2

Create a method

public void update( Map<String, Object> parameters );

to all Papers and pull the relevant properties from it in the Paper implementations.

In Publication it might look like:

public void update( Map<String, Object> parameters ) {
  super.update( parameters );

  this.pages = parameters.get( "pages" );
}
andy.xyz
  • 2,567
  • 1
  • 13
  • 18
  • Not sure why the downvote here, a comment would help. Either way the main point is that the extending class should be responsible for understanding it's extended properties and not the calling class. Reflecton of instanceof are breaking encapsulation. – andy.xyz Mar 11 '12 at 22:31
  • I removed my down-vote. But your killing a mosquito with an atomic bomb – UmNyobe Mar 11 '12 at 22:32
  • Your answer is not wrong but it lacks any compile time checks on the parameters. It would also break if I wanted to pass an object (or other type that can't be encoded as a string) as a parameter. It is, however, slightly better from an OOP perspective. – MitMaro Mar 11 '12 at 23:18
0

The problem with the accepted answer is that it requires you to update all of the properties manually. If the list of properties changes, you have to change the update() method or things will get out of sync. In my experience this happens frequently. And then you've got to spend a lot of time trying to track down the bug.

A different way (I won't call it a "better" way) is to use reflection or some third party library to copy the fields. There are some tradeoffs, though. The advantage is that your code requires a lot less work and will (probably) have fewer bugs. The downside is that your code will be slower, less flexible, and lack compile-time checks.

I have sometimes used Jackson's ObjectMapper.convertValue() to do this. You can find other ways to do it here: Copy all values from fields in one class to another through reflection.

Community
  • 1
  • 1
ccleve
  • 15,239
  • 27
  • 91
  • 157