6.3. Delegation and Callback

Component-based design is “divide and conquer” with clear-cut compartmentalization: Sharing among bundles and services is strictly controlled. This poses a challenge to designing services that work together. For multiple callers to use a service, they must go through the same interface provided by the callee; at the same time, they may all want the callee in slightly different ways.

Suppose we are to design a news service, NewsService, whose HTML contents are needed by services for a pager, a touch panel, and a PC. The data are from the same source, but how they are displayed varies: text only on the pager, no dynamic contents on the touch panel, and full media on the PC.

The news service has a few options to deal with the variation of demands from its clients. First, it can expose a carefully designed service interface to accommodate the use scenarios of the callers. For example, the news service may be defined as

public interface NewsService {
   static final int TEXT_ONLY = 0;
   static final int STATIC_CONTENT = 1;
   static final int UNABRIDGED = 3;
   /**
    * Gets news from the source, requesting appropriate
    * content transformation.
    */
   public InputStream getNews(int contentTransformType);
}

Then the service for the pager would call

InputStream src = newsService.getNews(NewsService.TEXT_ONLY);

to obtain the plain text contents from the news service.

Second, register multiple instances of the service, each providing a different implementation, to satisfy callers with different needs. For example, we could define the NewsService interface simply as

public interface NewsService {
   public InputStream getNews();
}

and supply a number of implementations based on target devices:

/** The first news service impl: text only */
class TextOnlyNewsImpl implements NewsService {
   public InputStream getNews() {
      // strip anything but text from the source and
      // return the stream.
   }
}
/** The second news service impl: no dynamic content */
class StaticContentNewsImpl implements NewsService { ... }

/** The third news service impl: unabridged content */
class UnabridgedNewsImpl implements NewsService { ... }

If all three forms of the news services are registered along with the property contentTransformType, used to distinguish the three, the service for the pager would perform the following sequence of calls to get news:

ServiceReference[] ref = bundleContext.getServiceReferences(
   "NewsService", "(contentTransformType=text-only)");
NewsService newsService =
   (NewsService) bundleContext.getService(ref);
InputStream src = newsService.getNews();

But no matter how hard the callee tries, we can always sense some degree of tension between the callers, who clamor for customization, and the callee that offers a unified interface. For instance, the first alternative requires the definition of a predetermined set of content-transforming type constants; the second alternative requires the implementation of multiple news services.

Rather than put the burden entirely on the callee, we can delegate whatever customization is needed to its callers. In other words, services on the pager, the touch panel, and the desktop now have to assume the responsibility of implementing their own particular application logic, and register with the news service. At the time the news service gets news on behalf of its callers, it calls back to them to have specialized decisions made. Here is one possible design:

public interface NewsService {
   InputStream getNews(ContentTransformer ct);
}

The ContentTransformer parameter may be defined as

public interface ContentTransformer {
   /** Returns a set of HTML tags to be stripped from
    * the source content.
    */
   public String[] getTagsForRemoval();
}

Now the service for the pager would use the news service like this:

ContentTransformer ct = new ContentTransformer() {
   public String[] getTagsForRemoval() {
      // remove image, applet, and script tags
      String[] tags = new String[] {
         "img", "applet", "script" };
      return tags;
   }
};
InputStream src = newsService.getNews(ct);

Similarly, the service for the touch panel would do

ContentTransformer ct = new ContentTransformer() {
   public String[] getTagsForRemoval() {
      // remove applet and script tags; image tags okay
      String[] tags = new String[] { "applet", "script" };
      return tags;
   }
};
InputStream src = newsService.getNews(ct);

And, finally, the service for the PC would do

ContentTransformer ct = new ContentTransformer() {
   public String[] getTagsForRemoval() {
      // nothing to remove
      return null;
   }
};
InputStream src = newsService.getNews(ct);

The news service, before returning any content to the caller, determines the unwanted tags from the ContentTransformer object supplied by the caller and removes them as specified.

This design strategy puts customization close to where it is needed: in the calling services. The callers pass an implementation of a well-known interface to the callee, informing the latter how they want a particular behavior carried out. The callee duly calls back to the callers to learn their wishes before completing the requests.

In the next chapter, we see that the standard OSGi HTTP service interface is a design typical of this kind.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.235.62