views:

159

answers:

5

I have the following two classes and I am starting to see a pattern that even with my little Java background is screaming for a fix. Every new Object is going to require a set of Actions and the number of classes could grow out of hand. How do I refactor this into a generic DeleteAction class?

I know some of the answers will be use Hibernate, or JPA, or some Framework, but at the moment I can't utilize any of those tools. Oh, and our server only has jdk 1.4 (don't ask!). Thanks.

public class DeleteCommitmentAction implements ControllerAction {

  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {

    CommitmentListDAO clDAO = new CommitmentListDAO();
    CommitmentItemForm ciForm = new CommitmentItemForm(clDAO);
    CommitmentItem commitmentItem = ciForm.deleteCommitmentItem(request);

    RequestDispatcher view = request.getRequestDispatcher("views/commitmentView_v.jsp");
    view.forward(request, response);
  }
}

.

public class DeleteProgramAction implements ControllerAction {

  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {

    ProgramDAO prgDAO = new ProgramDAO();
    ProgramForm prgForm = new ProgramForm(prgDAO); 
    ProgramForm prg = prgForm.deleteProgram(request);

    RequestDispatcher view = request.getRequestDispatcher("views/programView_v.jsp");
    view.forward(request, response);
  }
}

The approach that I think I need to take is to make interfaces. Starting with the DAO, I have created the following interface.

public interface GenericDao {
  public void create(Object object, STKUser authenticatedUser) throws DAOException;
  public void retreive(String id, STKUser authenticatedUser) throws DAOException;
  public void update( final Object object, STKUser authenticatedUser) throws DAOException;
  public void delete(String id, STKUser authenticatedUser) throws DAOException;
}

And then in my DeleteAction class I tried this

GenericDao gDAO = new GenericDao();

but Eclipse is stating "Cannot instantiate the type GenericDao" So now I am lost.

Update: Based on Péter Török's answer, here is what I have:

This is the servlet specific for handling operations on Commitment Items:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String schema = General_IO.getSchemaPath("TPQOT_463_COMMITMENT", request.getServerName());
    CommitmentListDAO clDAO = new CommitmentListDAO();
    CommitmentItemForm ciForm = new CommitmentItemForm(clDAO);
    CommitmentItem commitmentItem = new CommitmentItem();

    // I think this is the Application Controller Strategy
    actionMap.put(null, new ListCommitmentsAction());
    actionMap.put("list", new ListCommitmentsAction());
    actionMap.put("view", new ViewCommitmentItemAction(schema));
    //actionMap.put("delete", new DeleteCommitmentAction(schema));
    // Change to the Generic DeleteAction and pass in the parameters
    actionMap.put("delete", new DeleteAction(ciForm, commitmentItem, schema,  "views/commitmentDeleteConfirm_v.jsp",  "views/commitmentView_v.jsp" ));
    // When happy with this approach, change other actions to the Generic Versions.


    actionMap.put("sqlConfirmDelete", new DeleteCommitmentConfirmAction());
    actionMap.put("edit", new EditCommitmentItemAction(schema));
    actionMap.put("sqlUpdate", new UpdateCommitmentItemAction1(schema));
    actionMap.put("new", new NewCommitmentFormAction(schema));
    actionMap.put("sqlInsert", new InsertCommitmentItemAction1(schema));

    String op = request.getParameter("method");
    ControllerAction action = (ControllerAction) actionMap.get(op);

    if (action != null) {
        action.service(request, response);
    } else {
        String url = "views/errorMessage_v.jsp";
        String errMessage = "Operation '" + op + "' not a valid for in '" + request.getServletPath() + "' !!";
        request.setAttribute("message", errMessage);
        request.getRequestDispatcher(url).forward(request, response);
    }
}

And here is the Generic DeleteAction:

public class DeleteAction implements ControllerAction {

  private Form form;
  private Object obj;
  private String schema = null;
  private String xPage;
  private String yPage;

  public DeleteAction(Form form, Object item, String schema, String yPage, String xPage) {
    this.form = form;
    this.item = item;  //passed in javabean??
    this.schema = schema;
    this.xPage = xPage;
    this.yPage = yPage;
  }

  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    item = form.delete(request);

    /* Database schema is described in xml files.
    Hash maps of field names, sizes, and titles; foreign key names, titles, 
    lookup tables; and primary keys information are used to dynamically 
    build HTML forms in the views.
    */      
    HashMap test = ReadTableSchema.returnSchema(schema);
    HashMap hshFields = (HashMap) test.get("hshFields");
    HashMap hshForeignKeys = (HashMap) test.get("hshForeignKeys");
    HashMap hshPrimaryKeys = (HashMap) test.get("hshPrimaryKeys");

