Chapter 48. Decision Table

Represent a combination of conditional statements in a tabular form.

image

When you have code that composes several conditional statements, it can often be hard to follow exactly what combinations of conditions lead to what outcomes.

A Decision Table improves understandability by representing the group of conditions as a table, where each column shows the outcome for a particular combination of conditions.

48.1 How It Works

A decision table is divided into two sections: conditions and consequences. Each condition row indicates the state of that condition; for a simple two-value Boolean condition, each cell in the row will be either true or false. There are as many columns in the table as needed to capture each combination of conditions, so for n two-value Booleans you’ll have 2n columns.

Each consequence row represents values of a single output from the table. Each cell represents the value matching the conditions in the same column. So for the case in the sketch, if we have a domestic, regular order from a premium customer, the fee is $50 and we don’t alert a representative. A Decision Table only needs a single consequence, but can happily accept more.

As in the case of the sketch, it’s quite common to have a three-valued Boolean logic, where the third value is “don’t care,” indicating that this column is valid for any value of the condition. Using don’t-care values can remove a lot of repetition in the table, keeping it more compact.

A valuable property of decision tables is that you can determine whether all the permutations of conditions have been captured as columns, and thus indicate missing permutations to the user. It may well be the case that some combinations cannot happen; this can be captured as an error column, or the semantics of the table can allow for missing columns, treating them as errors.

A table can get more complex if we want to introduce more arbitrary enumerations, numeric ranges, or string matches. We can capture each such case as a Boolean, but then the table needs to know that, if we have conditions like 100 > x > 50 and 50 >= x, these conditions cannot be both true at the same time. Alternatively, we can have just a single condition row for the value of x and allow the user to type the ranges in the cells. The latter approach is usually easier to work with. If we have more complex condition values, it will be more awkward to compute all the permutations, and may be better to treat an unmatched case as an error.

As usual, I’d advise building a separate Decision Table Semantic Model and parser. With both of these, you’ll need to decide how generic to make them. You can build a model and parser for just a single Decision Table case. Such a table would have its condition rows fixed in the table code, together with the number and types of its consequences. You’ll usually still want the column values to be configurable, so that it’s easy to change the consequence values for each combination of conditions.

A more generic Decision Table would allow you to configure the conditions and consequence types. Each condition would need some way of indicating the code to run to evaluate a condition (a method name or a closure). The type of input and each consequence would be needed for a strongly typed language configured at compile time.

Similar decisions are needed for the parser. The parser could be for a fixed table, even if it configures a generic Semantic Model. To be more flexible, you need something akin to a simple grammar for the table structure so the parser can properly interpret the input data.

Decision Tables are very simple to follow, and indeed edit, and so are particularly suited to capturing information from domain experts. Many domain experts are familiar with spreadsheets, so a good tactic is to allow the domain experts to edit the tables in a spreadsheet and then import the spreadsheet into the system. Depending on the spreadsheet program and your platform, there are lots of ways of doing this. The crudest (but often effective) way is to save the decision table in a simple text form, such as CSV. This usually works because the table is pure values, no formulae are needed. Other approaches include inter-operating with the spreadsheet program—for example, starting up and talking to a running instance of Excel. Spreadsheets like Excel that have their own programming language can be programmed to receive, edit, and transmit decision table data to a remote program.

48.2 When to Use It

Decision Tables are a very effective way to capture the results of a set of interacting conditions. They communicate well both to programmers and domain experts. Their tabular nature allows domain experts to manipulate them using familiar spreadsheet tools. Their biggest disadvantage is that they do take some effort to set things up so they can be edited and displayed easily, but this effort is usually quite small compared to be communicative benefit they provide.

Decision Tables can only handle a certain degree of complexity—no more than what you can capture in a single (if complex) conditional expression. If you need to combine multiple kinds of conditionals, consider a Production Rule System.

48.3 Calculating the Fee for an Order (C#)

Here I’ll outline a Decision Table that can handle the example I’ve shown in the sketch.

48.3.1 Model

The Semantic Model here is a Decision Table. I’ve decided to create for this example a generic Decision Table that can handle any number of conditions, each of which supports three-value Booleans. I’m using C# generics to specify the input and output types for the Decision Table. Here’s the class declaration and fields:

image

The table needs two kinds of configuration: conditions and columns, each of which gets its own class. The conditions are parametrized with the input type, and the columns with the output (consequence) type. I’ll begin with the conditions.

image

This allows me to configure the conditions for the example table with this code:

image

The input type for the decision table is an order. I won’t go into details, since it’s just a dummy for this example. The output is a special class that just wraps the output data.

image

The next part of setting up the table is to capture the column values. Again, I use a class for the column.

image

The column has two parts. The result is the type that handles the consequences. This type is the same output as the output type of the decision table itself. The condition block is a special class that represents one combination of condition values.

image

I’ve made a three-valued Boolean class to represent the values in the conditions. I’ll describe how they work later on, but for the moment we can assume that there’s just three legal instances of Bool3, corresponding to true, false, and don’t care.

I can now configure the columns like this:

image

This describes how to configure a decision table, but the next question is how it works. At the heart of the table is the three-valued Boolean. I’ve written this polymorphically, using a different subclass for each value:

image

A single Bool3 has a Matches method that compares to another value. Similarly, the condition block compares its list of Bool3s against another condition block.

image

This method is a “matches” method, not an “equals” method because it’s not symmetric. (This means that Bool3.X.Matches(Bool3.T), but not vice versa).

The matching of condition blocks is the core mechanism. Now, once I have a decision table configured, I can run it on a particular order to get a fee result.

image

With this, we can see how the decision table model is configured and how it runs. But before we move onto the parser, I think it’s worth showing the code that the decision table can use to ensure it has a column to match every permutation of conditions.

The top level of this code is straightforward. I write a function to find any missing permutations by generating every possible permutation for a given number of conditions and checking if it is matched by the columns.

image

This begs the question of how do I generate all the permutations. I found it easier to do this in a two-dimensional matrix and then pull out each column of the matrix as a permutation.

image

The code for generating the permutations is trickier than I’d like, but it seemed easier to write using a matrix data structure. In situations like this, I’m quite happy to use the data structure that makes it easiest to write some code and then transform the result into the data structure I actually want to consume. It reminds me of my engineering days when you would get a problem that’s difficult to solve in your usual coordinate system; you’d then transform the problem to a coordinate system that made it easier to solve the problem, solve it, and transform it back again.

48.3.2 The Parser

When working with a tabular form like this, often the best form of editor is a spreadsheet. There are lots of ways to get data from a spreadsheet into a C# program, and I’m not going to try to describe them here. Instead, I’ll write the parser to operate on a simple interface for a table.

image

I’ll parse the table in the spirit of Delimiter-Directed Translation but using rows and columns instead of a stream of delimiter-separated tokens.

For the model, I wrote a generic decision table that I could use with any table of three-value Booleans. For the parser, however, I’ll write one specifically designed for this table. It is possible to write a general table parser and configure it for this case, but I thought I’d leave that as an exercise for you to do on some cold winter night.

The basic structure of the parser is a command object that takes an ITable as input and returns a decision table as output.

image

As is my habit with command objects, I provide the parameters to the command in the constructor and use a run method to do the work.

The first step is to load the conditions.

image

A potential problem here is that the table might reorder the conditions or change them without updating the parser. So I do a simple check on the condition names.

image

Loading the conditions doesn’t really pull any data from the table, other than the check of the condition names. The main purpose of the table is to provide the conditions and consequences for each column, which I load in the next step.

image

As well as picking out the right cells from the input table, I also need to parse the strings into appropriate values.

image

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

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