Tutorial #2. Service Factories

The sources for this tutorial may be found in the tutorial/02 directory of the Copland distribution.

Introduction

As you saw in the last tutorial, you can create services by mapping them directly to Ruby classes. The last tutorial used the simple implementor to do this, which has its limitations. For one, the class must have a no-argument constructor, which restricts what classes you can use with it.

This tutorial will demonstrate the use of service factories to perform more complex styles of instantiation and initialization.

A service factory is just a special kind of service that is used to create other services. There are several different service factories that Copland provides by default; this tutorial will only use the BuilderFactory.

This tutorial will continue the application started in the first tutorial. We’ll extend the “adder” example into a simple calculator object that delegates its operations to different services.

Steps

  1. Implement the Services

    Most calculators support at least four operations: addition, multiplication, division, and subtraction. We’ve already got a service that does addition: let’s write three more that do the multiplication, division, and subtraction:

      class Subtractor
        def subtract( a, b )
          a.to_f - b.to_f
        end
      end
    
      class Multiplier
        def multiply( a, b )
          a.to_f * b.to_f
        end
      end
    
      class Divider
        def divide( a, b )
          a.to_f / b.to_f
        end
      end
    

    Lastly, we’ll create our calculator class:

      class Calculator
        attr_writer :adder
        attr_writer :subtractor
        attr_writer :multiplier
        attr_writer :divider
    
        def add( a, b )
          @adder.add( a, b )
        end
    
        def subtract( a, b )
          @subtractor.subtract( a, b )
        end
    
        def multiply( a, b )
          @multiplier.multiply( a, b )
        end
    
        def divide( a, b )
          @divider.divide( a, b )
        end
      end
    

    Notice that we never instantiate any of the delegate classes: we’re going to leave that up to Copland. Instead, we simply provide some setters, which Copland will use to set those dependencies. (Note that these setters can also aid in unit testing, since you can easily set each of those properties to some mock object. We won’t demonstrate that, though, since unit testing is beyond the scope of this tutorial.)

  2. Package Descriptor

    We now add to our package descriptor. Just as we defined a service point for the Adder class, we now have to add service points for each of the other classes we created.

    The Subtractor, Multiplier, and Divider service points will look very much like the Adder service point. Each uses the simple implementor.

      Subtractor:
        implementor: tutorial/Subtractor
    
      Multiplier:
        implementor: tutorial/Multiplier
    
      Divider:
        implementor: tutorial/Divider
    

    The Calculator service point, however, is a bit more complicated. Not only do we need to say what class implements the service, but we also need to say what services get wired into it for each of its properties. To do this, we’ll use the copland.BuilderFactory service.

      Calculator:
        implementor:
          factory: copland.BuilderFactory
          class: tutorial/Calculator
          properties:
            adder: !!service tutorial.Adder
            subtractor: !!service tutorial.Subtractor
            divider: !!service tutorial.Divider
            multiplier: !!service tutorial.Multiplier
    

    The implementor section, in this case, is a map, instead of a string. The map specifies the service factory to use (copland.BuilderFactory, in this case), the class that will implement the service, and the properties that will be set. (Note that if you are using copland.BuilderFactory as your service factory, you can omit the factory element. It is retained explicitly for this tutorial to demonstrate the use of the factory element.)

    Note the special syntax of the properties: that !!service bit says that the following name is the name of a service point that should be instantiated and passed as the value of the corresponding property. In other words, when the BuilderFactory instantiates the Calculator class, it will also obtain references to the Adder, Subtractor, Divider, and Multiplier services, and wire them into the appropriate properties of the new Calculator object.

    Lastly, note that we used the fully-qualified service names for the dependencies (i.e., “tutorial.Adder”). Because the Calculator service point is in the same package as the dependencies, we could have simply given the names of the service points, unqualified (without the package names).

  3. Putting it all Together

    We’ll modify the “main.rb” driver file slightly, to test our new Calculator service:

      require 'copland'
    
      registry = Copland::Registry.build
    
      calc = registry.service( "tutorial.Calculator" )
    
      puts calc.add( "5", 7 )
      puts calc.subtract( "5", 7 )
      puts calc.multiply( "5", 7 )
      puts calc.divide( "5", 7 )
    

    Once run, you should see the program spit out the answers, as you would expect.

Summary

This tutorial showed you how to use more complicated implementors. In particular, it showed you how to have Copland automatically wire dependencies together, instantiating services as needed. However, you only saw how to use properties to wire services together; you can also specify constructor parameters that will be passed to the new service when it is instantiated.

You also saw how to use the !!service directive, to instruct Copland to interpret a value as a service point name, instead of simply as a string. There are several such directives; see the Copland documentation for a comprehensive list.