Implementing multiple inheritance in Groovy

Java classes are only allowed to inherit from a single parent class. Multiple inheritance is only available for interfaces that do not carry any state or implementation details. This is not a drawback, but rather a design choice that allows you to avoid several problems. An example of this is the diamond problem that arises in languages which do employ multiple inheritance. Basically, there is an ambiguity (which method implementation to call) if classes B and C inherit from class A and class D inherits from both B and C. The diamond name comes from the shape of the class diagram formed by the A, B, C, and D.

Groovy still does not allow multiple class inheritance, but offers another approaches for injecting logic that is spread over multiple classes into a single entity.

This recipe will demonstrate a simple use case for the @Mixin and @Delegate annotations that can help you simulate multiple inheritance.

Getting ready

We'll start with a simple project that models a publishing house. We are going to build the code with the Gradle build tool that we touched in the Integrating Groovy into the build process using Gradle recipe in Chapter 2, Using Groovy Ecosystem. The following directory structure is what we are aiming for:

src/main/groovy/org/groovy/cookbook
    Publication.groovy
    OnlinePublication.groovy
    PeriodicPublication.groovy
    PrintedPublication.groovy
src/test/groovy/org/groovy/cookbook
    PublicationTest.groovy
build.gradle

The Publication class is the base class for describing the properties and behavior of all the published items our publishing agency is dealing with:

package org.groovy.cookbook

abstract class Publication {
  String id
  String title
  Date issueDate
}

Then we have to define the specializations of our publications. For example, the PrintedPublication class exists to denote items printed on paper:

package org.groovy.cookbook

class PrintedPublication {
  int pageCount
}

On the other hand, the OnlinePublication class may represent items published electronically:

package org.groovy.cookbook

class OnlinePublication {
  URL url
}

The PeriodicPublication class refers to items issued daily, weekly, monthly, or repeatedly after any other period:

package org.groovy.cookbook

class PeriodicPublication {
  Duration issuePeriod
}

As you can guess, these three classes can be combined in a number of ways to properly satisfy the publishing business needs. For instance, our publishing house may have an electronic, monthly magazine, or a printed and online publication. Implementing all of these combinations with a single inheritance hierarchy is clearly impossible. In the course of the next section of this recipe, we'll show how Groovy deals with this situation.

We also have to define an empty unit test, which we are going to fill in soon:

package org.groovy.cookbook

import org.junit.Test
import groovy.time.DatumDependentDuration

class PublicationTest {

  @Test
  def testPublications() { ... }

}

Before we can start, build.gradle also goes here, which is the simplest possible in this situation:

apply plugin: 'groovy'

repositories {
  mavenCentral()
}

dependencies {
  compile 'org.codehaus.groovy:groovy-all:2.1.6'
  testCompile 'junit:junit:4.11'
}

How to do it...

At this stage, we are ready to introduce multiple inheritance in our code base:

  1. Let's create the Newspaper class (in the src/main/groovy/org/groovy/cookbook/Newspaper.groovy file), which obviously represents the printed periodic publication:
    package org.groovy.cookbook
    
    @Mixin([PeriodicPublication,
            PrintedPublication])
    class Newspaper extends Publication {
    
    }
  2. The next step is to create a class representing the online magazine, OnlineMagazine.groovy:
    package org.groovy.cookbook
    
    @Mixin([OnlinePublication,
            PeriodicPublication])
    class OnlineMagazine extends Publication {
    
    }
  3. And finally, we create a class that shares all the publication features we have: MultimediaMagazine.groovy:
    package org.groovy.cookbook
    
    @Mixin([PeriodicPublication,
            PrintedPublication,
            OnlinePublication])
    class MultimediaMagazine extends Publication {
    
    }
  4. Let's extend the unit test to verify that our hierarchy actually works:
    @Test
    def testPublications() {
    
        def monthly = new DatumDependentDuration(0, 1, 0, 0, 0, 0, 0)
        def daily = new DatumDependentDuration(0, 0, 1, 0, 0, 0, 0)
    
        def groovyMag = new OnlineMagazine(
                          id: 'GRMAG',
                          title: 'GroovyMag'
                        ).with {
                          url = new URL('http://grailsmag.com/')
                          issuePeriod = monthly
                          it
                        }
    
        def time = new MultimediaMagazine(
                     id: 'TIME',
                     title: 'Time'
                   ).with {
                     pageCount = 60
                     url = new URL('http://www.time.com')
                     issuePeriod = monthly
                     it
                   }
    
        def pravda = new Newspaper(
                       id: 'PRVD',
                       title: 'Pravda'
                     ).with {
                       pageCount = 8
                       issuePeriod = daily
                       it
                     }
    
    }
  5. To verify that our code compiles and our test works, we just ran the standard Gradle command:
    gradle clean build
  6. The result should be a normal successful build output:
    ...
    BUILD SUCCESSFUL
    ...
    

