Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

The busy Java developer's guide to db4o: Structured objects and collections

Inheritance and polymorphism in db4o

Ted Neward, Principal, Neward & Associates
Ted Neward photo
Ted Neward is the principal of Neward & Associates, where he consults, mentors, teaches, and presents on Java, .NET, XML services, and other platforms. He resides near Seattle, Washington.

Summary:  Object-oriented applications make considerable use of inheritance, and they frequently want to use that inheritance (or "is-a") relationship to categorize and organize objects within a given system. This can prove difficult in a relational storage scheme, which has no intrinsic concept of inheritance, but in an OODBMS, it's a core feature. In this installment of The busy Java™ developer's guide to db4o, discover the surprising ease (and power) of using inheritance as a core feature when creating queries in db4o.

View more content in this series

Date:  23 Oct 2007
Level:  Introductory
Also available in:   Chinese  Russian

Activity:  1839 views
Comments:  

So far in this series, I've used the Person type to reveal almost all of the fundamentals of working with db4o. You've learned how to create entire graphs of Person objects, retrieve them in some fairly fine-grained manner (using db4o's native query functionality to constrain the actual object graph returned), update and delete entire graphs of objects (subject to some limitations), and so on. In fact, just one aspect of object-orientation has remained entirely outside of this discussion, and that is inheritance.

About this series

Information storage and retrieval has been nearly synonymous with RDBMS for about a decade now, but recently that has begun to change. Java developers in particular are frustrated with the so-called object-relational impedance mismatch and impatient with the solutions that attempt to resolve it. This, along with the emergence of a viable alternative, has led to a renaissance of interest in object persistence and retrieval. The busy Java developer's guide to db4o introduces db4o, an open source database that leverages today's object-oriented languages, systems, and mindset. See the db4o home page to download db4o now; you'll need it to follow the examples.

While the end goal of my evolving example is a data management system for storing employee data, I've really focused on developing my Person type. I may want a system capable of storing information about people who work for a company, as well as their spouses and children, but at this point they're all just Persons to the system. (Or, if you'll pardon the poor English, Employees is-a Person, but Persons isn't-a Employee.) Furthermore, I may want behavior for Employees that shouldn't be a part of the Person API. It's fair to say that from an object modeler's point of view, the ability to model types in is-a terms is the heart of what object-orientation is about.

While I could model the notion of employment using a field in the Person type, that's a relational approach and doesn't fit well within the object-design scenario. Fortunately, the db4o system, like most OODBMS systems, has an integral understanding of inheritance. Having inheritance at the core of a storage system makes it surprisingly easy to "refactor" the existing system into one that takes greater advantage of inheritance as a design concept, without complicating the query facilities. As you'll see, it also makes things much easier when querying for particular kinds of objects.

A highly evolved Person

Just in case you're joining the series now, Listing 1 is a recap of the Person type, which is the long-running example-in-progress for the series:


Listing 1. When we last left our hero ...
                
package com.tedneward.model;

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

public class Person
{
    public Person()
    { }
    public Person(String firstName, String lastName, Gender gender, int age, Mood mood)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.gender = gender;
        this.age = age;
        this.mood = mood;
    }
    
    public String getFirstName() { return firstName; }
    public void setFirstName(String value) { firstName = value; }
    
    public String getLastName() { return lastName; }
    public void setLastName(String value) { lastName = value; }

    public Gender getGender() { return gender; }
    
    public int getAge() { return age; }
    public void setAge(int value) { age = value; }
    
    public Mood getMood() { return mood; }
    public void setMood(Mood value) { mood = value; }

    public Person getSpouse() { return spouse; }
    public void setSpouse(Person value) { 
        // A few business rules
        if (spouse != null)
            throw new IllegalArgumentException("Already married!");
        
        if (value.getSpouse() != null && value.getSpouse() != this)
            throw new IllegalArgumentException("Already married!");
            
        spouse = value; 
        
        // Highly sexist business rule
        if (gender == Gender.FEMALE)
            this.setLastName(value.getLastName());

        // Make marriage reflexive, if it's not already set that way
        if (value.getSpouse() != this)
            value.setSpouse(this);
    }

    public Address getHomeAddress() { return addresses[0]; }
    public void setHomeAddress(Address value) { addresses[0] = value; }

    public Address getWorkAddress() { return addresses[1]; }
    public void setWorkAddress(Address value) { addresses[1] = value; }

    public Address getVacationAddress() { return addresses[2]; }
    public void setVacationAddress(Address value) { addresses[2] = value; }

    public Iterator<Person> getChildren() { return children.iterator(); }
    public Person haveBaby(String name, Gender gender) {
        // Business rule
        if (this.gender.equals(Gender.MALE))
            throw new UnsupportedOperationException("Biological impossibility!");
        
        // Another highly objectionable business rule
        if (getSpouse() == null)
            throw new UnsupportedOperationException("Ethical impossibility!");

        // Welcome to the world, little one!
        Person child = new Person(name, this.lastName, gender, 0, Mood.CRANKY);
            // Well, wouldn't YOU be cranky if you'd just been pushed out of
            // a nice warm place?!?

        // These are your parents...            
        child.father = this.getSpouse();
        child.mother = this;
        
        // ... and you're their new baby.
        // (Everybody say "Awwww....")
        children.add(child);
        this.getSpouse().children.add(child);

        return child;
    }
    
    public Person getFather() { return this.father; }
    public Person getMother() { return this.mother; }
    
    public String toString()
    {
        return 
            "[Person: " +
            "firstName = " + firstName + " " +
            "lastName = " + lastName + " " +
            "gender = " + gender + " " +
            "age = " + age + " " + 
            "mood = " + mood + " " +
            (spouse != null ? "spouse = " + spouse.getFirstName() + " " : "") +
            "]";
    }
    
    public boolean equals(Object rhs)
    {
        if (rhs == this)
            return true;
        
        if (!(rhs instanceof Person))
            return false;
        
        Person other = (Person)rhs;
        return (this.firstName.equals(other.firstName) &&
                this.lastName.equals(other.lastName) &&
                this.gender.equals(other.gender) &&
                this.age == other.age);
    }
    
    private String firstName;
    private String lastName;
    private Gender gender;
    private int age;
    private Mood mood;
    private Person spouse;
    private Address[] addresses = new Address[3];
    private List<Person> children = new ArrayList<Person>();
    private Person mother;
    private Person father;
}

