Chapter 36. Object Scoping

Place the DSL script so that bare references will resolve to a single object.

image

Nested Function and (to an extent) Function Sequence may provide a nice DSL syntax, but in their basic forms they come with a serious cost: global functions and (worse) global state.

Object Scoping alleviates these problems by resolving all bare calls to a single object and this avoids cluttering the global namespace with global functions, allowing you to store any parsing data within this host object. The most common way to do this is to write the DSL script inside a subclass of a builder that defines the functions—this allows the parsing data to be captured in that one object.

36.1 How It Works

One of the many useful properties of objects is that each object provides a contained scope for functions and data. Inheritance allows you to use this scope separately from where it’s defined. A DSL can use this facility by defining DSL functions on a base class, and then allowing developers to write DSL programs in subclasses. The base class can also define fields to hold any parsing data that’s required.

Using a base class like this is an obvious place for an Expression Builder. Clients then write DSL programs in a subclass of the Expression Builder. Using inheritance allows them to add other DSL functions in the subclass, or even override base functions in the DSL object if they need to.

Although inheritance is the most common mechanism to use for this kind of work, some languages provide other ways to use Object Scoping. An example of this is Ruby’s instance evaluation which provides the facility to take any program code and execute it within the context of a particular object (using the instance_eval method). This allows a DSL writer to write the DSL text without declaring any links to the base class that defines the language.

Another technique that’s available in Java is instance initializers. These are not well known nor often used, but can work well for this case.

36.2 When to Use It

Object Scoping solves the niggly problems of globalness within Nested Function and Function Sequence and as such is always worth considering. Using Object Scoping allows you to have bare function calls in your DSL and have them resolve to instance methods on an object. Not only does this avoid messing with a global namespace, it also allows you to store parsing data in an Expression Builder. I find these advantages quite compelling, and thus would always suggest using Object Scoping if you can.

Sometimes, however, you can’t. For a start, you need to be using an object-oriented language to do this. I, of course, don’t see that as a problem since I prefer using OO languages anyway.

The more common issue is that Object Scoping puts constraints on where your DSL script can go. With the most common inheritance case, it means you must put the DSL script within a method in a subclass of an Expression Builder. This isn’t too much of problem for self-contained DSL scripts. Such scripts often sit in their own file and are well-separated from other code. In this case, there is a little syntactic noise to set up the inheritance structure but it’s not too obtrusive. (You can avoid even that syntactic noise with techniques like Ruby’s instance_eval.)

The real problem is with fragmentary DSLs, where using Object Scoping forces you into an inheritance relationship that may be awkward or even impossible.

Object Scoping is mostly an antidote to global functions, so it’s worth remembering that the biggest problems of global functions come with modifying global data. A common case where you don’t get this problem is when the global function just creates and returns a new object, such as Date.today(). Static methods, which are effectively global, can very effectively return such objects which can either be regular objects or Expression Builders. If you can arrange your bare functions to be like this, then there is much less need for Object Scoping.

If the DSL framework is set up to allow a user of the DSL to substitute their own subclass of the scoping class for Object Scoping, this also makes the DSL more extensible. A user subclass can add more methods to extend the language. Indeed if particular methods are only needed in one script, then that script subclass can define those methods directly.

36.3 Security Codes (C#)

We have a building that houses all sorts of secret projects. As a result, the building is divided into zones, and each zone has security policies that govern what kinds of employees can enter a zone. As an employee approaches a door into the zone, the system checks the employee against the zone’s policies and decides whether or not to admit her.

The DSL I’m going to build will support expressing rules like this:

image

36.3.1 Semantic Model

The Semantic Model has a zone class with multiple admission rules. Each admission rule is either an allow rule (specifying conditions to let someone in) or a reject rule (specifying conditions to refuse entry). The admission rule has a body (that we’ll explore later) and the method to check if an employee can be admitted.

image

I handle the two kinds of admission rules through inheritance. Each one provides an implementation of CanAdmit.

image

When asked to admit an employee, the zone class runs through the admission rules in order, seeing how they respond.

image

If none of the rules give an opinion, the method defaults to refusal (false). The body of the admission rule is a composite structure of rule elements, essentially a Specification [Evans DDD]. The declared type is an interface.

image

Various implementations all check the attributes of an employee. Here’s checking for grades and departments:

image

I have a composite element, so I can combine them into logical structures.

image

So if I want to admit someone who is a senior programmer in the K9 department, I can set up the zone like this:

image

36.3.2 DSL

To use Object Scoping, I create a builder superclass that I can inherit from to form the DSL. Here’s the example subclass again that shows the kind of DSL I’m supporting:

image

This rule first allows anyone from the MF department in until a cut-off date next year. It then explicitly refuses access to anyone in the finance or audit departments (in separate refusal clauses), finally allowing any director in between fixed hours up to another cut-off date.

Although the underlying model allows arbitrary Boolean expressions, the DSL is simpler. Each admission rule is a conjunction (“and”) of its clauses. This is why I need separate refuse statements for the two departments. If I put them in the same clause, it would only refuse people who were in both departments.

Arbitrary Boolean expressions are powerful, but often difficult for people, particularly non-nerds, to follow. So some form of simplified structure can be handy in a DSL.

The DSL is comprised of methods which are defined on the base builder class. This allows me to call them in the subclass without any qualification. The Allow method adds a new allow rule to the zone whose body is the conjunction of the method’s arguments.

image

I use a vararg method here as a Literal List. (If there’s only one subexpression, then the wrapping and expression are unnecessary. I could fix that, but I got a good deal on and expressions, so I haven’t bothered.)

Each argument is built up through further functions on the base builder.

image

To add a new element to the system, I define a new expression for the model and a function on the builder.

image

As well as adding rules for all users of a DSL, I can also extend the DSL for specific DSL programs. Let’s imagine it’s only my department that wants access restricted to certain hours. I can put that code directly in the subclass.

image

If other script classes want this feature, but I’m unable to modify the library, I can create my own zone builder class that’s a subclass of the library zone builder and let my scripts subclass that. I can then put any useful methods into my own abstract zone builder.

Object Scoping does help in reducing noise in the DSL, but one problem is that it does introduce noise in the code that declares the DSL class. The first two lines (and closing braces) are awkward noise. It could be a little worse; my natural way to use this class would be to pass the zone into the builder in the constructor, but that would force me to add a constructor declaration to the subclass. I avoided that by passing the zone with a separate method.

image

I call it like this:

image

It’s a small thing, but saves me a bit of noise in the DSL text. These small things add up.

36.4 Using Instance Evaluation (Ruby)

While Object Scoping is a very valuable pattern, as it provides good names without global artifacts, using subtyping does introduce limitations. For a stand-alone DSL, the script file needs some head and tail noise to set up the context. For fragmentary DSLs, you need to be in a subclass of the DSL builder to write DSL expressions.

Ruby has a very good mechanism that you can use to get around these problems: instance evaluation. The idea behind instance evaluation is that you can take some text and evaluate it within the context of a particular Ruby object instance. Any bare method calls on the script are resolved to that instance, as if they were inside an instance method of the class itself. This allows you to write a DSL using Object Scoping without needing to bother with any subclassing.

So for the zones example above, I have the following script file:

image

The builder executes this with a simple call.

image

The bare function calls can resolve to methods on the builder.

image

That’s the essence of using instance evaluation to handle Object Scoping. However, while I was putting this example together, there were a couple of other interesting things that I couldn’t resist going into. It’s crossing the line into language-specific tricks that I usually try to avoid, but it’s been a long day, so please indulge me.

In the example code, I tried to follow the structure of the C# example. However, I felt it read better if a multiclause condition used a Nested Closure rather than a Nested Function. Pulling this off results in a complication. If I have a single clause in an allow or refuse statement, I need to return the value of the clause; if I have a nested block, I need to return an and expression of the values of each clause.

image

For the simple case, I make the method in the builder just return the rule element, so that the rule element is wrapped in the parent allow rule.

image

If I have a Nested Closure, I use a child builder for that expression and use instance evaluation on that again, so the expressions in the DSL bind to the child builder rather than the parent.

image

This mechanism allows me to handle calls to methods like gradeAtLeast differently in different parts of the DSL. In Ruby, it’s nice to use Function Sequence inside a Nested Closure as that allows the contents of the group to be separated with newlines rather than commas.

36.5 Using an Instance Initializer (Java)

A way to use Object Scoping in a relatively unobtrusive inline manner is to use an instance initializer. This technique was popularized by JMock; I confess that until I’d seen it used, I completely neglected that language feature. Using it in a DSL script looks like this:

image

The builder that makes this work looks pretty much the same as the one in C#.

image

The trick is the use of double curly brackets in the DSL script. This creates not an instance of the zone builder, but an inner class that’s an instance of a subclass of the zone builder. This one-off subclass has the code between the double curly braces woven into the constructor. You can always do this sort of thing in Java, although I’ve not seen it that widely used. Since the code between the double curlies is in a subclass of zone builder, we have the Object Scoping that we need.

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

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