    request.setAttribute("hshFields", hshFields);
    request.setAttribute("hshPrimaryKeys", hshPrimaryKeys);
    request.setAttribute("hshForeignKeys", hshForeignKeys);

    request.setAttribute("item", item);
    request.setAttribute("form", form);
    request.setAttribute("pageName", "Delete");

    //Check for deletion authorization if successful forward to the confirmation page
    if (form.isSucces()) {
      request.setAttribute("message", "Please confirm permanent deletion of the data below.");
      RequestDispatcher view = request.getRequestDispatcher(yPage);
      view.forward(request, response);
    } else {
      // Not authorized to delete the data so just re-display
      RequestDispatcher view = request.getRequestDispatcher(xPage);
      view.forward(request, response);
    }
  }
}

then here is the interface (right now just for delete) that will be used by all forms.

public interface CRUD {
    public Object delete(HttpServletRequest request);
}
+1  A: 

GenericDAO is an interface, it cannot be instantiated directly. I don't know much Java, but every OOP language is pretty much the same. So what you need to do is create a concrete implementation of your interface (as a class) and then instantiate that instead. Something like this (sorry for the C# code but you get the idea):

public interface IGenericDAO {
    void create(...);
}

and the implementation:

public class GenericDAO implements IGenericDAO {

    public void create(...) {
        /* implementation code */
    }
}

Does that make sense?

kprobst
public class GenericDAO implements IGenericDAO
nanda
Yes, thus my comment about being sorry for the C# code.
kprobst
"every OOP language is pretty much the same"... not really. CLOS is quite a bit different from Java, for example. Heck, even JavaScript's object system, which is prototype-based, is quite different from Java. It is true that C# and Java are very similar, however.
Laurence Gonsalves
This code compiles under java XD (without the ...s).
Leo Izen
A: 

Interface can't be instantiated. Instead, you should create a concrete class implementing the interface and instantiate this class.

nanda
+3  A: 

You can't instantiate an interface, you need a concrete subclass for that. However, creating concrete subclasses just increases the number of classes, which you are trying to avoid. It is better to use composition instead of inheritance.

Namely, if you manage to make a common interface for the forms, and hide the actions deleteCommitmentItem, deleteProgram etc. behind one single method, you can parametrize your action instances with the required form (or a factory to provide this), e.g.:

public class GenericAction implements ControllerAction {
  private Form form;
  private String page;

  GenericAction(Form form, String page) {
    this.form = form;
    this.page = page;
  }

  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {

    Item item = form.performDelete(request);

    RequestDispatcher view = request.getRequestDispatcher(page);
    view.forward(request, response);
  }
}

...
CommitmentListDAO clDAO = new CommitmentListDAO();
CommitmentItemForm ciForm = new CommitmentItemForm(clDAO);
GenericAction deleteCommitmentAction = new GenericAction(ciForm, "views/commitmentView_v.jsp");

ProgramDAO prgDAO = new ProgramDAO();
ProgramForm prgForm = new ProgramForm(prgDAO); 
GenericAction deleteProgramAction = new GenericAction(prgForm, "views/programView_v.jsp");

Thus you need no new classes for new kinds of actions, just instantiate GenericAction with different parameters.

