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
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.)
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 usingcopland.BuilderFactory
as your service factory, you can omit thefactory
element. It is retained explicitly for this tutorial to demonstrate the use of thefactory
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).
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.