Composition
Example
An example that is meant to illustrate protocol composition, or nesting. It involves a Product
protocol between a
customer
and a company
, and a Conditions
protocol that
governs the rate the customer
is paying.
The Product
is a simple product, where the customer
pays for some quantity at some monthly unit rate. This rate is
initially specified, but is ultimately governed by Conditions
that are subject to change. Unilaterally, the company
may increase the rate by no more than 0.5 on a monthly basis via the setRate
permission in Conditions
. However, other
changes are allowed if both parties agree via
changeConditions
.
Source
protocol[company] Conditions(var currentRate: Number) {
require(currentRate > 0, "Rate must be strictly positive");
var lastRateChange: DateTime = now();
permission[company] setRate(newRate: Number) after lastRateChange.plus(months(1)) {
require(newRate > 0, "Rate must be strictly positive");
require(newRate <= (currentRate + 0.5), "Rate increases may not exceed 0.5");
this.currentRate = newRate;
this.lastRateChange = now();
};
};
protocol[customer, company] Product(var quantity: Number, var rate: Number) {
initial state active;
final state cancelled;
var conditions: Conditions = Conditions[company](rate);
permission[customer] computeMonthlyCost() returns Number | active {
return quantity * conditions.currentRate;
};
permission[customer] cancel() | active {
become cancelled;
};
permission[company & customer] changeConditions(newConditions: Conditions) {
this.conditions = newConditions;
};
};
Tests
const CUSTOMER = 'customer';
const COMPANY = 'company';
@test
function testMonthlyCost(test: Test) -> {
var p = Product[CUSTOMER, COMPANY](5, 0.85);
// Compute and verify initial cost
var initialCost = p.computeMonthlyCost[CUSTOMER]();
test.assertEquals(4.25, initialCost, "Monthly cost should be correct");
};
@test
function testIllegalRateIncrease(test: Test) -> {
var p = Product[CUSTOMER, COMPANY](5, 0.85);
// Attempt an illegal rate increase, and expect failure
test.assertFails(function() returns Unit -> p.conditions.setRate[COMPANY](0.9));
};
@test
function testRateIncrease(test: Test) -> {
var p = Product[CUSTOMER, COMPANY](5, 0.85);
// Forward the clock, attempt increase
test.setTime(now().plus(months(1)));
p.conditions.setRate[COMPANY](0.9);
// Compute and verify new cost
var newCost = p.computeMonthlyCost[CUSTOMER]();
test.assertEquals(4.5, newCost, "Monthly cost should be updated and correct");
};
@test
function testChangeOfConditions(test: Test) -> {
var p = Product[CUSTOMER, COMPANY](5, 0.85);
// Compute and verify initial cost
var initialCost = p.computeMonthlyCost[CUSTOMER]();
test.assertEquals(4.25, initialCost, "Monthly cost should be correct");
// Set new conditions
var newCnd = Conditions[COMPANY](0.80);
p.changeConditions[COMPANY, CUSTOMER](newCnd);
// Compute and verify new cost
var newCost = p.computeMonthlyCost[CUSTOMER]();
test.assertEquals(4.00, newCost, "New monthly cost should be correct");
};