How it works...

The @Mixin annotation that we placed in our source code informs Groovy to perform a transformation of the target class and add behavior (methods) of mixed-in classes into that. The annotation accepts a single class name or an array of class names. Mixing in behavior is done exclusively at runtime, since (as we already noted) under the hood, JVM does not allow multiple parent classes. This is also the reason that the instanceof operator will not work for mixed-in classes. For example, you can't check that groovyMag is an instance of the PeriodicPulication class. But you can still use the groovyMag and other variables to call the features of the mixed-in classes at runtime.

In the test class, we used the Groovy Bean constructor syntax (we discussed Groovy Beans in more details in the Writing less verbose Java Beans with Groovy Beans recipe) to refer to the fields that are available in the base class, but the fields we mixed-in from different sources can only be accessed in a dynamic way. In fact, they are not just fields but also a pair of getter and setter automatically defined by Groovy for all the bean classes. So, we really mix the behavior (that is, methods) in, not the state of the class. That's also the reason we used another Groovy feature here—the with method. This method is defined for all objects in Groovy; it takes a closure (see the Defining code as data in Groovy recipe) as a parameter, and the object itself is passed to the closure's context. That's why you can easily refer to an object's methods and properties in a short form. In fact, you could have used the with method to define all the properties of our object:

def groovyMag = new OnlineMagazine().with {
                  id = 'GRMAG'
                  title = 'GroovyMag'
                  url = new URL('http://grailsmag.com/')
                  issuePeriod = monthly
                  it
                }

But we used different syntax to show the distinction between base class properties and mixed-in properties.

There's more...

Another way to add behavior from other classes is to use Groovy's @Delegate annotation. To give an example, we can define a new Book class:

package org.groovy.cookbook

class Book extends Publication {

  @Delegate
  PrintedPublication printedFeatures = new PrintedPublication()

}

The @Delegate transformation makes all the methods available on the PrintedPublication class also accessible from instances of the Book class. It delegates all those method calls to the specific PrintedPublication's instance created upon book object construction.

def groovy2cookbook = new Book(
                        id: 'GR2CBOOK',
                        title: 'Groovy 2 Cookbook',
                        pageCount: 384
                      )

This is similar in the result it achieves compared to the @Mixin annotation, but the implementation is quite different. The @Delegate annotation is a compile-time transformation and influences the way Groovy constructs the bytecode for this class. This is also the reason that the map-based constructor syntax worked for the pageCount property. If we'd used the @Mixin annotation for the Book class, it would not have worked due to the runtime nature of the @Mixin transformation.

It's hard to vote for any of these approaches and decide which one would be better to handle your multiple inheritance needs. The @Mixin definition is less verbose and automatically creates instances of all mixed-in classes, but on the other hand, managing the state of those instances may not be so obvious. The @Delegate transformation has a more verbose definition due to an additional variable declaration, but it also gives your target class direct access and control over the way your mixed-in state is created.

In general, mixins and delegates can also be handy for splitting long classes into several code units.

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

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