As with the other articles in this series, I'm not going to show the Person class in full for each change I make, but just the incremental changes I make as I go along. In this case, I won't actually be making changes to Person at all because I'm going to extend Person rather than modify it.


Is-you or isn't-you?

The first thing I need to do is make it possible for my employee management system to recognize the difference between normal Persons (such as the spouses and/or children of employees) and Employees. From a pure modeling standpoint, this change is trivial. I just introduce a new derived class to Person, which is of the same package as the other classes thus far. Not surprisingly, I'll call this class Employee, shown in Listing 2:


Listing 2. Employee extends Person
                
package com.tedneward.model;

public class Employee extends Person
{
    public Employee()
    { }
    public Employee(String firstName, String lastName, String title,
                     Gender gender, int age, Mood mood)
    {
        super(firstName, lastName, gender, age, mood);
        
        this.title = title;
    }

    public String getTitle() { return title; }
    public void setTitle(String value) { title = value; }
    
    public String toString()
    {
        return "[Employee: " + getFirstName() + " " + getLastName() + " " +
            "(" + getTitle() + ")]";
    }
    
    private String title;
}
    

The Employee class doesn't actually have much more to it than what you see in Listing 2. From the perspective of the OODBMS, the additional methods on Employee are irrelevant. All you need to keep in mind for the discussion is that Employee is a subclass of Person. (If you are more interested in the modeling aspect of the system, imagine methods on Employee like promote(), demote(), getSalary() and setSalary(), and workLikeADog().)


Testing the new model

