Object Slicing and Component Design with Java
Object-oriented systems are usually partitioned into layers of related responsibilities and only dependencies in one direction are allowed, from higher layers (more specific, less reusable) to lower ones (more general, more reusable). Classes in higher layers can extend or wrap classes in lower ones, but not the other way around. Learn more about object slicing.
Motivation
Object-oriented systems are usually partitioned into layers of related responsibilities and only dependencies in one direction are allowed, from higher layers (more specific, less reusable) to lower ones (more general, more reusable). Classes in higher layers can extend or wrap classes in lower ones, but not the other way around. However, they can register themselves as event listeners with classes in lower levels through a generic interface.
A component resides in a single layer, which can be itself divided in multiple levels of separate concerns. For example, a business component residing in the domain layer consists not only of business logic, but also of other types of functionalities, like database persistence, lifecycle callback methods or upper layer event notification. A level is created for each such type of functionality, and the component is divided into level specific classes, with those in higher levels enhancing some in the lower ones. If components live in a container of some sort, some levels will probably have to deal with container related services, and the classes in these levels may be generated. Ideally, the lower levels have to be oblivious of any enhancements in levels above them.
One of the following three approaches is usually chosen to handle the layer/level dependencies:
- Each layer/level of the system has its own types, and a conversion is needed when crossing boundaries. A lot of data copying is going on, and logic is duplicated.
- A class in a higher layer/level enhances one in a layer/level below by using composition. If relationship maintenance is needed, it may have to be duplicated at the wrapper level.
- A class in a higher layer/level enhances one in a layer/level below through extension. But upcasting does not prevent a user of objects of the base type to downcast them to the derived type and use the enhancements, so additional work is needed if true obliviousness of the enhancements is desired. Also, if the derived class overrides methods from the base class, the lower layer will get access to functionality or data representations that were designed for the above layer.
While each approach has its advantages and disadvantages, the third one seems to be the choice in a lot of JDO and EJB implementations. In another TSS article, Chris Richardson also advocates using inheritance for enhancing business objects with persistence logic by using a two level domain design. Bob Lee points out in a blog entry that when using this approach the this reference poses a problem because it always points to the derived instance (it is polymorphic), and he suggests interposing a wrapper between the subclass and base classes, which only exposes the methods inherited from the base class:
However, this approach does not address the overridden methods issue (which in many cases could be acceptable). We are going to address it in this article, by presenting a simple technique that achieves obliviousness to both kinds of enhancements allowed by inheritance: new and overridden methods.
Introduction
Object slicing is defined as the conversion of an object into something with less information (typically a superclass). In C++ it occurs when an object is passed by value and copying the parameter value results in an upcast and it is considered a bad thing, as it may result in very subtle bugs. Object slicing throws away information. But in some situations this may be exactly what we want.
In Java, objects are passed by reference, and all methods are virtual, so object slicing will not occur inadvertently. Even if you upcast an object, its real type is not forgotten, that's the definition of polymorphism.
Let’s consider the simplest version of object slicing. Suppose class Derived extends class Base, and we have an object of type Derived. We want to "extract" the base part from it.
This does not work, because the compiler will make sure you put a dot after super, followed by a valid identifier.
public class Derived extends Base { public Base getBase() { return super; // compilation error } }
Also, it does not help to return a reference to this from the Base class (which is equivalent to an upcast), because this is polymorphic. Overridden methods will be invoked instead of those defined in the Base class, and any additional methods would also be available for invocation. That is exactly what we want to avoid. We want a proxy to the base type part of an object, not just to restrict the interface, and not just a copy of the data and behavior.
We could create a proxy to a base type of an object this way: for a subclass of that base type where a method is overridden, we also add a (final) method invoking the overridden method using super. Then we create a wrapper to forward the calls to the right methods. But this is not elegant, and it requires keeping track of too much information. The object oriented way of doing it is using inner classes. Also, we cannot use dynamic proxies to avoid implementing each method from the base class, because we cannot invoke a method with reflection on super.
How to do Object Slicing in Java
Let's recall that a (non-static) inner class needs an instance of the enclosing class in order to be instantiated, and all members of the enclosing object (including those with private access) are accessible from the inner class.
Our idea is to exploit this feature and the fact that unlike this, super is not polymorphic. Let’s take a look at the diagram below.
In the Derived class, extending Base, we define an inner class Slice, which also extends Base and overrides all the methods in Base by forwarding them to the base part of the enclosing object. The getBase() method in the Derived class returns an instance of Slice, which in a way is a proxy for the base part of the object.
Here is a test class that shows that getBase() works the way we want, i.e. it slices the object:
public class Test { public static void main(String[] args) { Base base = null; Derived derived = new Derived(); System.out.println(derived.sayHello()); // HELLO. //upcasting does not do slicing base = (Base) derived; System.out.println(base.sayHello()); // HELLO. //the slice has only the base part base = derived.getBase(); System.out.println(base.sayHello()); // Hello. //the whole and the slice are in sync derived.setGreeting("Hi."); System.out.println(base.sayHello()); // Hi. } }
When thinking of object handles in Java, I find the analogy with a remote control (handle) for a TV set (object) quite helpful. With this terminology, we want to produce a second remote control that would work only with a part of the TV set, let’s say the sound.
You may wonder why doing so would be useful. After all, we can use composition instead of inheritance by making the Derived class hold a reference of type Base, which would be initialized in a constructor or assigned by using a setter method. This is not always a good idea. Imagine that we have a class Human that needs to be enhanced by the classes Male and Female. It does not make sense to require that Male and Female instances are built out of preexisting Human instances. We have a genuine IS-A relationship here, and the Male and Female classes should extend the Human class. And if we want to disallow discrimination based on type in one of our layers (i.e. we want to work only with Human types), we need to excise the extra information or behavior modifications introduced by the subclasses. What is usually done in order to accommodate this need is to copy the relevant data from a Male or Female object into a new object of type Human. A proliferation of layer specific types occurs when this is done, and a conversion is needed when crossing a layer boundary.
Persistence Logic and Domain Design
I first considered the possible need for object slicing when working with Domain and Data Access Objects. These contain the business and persistence logic, respectively. Although one could combine them in the same class as it is described in the Active Record pattern, or in many EJB books, in most cases it is not acceptable to do so.
These are the most common ways of dealing with the relationship between the business logic and the persistence logic:
- One class approach: put them in the same class (Active Record) (classes involved: Person)
- Service oriented or façade approach: put them in 2 different classes and pass one as an argument to a method of the other (Data Mapper , DAO) (classes involved: Person, PersonMapper)
- Component oriented approach: put them in 3 classes: Person, PersonRepository, PersistentPerson and copy data between Person and PersistentPerson; we can further distinguish the following cases:
- PersistentPerson and Person are unrelated, PersonRepository does the copying
- PersistentPerson holds reference to Person (composition)
- PersistentPerson extends Person (2-level domain)
Each choice has its advantages and disadvantages. I like having a persistent instance for each domain instance (options listed under category 3, because it offers more options (for example we can use the persistent instance directly or drive it from a BMP EJB).
Robert Martin uses the approach described in 3b) to “decouple persistence from policy” in the example presented in his Agile Software Development book, on pages 374 to 378. The description of the idea that he calls “proxyfying” is also described in this online sample chapter.
The 3c) approach is described in Chris Richardson’s article about a two level domain. But as we mentioned, this choice is not without problems.
Object slicing can be used to create a modified version of 3c) where an intermediate level is inserted, to prevent the business logic from accessing the persistence logic, and also addresses the other drawbacks we mentioned.
Example of Using Object Slicing with Persistence
We will describe how object slicing can be used in a somewhat realistic development scenario, but at the same time we want to keep the example simple and easy to understand. Our example is quite similar to Robert Martin’s in terms of class structure, except for simplicity we are not using interfaces.
Suppose our domain needs to work with a Person type. We introduce the SliceablePerson class, which extends Person, contains a set of inner classes (anonymous or not) and returns their instances as slices or views of the object. For example, the asPerson() method returns an instance of the PersonSlice inner class which invokes the methods defined in the Person class. This instance can be cached.
Any class extending SliceablePerson can be sliced to a Person type, leaving out additional functionality contained in new or overridden methods or constructors, if so desired.
The PersistentPerson is an example of a class with enhanced functionality, whose instances will be sliced. The getName() method is overriden in PersistentPerson to return the name capitalized. The setName() method is overriden in PersistentPerson to use new (persistence related) methods introduced in this class. We are assuming here that we want to store the object when its name field is changed (if we had more setters we would probably want to explicitly call a commit method after the setters).
We can choose which functionality we want from the base class, and which from the this object, as shown in the anonymous class whose instance is returned in the method asStoredPerson(), where the overriden setter is exposed. We are also showing an example when the slice is read only (the setter is reimplemented in the anonymous class whose instance is returned in the method asROPerson() to throw an exception when called).
Suppose we choose to use a repository for domain objects, and that it makes sense to work with objects that have enhanced functionality behind it, but expose only the limited functionality needed by its users. Again, we are using persistence as the enhancing functionality, and for presentation purposes only minimal functionality is implemented (e.g. we are not dealing with concurrency and transaction aspects).
Here is a test class that shows the functionality of a sliceable object:
public class Test { public static void main(String[] args) throws Exception { PersistentPerson pp = new PersistentPerson("George"); System.out.println(pp.getName()); // GEORGE // get the slice Person p = pp.asPerson(); System.out.println(p.getName()); // George // changes in the whole are reflected in the slice pp.setName("Lisa"); System.out.println(p.getName()); // Lisa // changes in the slice are reflected in the whole p.setName("Michael"); System.out.println(pp.getName()); // MICHAEL // for read only version override setter to throw Person rop = pp.asROPerson(); System.out.println(rop.getName()); // Michael try { rop.setName("Paul"); } catch (RuntimeException e) { System.out.println(e.getMessage()); // You are not allowed to change the name. } // using the repository p = PersonRepository.createPerson("Claudia"); // Creating Person: CLAUDIA, 1148943844 System.out.println(p.getName() + ", " + p.getId()); // Claudia, 1148943844 //changing the name triggers the storage of the object p.setName("Claudia Maria"); // Storing Person: CLAUDIA MARIA, 1148943844 p = PersonRepository.getPerson(p.getId()); // Loading Person: CLAUDIA MARIA, 1148943844 System.out.println(p.getName()); // Claudia Maria p.setName(null); // Update failed: Cannot store a person without a name System.out.println(p.getName()); // Claudia Maria } }
Final Remarks
- In a way we are also using composition, as an inner class has a reference to its outer object. But the use of the inner class allows making a call to the super of the outer object in a very clean way. If you want to do this with a wrapper you would have to add methods to the wrapped class in which you would call the overridden methods (this is polymorphic, super is not).
- The SliceablePerson class is similar in functionality to the wrapper Bob Lee is talking about, which plays the role of a converter between the corresponding types in the other two levels. This class can be extended by different implementations (for example in-memory DAO, or EJB).
- We could move all the code from the SliceablePerson class into PersistentPerson if we wanted to minimize the number of java source files and/or give the appearance of having a two level domain.
- The SliceablePerson class can be easily generated.
- The Person class can be concrete. Also, it is easy to work with interfaces, if we want.
- We can use a variation of this idiom, where the proxy slice is initialized with the outer object's data (using a constructor, or an instance initializer, perhaps with reflection). This data can be used as temporary in-memory storage of changes, before committing them to the outer object. Or we can implement undo operations this way.
- We can make PersonSlice extend SliceablePerson, if we want the ability of chaining slices.
Acknowledgements
I would like to thank my brother Nick Gonciulea and my friend Joel Peach for their valuable remarks about the content of this article.
About the Author
Constantin Gonciulea is a software architect who specializes in J2EE, Data Mining and Knowledge Management consulting. He can be reached at [email protected]
References
https://www.theserverside.com/news/1364578/The-Keel-Meta-framework-A-Hands-On-Tutorial
http://crazybob.org/roller/page/crazybob/20030814
http://c2.com/cgi/wiki?ObjectSlicing
http://martinfowler.com/eaaCatalog/activeRecord.html
http://martinfowler.com/eaaCatalog/dataMapper.html
http://martinfowler.com/eaaCatalog/repository.html
http://www.objectmentor.com/resources/bookstore/books/AgileSoftwareDevelopmentPPP