Sometimes abstraction is taken to the extreme, either with an interface that extends an interface that extends an interface (see the [interface.indirection] rule), or where no concrete types for a model class are written at all.

For example, imagine there was an Account object that needed to be read from a database table into a Java application.  The Account potentially needs to be used throughout the application for complex calculations.  Rather than defining a class called Account, the developer decided to read all database records into a generic proxy class called DbObject, so the properties of the Account record would be stored as key/value pairs in a Map.  eg.

public class DbObject
    // Name of the record type we have, such as ”Account”
    private String typeName;

    // Map of property name [String] => Object
    private Map values = new HashMap();   


    public Object getValue(String propertyName)
        return values.get(propertyName);

    public void setValue(String propertyName, Object newValue)
        return values.put(propertyName,newValue);

//elsewhere in another class
DbObject myAccount = DbUtil.load(“Account”,1234L);
BigInteger balance = (BigInteger)myAccount.getValue(“balance”);

Since the properties of an Account are already well known (we know it has a balance for example) why not define a model class as a series of named properties?

The disadvantages of the proxy approach are immediate:

  • Compile-time type safety is replaced by ClassCastExceptions at runtime, when balance suddenly becomes an Integer value in the database.

  • Maintainability is difficult to achieve because all references to balance may not be found easily (they could even be in a properties or XML file).

  • Helper methods to deal with the Account class can't be added to the Account class because it doesn't exist.

  • If the account balance is referenced with any frequency in the code, the constant dereferencing and casting becomes tedious. It slows development.

  • Code assist tools built into IDEs can't show the available values on the Account record.

If use of a generic proxy type was a good way to write software, we would all be using the Java Reflection API to invoke method calls.

Tip: Try and avoid abstract or proxy classes where everything is well known and no extension or inheritance is planned.  They can easily be added later.

It doesn't take much more effort to hook in a real Account class, which database persistence layers such as Hibernate can easily load with data for you.

public class Account extends BaseModel
    private BigInteger balance;

    public BigInteger getBalance()
        return balance;

    public void setBalance(BigInteger balance)
        this.balance = balance;

Exception: The generic proxy class approach works well where all the properties of a class aren't known at development time.  In this case you trade the ability to easily manipulate properties of a class that will rarely change, for flexibility of content.

For example, imagine a generic reporting engine that executes reports that are extensions of a Report class.  The Report class has a variable length list of parameters it needs to generate a report.  The screen to generate a report would be written once, without knowledge of the actual parameters that future report implementations provide, because different kinds of reports have different numbers of parameters.

blog comments powered by Disqus