Péter Török
yes, this what I am actually trying to do but I am stumped. Because I Still want to use the delete methods from all my concrete DAOs (CommitmentDAO, ProgramDAO, etc....) I don't think I would be on the right track using the GenericDAO and IGenericDAO.
jeff
@jeff, note that only the form uses the DAO object - the action is not directly dependent on it. Thus you need no `GenericDAO`, you can continue using your existing DAO classes, as shown in my example. Unless I am misunderstanding something completely - if my idea doesn't make sense to you, please clarify.
Péter Török
@ Péter Török, This is exactly what I was looking for. But Don't I need to also pass in the javabean (CommitmentItem, Program, etc...) as a parameter, which gets populated in the generic Actions and passed to the view? But then I am confused at what Item shoudl be?? I have updated my original question.
jeff
@jeff, glad to hear I could help :-) In the code you show, `item` is returned from `form.delete(request)` in `DeleteAction.service()`, so you don't need to pass it via the constructor - in fact, this effectively overwrites the reference passed to the constructor earlier.
Péter Török
@ Péter Török, right item is returned from all my forms delete methods. But what is Item (capital I). Look at my code in my updated question. I change to a passed in parameter as an Object. The object with be an empty bean that gets populated from the DAO. form.delete acutally returns the bean of the data to delete for verification. As does form.view, form.update the object will be the bean such as Program or CommitmentItem, etc... Is Item some generic java container such as List or Object or is it a custom object class?
jeff
@jeff, now I get what you mean - sorry for being a bit slow. I did not give any particular significance to the `Item` class - just assumed that if there are so many classes named `*Item`, there must / should be a common superclass to them. If there is none, you may still consider creating it, if they have a meaningful common interface. However, you might just get away with an `Object` as well, if all you do with it is pass it to `request.setAttribute`.
Péter Török
@ Péter Török, right now I don't have a superclass because these objects really couldn't have an interface. These Items/javabeans/transfer objects represent things such as Employee, Programs, CommmitmentItem, Reports, ScheduleItem, etc with unique setters/getters. Thanks for your help in showing me this approach, which has seriously eliminated duplicate Action Classes!! I now have DeleteAction, ViewAction, UpdateAction etc, instead of DeleteCommitmentItemAction, ViewCommitmentItemAction, UpdateCommitmentItemAction, ViewEmployeeAction, ViewReportAction, etc...
jeff
+1  A: 

It's clear by your naming that you already have implemented DAO objects (CommitmentListDAO, ProgramDAO). You should (probably) modify these classes to implement your new interface. Then your problem now becomes, how do you know which DAO to instantiate when you're in your generic delete action. Either that DAO should be passed into your action directly, or some other information on how to instantiate it (either a Class or factory) must be provided to your action.

Kirk Woll
@Kirk Woll: You are right. I modified CommitmentListDAO, ProgramDAO to implement the interface, but you are right, it needs to know which DAO to use and this is where I am getting stumped. jdk 1.4 does not have paramterized variables.
jeff
First choice, why don't you pass it in when creating the DeleteAction? Second choice, pass in the class and call newInstance() method on it. If neither of those are an option, can you elaborate on how you are instantiating DeleteAction? (looks like some MVC pattern)
Kirk Woll
+1 for recommending to have the DAOs implement the interface. At my last gig, they didn't do this. Their internal table maintenance servlets were 100% cut-and-paste copies, except for the DAOs and model class specific to any particular maintenance screen. Using the interfaces would have reduced the code by 66%.
Tony Ennis
+1  A: 

One servlet per action is not unreasonable. Consider that if you have to do some action X, then you need to do X. Write a servlet to do X. It's that simple.

As you're noticing, this could lead to a lot of nearly identical servlets. That's ok because now you can use delegation (as Peter Torok recommends) or inheritance to move all the shared and abstracted code into one place. Which is better? Either is better than neither. You are 90% of the way to victory if you use one or both as appropriate.

I prefer a main servlet from which all others inherit. This allows me to wrap every service call in a consistent proper transaction in the base controller class. The subclasses never have to worry about it. This code shows the gist of it.

public class BaseControllerAction implements ControllerAction {

  public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException {
    Connection conn = null;
    try {
        conn = getAConnection();
        log.info("+++ top of "+getClass().getName());
        conn.getTranaction().begin();

        String dest = go(request, response, conn);

        conn.getTransaction().commit();
        RequestDispatcher view = request.getRequestDispatcher(dest);
        view.forward(request, response);

    } catch (Exception e) {
        conn.getTransaction().rollback();
    } finally {
        conn.close();
        log.info("--- Bottom of "+getClass().getName());
    }


  protected abstract String go(HttpServletRequest request, HttpServletResponse response, Transaction transaction) throws ServletException;

}

and now you can implement your servlet:

public class DeleteCommitmentAction extends BaseControllerAction {
  protected String go(HttpServletRequest request, HttpServletResponse response, Connection conn) throws ServletException {

   // Given what this method is supposed to do, it's very reasonable
   // to refer to models and DAOs related to deleting commitments.
   Long id = new Long(request.getParameter("id"));
   CommitmentDAO.delete(conn, id);
   return "views/commitmentView_v.jsp";
  }
}

So now none of your servlets have to worry about transactions or opening and closing connections. They only have to worry about the details of their specific task. Obviously I don't know your system so I can't give detailed suggestions but this is how I did two decent-sized apps recently. They have about 30 servlets each. But the servlets are generally about 15 lines long. I ended up with a utility class that implemented the sorts of tasks needed by all the servlets. Poor man's delegation, perhaps.

Tony Ennis