Вы находитесь на странице: 1из 4

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. 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:

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.

2. 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.

3. 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. 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.

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.

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 advantage of doing so is that, 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 :

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:

1. One class approach: put them in the same class

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

3. Component oriented approach: put them in 3 classes


Each choice has its advantages and disadvantages. Of all there is an advantage for
a persistent instance for each domain instance because it offers more options (for
example we can use the persistent instance directly or drive it from a BMP EJB).

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.

Вам также может понравиться