Chapter 35. Method Chaining

Make modifier methods return the host object, so that multiple modifiers can be invoked in a single expression.

image

35.1 How It Works

Method Chaining rapidly caught on amongst people as an example of what an internal DSL should look like. It caught on a bit too much—people started to assume that Method Chaining was synonymous with fluent interfaces and internal DSLs. My view is that Method Chaining is one of several techniques, but it’s still valuable and noticeable.

Its common form is on an Expression Builder. Consider the hard drive in the sketch. Using a regular command-query API, I might set it up like this:

image

I create my object, put it in a variable, and then use setters to manipulate its properties. For just three items like this, I’d be more likely to use a constructor, but let’s assume there’s many more of them. DSLs are often about building up configurations of objects, and doing so in constructors is often tricky. It’s also usually difficult to read, since constructors often allow only positional parameters.

Using Method Chaining, I would do something like this:

new HardDrive().capacity(150).external().speed(7200);

To make the chain work, methods designed to be used in a chain are implemented differently from the common convention for setter methods. In Java, we usually implement a setter method like this:

image

But a method designed for use in a chain needs to return an object to continue the chain. For this builder, it needs to return itself.

image

Returning a value from a modifying method breaks the principle of command-query separation (p. 70). Most of the time I follow that principle, and it’s served me well. A fluent interface is one case when we need to break it.

There’s a second consequence of using Method Chaining like this—the naming of the method. In many naming conventions, a method like sata() would seem like a query, not a modifier. This naming is very problematic, as it will seriously confuse anyone who is expecting a command-query API. Taken together, Method Chaining violates many common rules of common (command-query) API design.

Not just does Method Chaining change the rules for API design, it also implies a change to formatting conventions. Usually, we try to keep multiple method calls on a single line, but long Method Chaining often does not look good that way, particularly if we want to suggest a hierarchy. As a result, it’s often better to format Method Chaining with each call on its own line.

image

Java and C# ignore most newlines, which gives us a lot of flexibility in formatting. There is a general preference to have the periods at the start of the line, as this makes them more noticeable and thus emphasizes the use of chaining. Languages that use newlines as statement separators are less flexible here. Ruby, for example, will work but you need to have the periods at the ends of the lines rather than the beginnings. Putting methods on separate lines also makes debugging easier, as error messages and debugger control usually work on a line-by-line basis. Therefore, it’s wise to do less on each line.

35.1.1 Builders or Values

In the example above, I showed Method Chaining on an Expression Builder. I prefer keeping Method Chaining, and other fluent APIs, to Expression Builders since that reduces the confusion between the conventions of fluent and command-query APIs.

However, there are cases where it can be useful to use Method Chaining outside of Expression Builders, for example in something like 42.grams.flour. In this case, we are building up an expression through a sequence of Value Objects [Fowler PoEAA]. The grams method is defined on integer (using Literal Extension) and returns a quantity object, which is the host for the flour method that returns an ingredient. Instead of having a single Expression Builder, we have a sequence of regular objects. Often, when you see this, the objects are Value Objects.

At each step in the expression we see a change to a new type, a phenomenon that my colleague Neal Ford refers to as type transmogrification. (I need to mention that term here, as he’ll be upset otherwise and won’t bring me good tea anymore.)

There are plenty of good developers who are comfortable with using Method Chaining on domain types like this, so I’m cautious about arguing against it. My inclination, however, leads me to prefer using Expression Builders as much as possible, to clearly separate command-query and fluent API styles.

35.1.2 Finishing Problem

The finishing problem is a common issue with Method Chaining. It boils down to the lack of a clear end-point to a method chain. Imagine a builder for appointments that allows expressions like:

image

I would like the returned value to be an Appointment object, since that would be the most natural usage. However, the need to continue the method chain means that each method has to return an appointment builder. There’s nothing in the chain that tells me when I’m done, so I have to put in some kind of marker method to show the end.

image

It isn’t too bad, but the use of End is still a bit of syntactic noise. This is where using Nested Function or Nested Closure can be a valuable alternative. In C#, you can avoid it by using an implicit conversion operator, although that does mean you’ll forgo var for an explicit type.

35.1.3 Hierarchic Structure

Tied in with the finishing problem is the problem that Method Chaining doesn’t naturally fit a hierarchic structure. Hierarchic structures are common in languages, which is why syntax trees are valuable for thinking about them. Consider the sketch example again:

image

There’s a definite hierarchy to this, but it’s suggested by the indentation and not captured in the structure of the code itself. As a result, we have to manage that structure ourselves. This problem also occurs with Function Sequence.

A good example of where we have to do this management is checking if we are manipulating the correct disk when we have a method like size. There are a couple of approaches here. One is to use a Context Variable, such as currentDisk. Each time we see a disk method, we can update the Context Variable. We can keep a list of the disks and update the last one in the list each time.

Often, a useful approach is to have a new child builder for the disk. A separate builder allows us to limit the methods available to only those required to provide the information for the disk or a finishing method.

35.1.4 Progressive Interfaces

A valuable variation to the basic Method Chaining approach is to use multiple interfaces to drive a fixed sequence of method-chaining calls. Let’s consider building up an email message. We want the programmer to first specify the destination address, any Cc’s, the subject, and then the body. We can do this by presenting a sequence of interfaces to the Expression Builder. The first interface has only the to method. The to method returns an interface with only the legal next steps: to, cc, and subject. The cc method returns an interface with only cc and subject. The subject method returns an interface with only the body method.