Exploration testing the new model is fairly straightforward. I create a JUnit class called InheritanceTest and set up what is, by now, a rather complicated set of objects to serve as the initial working contents of the OODBMS. Just to make the output (which you'll see in Listing 6) more clear, I've shown the details of the @Before-annotated prepareDatabase() call in Listing 3:


Listing 3. Welcome to the company (you belong to me now)
                
    @Before public void prepareDatabase()
    {
        db = Db4o.openFile("persons.data");

        // The Newards
        Employee ted = new Employee("Ted", "Neward", "President and CEO",
            Gender.MALE, 36, Mood.HAPPY);
        Person charlotte = new Person("Charlotte", "Neward",
            Gender.FEMALE, 35, Mood.HAPPY);
        ted.setSpouse(charlotte);
        Person michael = charlotte.haveBaby("Michael", Gender.MALE);
        michael.setAge(14);
        Person matthew = charlotte.haveBaby("Matthew", Gender.MALE);
        matthew.setAge(8);
        Address tedsHomeOffice = 
            new Address("12 Redmond Rd", "Redmond", "WA", "98053");
        ted.setHomeAddress(tedsHomeOffice);
        ted.setWorkAddress(tedsHomeOffice);
        ted.setVacationAddress(
            new Address("10 Wannahokalugi Way", "Oahu", "HA", "11223"));
        db.set(ted);
        
        // The Tates
        Employee bruce = new Employee("Bruce", "Tate", "Chief Technical Officer", 
            Gender.MALE, 29, Mood.HAPPY);
        Person maggie = new Person("Maggie", "Tate", 
            Gender.FEMALE, 29, Mood.HAPPY);
        bruce.setSpouse(maggie);
        Person kayla = maggie.haveBaby("Kayla", Gender.FEMALE);
        Person julia = maggie.haveBaby("Julia", Gender.FEMALE);
        bruce.setHomeAddress(
            new Address("5 Maple Drive", "Austin",
                "TX", "12345"));
        bruce.setWorkAddress(
            new Address("5701 Downtown St", "Austin",
                "TX", "12345"));
        // Ted and Bruce both use the same timeshare, apparently
        bruce.setVacationAddress(
            new Address("10 Wanahokalugi Way", "Oahu",
                "HA", "11223"));
        db.set(bruce);
        
        // The Fords
        Employee neal = new Employee("Neal", "Ford", "Meme Wrangler",
            Gender.MALE, 29, Mood.HAPPY);
        Person candi = new Person("Candi", "Ford",
            Gender.FEMALE, 29, Mood.HAPPY);
        neal.setSpouse(candi);
        neal.setHomeAddress(
            new Address("22 Gritsngravy Way", "Atlanta", "GA", "32145"));
        // Neal is the roving architect
        neal.setWorkAddress(null);
        db.set(neal);
        
        // The Slettens
        Employee brians = new Employee("Brian", "Sletten", "Bosatsu Master",
            Gender.MALE, 29, Mood.HAPPY);
        Person kristen = new Person("Kristen", "Sletten",
            Gender.FEMALE, 29, Mood.HAPPY);
        brians.setSpouse(kristen);
        brians.setHomeAddress(
            new Address("57 Classified Drive", "Fairfax", "VA", "55555"));
        brians.setWorkAddress(
            new Address("1 CIAWasNeverHere Street", "Fairfax", "VA", "55555"));
        db.set(brians);
        
        // The Galbraiths
        Employee ben = new Employee("Ben", "Galbraith", "Chief UI Director",
            Gender.MALE, 29, Mood.HAPPY);
        Person jessica = new Person("Jessica", "Galbraith",
            Gender.FEMALE, 29, Mood.HAPPY);
        ben.setSpouse(jessica);
        ben.setHomeAddress(
            new Address(
                "5500 North 2700 East Rd", "Salt Lake City", 
                "UT", "12121"));
        ben.setWorkAddress(
            new Address(
                "5600 North 2700 East Rd", "Salt Lake City",
                "UT", "12121"));
        ben.setVacationAddress(
            new Address(
                "2700 East 5500 North Rd", "Salt Lake City",
                "UT", "12121"));
            // Ben really needs to get out more
        db.set(ben);
        
        db.commit();
    }
    

As with previous exploration testing examples in this series, I use the @After-annotated deleteDatabase() method to delete the database after each test, just to keep things nicely partitioned.

Let's just run a few queries ...

Before actually running that method, I'll check to see what effect, if any, having Employees in the system will have. It's natural to want to fetch all the Employees from the database — perhaps to fire them all as part of the company going bankrupt. (Yes, I know, it's cruel to think about it, but I'm still a little scarred from the dot-bomb collapse of 2001.) The initial test looks pretty straightforward, as you can see in Listing 4:


Listing 4. The Ted says, 'You're fired!'
                
@Test public void testSimpleInheritanceQueries()
{
    ObjectSet employees = db.get(Employee.class);
    while (employees.hasNext())
        System.out.println("Found " + employees.next());
}
    

When run, the test produces an interesting result: only the Employees in the database (myself, Ben, Neal, Brian, and Bruce) are returned. Implicitly, the OODBMS has recognized that the query is constrained explicitly by the subtype Employee, and has only selected those objects that fit that criteria for return. Because none of the other objects (the spouses or the children) are of type Employee, they aren't eligible and therefore aren't sent back.

Things get more interesting when I run a simple query to return all Persons, like so:


Listing 5. Find all humans!
                
@Test public void testSimpleNonEmployeeQuery()
{
    ObjectSet persons = db.get(Person.class);
    while (persons.hasNext())
        System.out.println("Found " + persons.next());
}
    

When I run this query, every single object — including all the Employees returned earlier — is sent back. At one level, this makes sense. Because Employee is-a Person, by virtue of the implementation-inheritance relationship established in the Java code, the criteria necessary for return from this query is met.

Inheritance (and, by extension, polymorphism) in db4o really is that simple. No complicated IS extensions to a query language, no introduced notion of "type" beyond what already exists in the Java type system. I simply indicate the type desired as part of the query, and those are the types that will form the basis of the query. It's almost like joining tables in an SQL query, by selecting tables whose data should be part of the results of the query. The added bonus, in this case, is that "parent types" are also implicitly "joined" as part of the query. Listing 6 shows the output from my InheritanceTest back in Listing 3:


Listing 6. Polymorphism at work
                
.Found [Employee: Ted Neward (President and CEO)]
Found [Person: firstName = Charlotte lastName = Neward gender = FEMALE age = 35
mood = HAPPY spouse = Ted ]
Found [Person: firstName = Michael lastName = Neward gender = MALE age = 14 mood
 = CRANKY ]
Found [Person: firstName = Matthew lastName = Neward gender = MALE age = 8 mood
= CRANKY ]
Found [Employee: Bruce Tate (Chief Technical Officer)]
Found [Person: firstName = Maggie lastName = Tate gender = FEMALE age = 29 mood
= HAPPY spouse = Bruce ]
Found [Person: firstName = Kayla lastName = Tate gender = FEMALE age = 0 mood =
CRANKY ]
Found [Person: firstName = Julia lastName = Tate gender = FEMALE age = 0 mood =
CRANKY ]
Found [Employee: Neal Ford (Meme Wrangler)]
Found [Person: firstName = Candi lastName = Ford gender = FEMALE age = 29 mood =
 HAPPY spouse = Neal ]
Found [Employee: Brian Sletten (Bosatsu Master)]
Found [Person: firstName = Kristen lastName = Sletten gender = FEMALE age = 29 m
ood = HAPPY spouse = Brian ]
Found [Employee: Ben Galbraith (Chief UI Director)]
Found [Person: firstName = Jessica lastName = Galbraith gender = FEMALE age = 29
 mood = HAPPY spouse = Ben ]

It may surprise you that the objects returned are still objects of the appropriate subtype, despite how they were queried. For example, in the above query, when toString() is called on each of the Person objects returned, Person.toString() is invoked for each Person, as expected. Because Employees have an overridden toString() method, however, the usual rules regarding dynamic binding kick in. The parts of Person that are stored in Employee aren't "sliced off," as can happen in a regular SQL query that fails to join every derived subclass table in a table-per-class model.


Inheriting from the natives

Of course, inheritance criteria extends into native queries just as strongly as it does the simple object queries I've done so far. The query syntax gets just a tad more complicated when the call is made, but fundamentally it follows from the syntax I've already used before, shown in Listing 7:


Listing 7. Are you a company widow?
                
@Test public void testNativeQuery()
{
    List<Person> spouses =
        db.query(new Predicate<Person>() {
            public boolean match(Person candidate) {
                return (candidate.getSpouse() instanceof Employee);
            }
        });
    for (Person spouse : spouses)
        System.out.println(spouse);
}
    

I begin with a query similar, in spirit, to the one I did earlier, looking through all the Persons in the system, but I constrain it further by looking for only those Persons who have a spouse that is an Employee — a call to getSpouse(), with the return value passed to the Java instanceof operator, and it's done. (Remember that the match() call only returns true or false, indicating whether the candidate object should be returned.)

Notice how changing the type of the Predicate passed in the query() call can change the type criteria implicitly selected, shown in Listing 8:


Listing 8. Scandalous! Office romance!
                
@Test public void testEmployeeNativeQuery()
{
    List<Employee> spouses =
        db.query(new Predicate<Person>() {
            public boolean match(Person candidate) {
                return (candidate.getSpouse() instanceof Employee);
            }
        });
    for (Person spouse : spouses)
        System.out.println(spouse);
}
    

When executed, this query produces no results because now the query is looking only for Employees with a spouse also employed by the company. Currently, no employee in the database satisfies that criteria. Should the company decide to hire Charlotte, then two Employees would be returned, however: Ted and Charlotte. (And they say office romance never works.)

For the most part, again, that's it. Inheritance doesn't have any effect on updates, deletes, or activation depth, just the query aspect of objects. But recall that the Java platform offers two forms of inheritance: implementation inheritance, via the ubiquitous extends clause, and interface inheritance, via implements. If db4o supports extends, then it also must support implements, which makes for a pretty powerful query capability, as you'll see.


It's all about the interface

As just about any Java (or C#) programmer learns after about six weeks of using the language, interfaces are highly useful for modeling. Although not often seen as such, interfaces present a powerful ability to "segregate" types across traditional implementation inheritance lines; through the use of an interface, I can declare that certain types are Comparable, or Serializable, or in this case, Employable. (Yes, this is overkill from a design perspective, but it serves the pedagogical purpose nicely.)


Listing 9. Hey, it's not 2001 anymore! Come work for me!
                
package com.tedneward.model;

public interface Employable
{
    public boolean willYouWorkForUs();
}

To see the interface at work, I need a concrete class inheriting from the Employable interface, and — you probably guessed it already — that means creating an EmployablePerson subtype that extends Person and implements Employable. I won't show that code here (there's not much worth showing, aside from adding ** EMPLOYABLE ** to the end of Person's toString() in the EmployablePerson.toString() method). I will also modify the prepareDatabase() call to return the fact that Charlotte is an EmployablePerson, instead of just a Person.

Now, I can write a query that iterates through the database, looking for spouses or relatives of our current employees that might be willing to work for the company, shown in Listing 10


Listing 10. There's this job, see ...
                
@Test public void testEmployableQuery()
{
    List<Employable> potentialEmployees =
        db.query(new Predicate<Employable>() {
            public boolean match(Employable candidate) {
                return (candidate.willYouWorkForUs());
            }
        });
    for (Employable e : potentialEmployees)
        System.out.println("Eureka! " + e + " has said they'll work for us!");
}    
    

Sure enough, Charlotte is returned as being employable. What's better, this means that any interface I introduce becomes a new way to constrain queries, without artificially introducing fields to carry the information; Charlotte fits the query criteria solely because she implements that interface, whereas none of the other spouses do (at least, for now).

In conclusion

Roles and objects

Some object models will, if you'll pardon the pun, object to my using interfaces and inheritance to model what is, essentially, a role played by Persons. For example, assume the spouse of an Employee decides to come work for the company -- does it make sense to remove them as a Person from the system and re-insert them as an Employee? Roles can, and frequently do, change over time. Objects aren't supposed to change their base and interface types to go along with every shift in role.

This is a fair argument, and one that can't be solved in this particular article, if it can be solved at all. For now, let's just say that my use of inheritance and interfaces is purely demonstrative and pedagogical. For further study, see "Role object" in the Resources section.

If objects and inheritance go together like chocolate and peanut butter, objects and polymorphism are like hand and glove. The two elements fit just as snugly as a manager and his or her high salary. Any system that stores objects has to be able to understand and project this notion of inheritance into the storage media and filter along it when retrieving data. Fortunately, object-oriented DBMSs render this a trivial undertaking, and they let you do it without having to introduce new query-predicate terms. In the long run, that makes the OODBMS much easier to use when inheritance is part of the picture.


Resources

Learn

Get products and technologies

  • Download db4o: An open source native Java programming and .NET database.

Discuss

About the author

Ted Neward photo

Ted Neward is the principal of Neward & Associates, where he consults, mentors, teaches, and presents on Java, .NET, XML services, and other platforms. He resides near Seattle, Washington.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=263800
ArticleTitle=The busy Java developer's guide to db4o: Structured objects and collections
publish-date=10232007
author1-email=ted@tedneward.com
author1-email-cc=jaloi@us.ibm.com

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers