Академический Документы
Профессиональный Документы
Культура Документы
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:
1. 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.
While each approach has its advantages and disadvantages, the third one seems to
be the choice in a lot of JDO and EJB implementations. Most of the experts advocate
using inheritance for enhancing business objects with persistence logic by using a
two level domain design. However, this approach does not address the overridden
methods issue (which in many cases could be acceptable). This is going to be
addressed by presenting a simple technique that achieves obliviousness to both
kinds of enhancements allowed by inheritance: new and overridden methods.
Object Slicing :
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.
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.
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.
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.
When thinking of object handles in Java, 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.
The possible need for object slicing is very much 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:
2. Service oriented or façade approach: put them in 2 different classes and pass
one as an argument to a method of the other
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).
* 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.