This can work really well in a statically typed language with IDE support. Autocompletion in the IDE can step you through each clause in the DSL by only suggesting the methods that are valid for that point in the chain.

This ability to control which methods are valid in which contexts is similar to that you get by using a child builder. Indeed, you can use a child builder to do the same thing as progressive interfaces, but progressive interfaces are easier if there’s no other reason to make a child builder.

Progressive interfaces can be used to enforce mandatory elements in a chain; for this, define an interface that only takes a single mandatory element.

35.2 When to Use It

Method Chaining can add a great deal to the readability of an internal DSL and, as a result, has become almost a synonym for internal DSLs in some minds. Method Chaining is best, however, when it’s used in conjunction with other function combinations.

Method Chaining works best when using optional clauses in a language. Method Chaining easily allows a DSL script writer to pick and choose clauses needed for a particular situation. It’s difficult to specify in the language that certain clauses must be present. Using progressive interfaces allows some ordering of clauses, but in the end clauses can always be left out. Nested Function is the better choice for mandatory clauses.

The finishing problem crops up from time to time. While there are workarounds, usually if you run into this you’re better off using a Nested Function or Nested Closure. These alternative are also better choices if you are getting into a mess with Context Variables.

35.3 The Simple Computer Configuration Example (Java)

Here’s the basic computer configuration example done with a healthy dose of Method Chaining:

image

To start an expression using Method Chaining, you need some method call to initiate the chain. In this case, I’m using a static method that I can reference in the DSL script by using a static import.

image

I use the computer builder to define the various methods I need for chaining. It also contains the parse data.

For the processor, I store a Construction Builder for the current processor in a Context Variable.

image

As is characteristic for Method Chaining, the builder returns itself with each call in order to continue the chain.

Specifying the disks is a bit more involved, since each disk has its own data. I could define more context variables on the computer builder, just as I did for processor, but in this case I’ll use a separate builder to capture the attributes for the disk.

image

The tricky bit here is shuffling between the computer builder and the disk builder and keeping the Context Variables in step. The disk clause introduces a new disk, so the computer builder puts a new disk builder into a context variable and passes the call to it.

image

The disk clause also occurs between disks. As a result, I add the current disk to a list of loaded disks before making a new builder. The disk builder will get the disk call if I’m in the middle of making one, so I just forward the call to the computer builder.

image

In this example, I have to deal with the finishing problem. I’ve done the simplest workaround here: an end method. As with the disk clause, the end method can appear as a call to disk builder, so I forward it to the computer builder when that happens.

image

In the computer builder, I use the end method to create and return the computer that’s been configured.

image

This allows me to use the builder in this style:

image

Otherwise, I need to do something like:

image

With this example, I’ve been inconsistent in my use of the subsidiary builders for the processor and disks. The processor builder is a simple Construction Builder, just used to store the intermediate values. With the disk builder, I’ve delegated the fluent methods to it. A simple Construction Builder works better for simple cases and full delegation works better for more complicated cases. I’ve shown both here for pedagogical reasons, although I lean more to full delegation.

The example illustrates quite well many of the issues in using Method Chaining, particularly compared to Nested Function. Method Chaining reads very clearly, without much of the syntactic noise that can clutter Nested Function. However, to pull it off, I have to do a lot of fiddling around with Context Variables and cope with the finishing problem.

35.4 Chaining with Properties (C#)

C# and Java are similar languages, so many of the comments that apply to Java apply to C# too. The biggest difference is that C# has a special property syntax, instead of Java’s more fumbly getters and setters. As a result, the regular example would look like this:

image

The chaining case looks almost the same.

image

The chaining modifiers for speed and capacity are identical (other than the capitalization convention). There is, however, one interesting variation in handling the external property. By using a property getter for the external property, I can get rid of the unnecessary and annoying parentheses. I implement the property getter like this:

image

This code should make you feel distinctly uneasy: It’s a property getter that’s really acting as a setter, returning the object itself rather than the value of the property. This violates all our expectations of how property getters should work. In almost all circumstances, I would call this extremely bad code. It’s only acceptable when clearly placed in a fluent context—again, I would confine this abomination to a securely fenced Expression Builder.

35.5 Progressive Interfaces (C#)

Autocompletion is one of the joys of modern IDEs. I no longer need to remember what methods are callable on a particular class—I can just hit a key combination and get a menu right there. Since my brain filled up about fifteen years ago, I appreciate having to remember less.

Many DSLs have a definite order in which things can be built up. We can use autocompletion to help signal that if we use progressive interfaces. Suppose we want to build up an email message:

image

We want to ensure that we build up the elements of the message in a particular order: first the destination address, then the Cc’s, then the subject, and finally the body. With vanilla Method Chaining, there’s nothing to enforce a particular order.

The chocolate sauce in this case is to use multiple interfaces over the Expression Builder. I’ll start with build.

image

I return an Expression Builder just as I would normally do, but the return type is a special interface that only allows the legal next step in the sequence. The Expression Builder implements that interface, and now I can only make that call next. As an added bonus, my autocompletion menus will now only show me the legal next steps (although it’s not perfect, as methods inherited from Object also show up). Thus autocompletion can guide me through the process.

The next step continues the story.

image

One new thing is that the legal next steps after To include the legal steps after Build. I can show this, without duplicating the body of IMessageBuilderPostBuild, by using inheritance between the interfaces. It’s not really that worthwhile in this example, but it’s often a useful technique.

The rest of the sequence continues as you’d expect.

image

I have a natural stop method with Body, so I’ll have that return the message.

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

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