Code, meet outside world

Một phần của tài liệu Manning functional reactive programming (Trang 91 - 96)

Each version of the petrol pump logic is an implementation of the interface given in listing 4.1, where Inputs and Outputs are simple container classes with Stream and Cell type fields. Each class that implements Pump creates the machinery that connects the inputs to the outputs; all the logic you’ll need can be done in this way. When you run the simulator, you can select which pump logic you want to run in a drop-down box at the top.

package pump;

public interface Pump {

public Outputs create(Inputs inputs);

}

Figure 4.5 is a diagrammatic representation of the inputs and outputs of the pump logic encapsulated by Inputs and Outputs, and how they connect to things in the real world.

Listing 4.2 shows Inputs. These are the input streams:

■ sNozzle1, sNozzle2, sNozzle3—For each of the three fuels, a stream that fires when the nozzle is lifted (signaling the start of a fill) or hung up.

■ sKeypad—A stream representing a press of one of the buttons on the preset keypad, used to enter the fill amount. Key is an enumerated type identifying which button was pressed.

■ sFuelPulses—Pulses counted through the fuel-flow meter. The event payload is an integer number of pulses counted since the last event.

■ sClearSale—A signal sent from the point of sale once payment is completed, to unlock the pump for the next fill.

The input cells are as follows:

■ calibration—The multiplier to turn pulses into liters

■ price1, price2, price3—The price of each of the three fuels in dollars per liter Listing 4.1 Interface for the petrol pump logic

69 Code, meet outside world

Java forces you to write a bit of container-class boilerplate here, but we’ve given the entire code in listing 4.2 to make sure it’s clear.

Immutability is extremely important in FRP, so you make all the fields final, which means they can only be modified in the constructor. This is a good habit, and you should follow it whenever you can. In your FRP code, you can do this all the time.

package pump;

import nz.sodium.*;

public class Inputs { public Inputs(

Stream<UpDown> sNozzle1, Stream<UpDown> sNozzle2, Stream<UpDown> sNozzle3, Stream<Key> sKeypad,

Stream<Integer> sFuelPulses, Cell<Double> calibration,

Listing 4.2 Inputs to the petrol pump logic

sNozzle1 sNozzle2 sNozzle3 sKeypad sFuelPulses

Logic goes here

Pump mechanics calibration

price1 price2 price3 sClearSale

sSaleComplete

Point of sale sBeep

priceLCD1 priceLCD2 priceLCD3 delivery presetLCD saleCostLCD saleQuantityLCD

Figure 4.5 The interface between the pump logic and the (simulated) outside world

Cell<Double> price1, Cell<Double> price2, Cell<Double> price3, Stream<Unit> sClearSale) { this.sNozzle1 = sNozzle1;

this.sNozzle2 = sNozzle2;

this.sNozzle3 = sNozzle3;

this.sKeypad = sKeypad;

this.sFuelPulses = sFuelPulses;

this.calibration = calibration;

this.price1 = price1;

this.price2 = price2;

this.price3 = price3;

this.sClearSale = sClearSale;

}

public final Stream<UpDown> sNozzle1;

public final Stream<UpDown> sNozzle2;

public final Stream<UpDown> sNozzle3;

public final Stream<Key> sKeypad;

public final Stream<Integer> sFuelPulses;

public final Cell<Double> calibration;

public final Cell<Double> price1;

public final Cell<Double> price2;

public final Cell<Double> price3;

public final Stream<Unit> sClearSale;

}

Listing 4.3 shows the outputs. The output streams are as follows:

■ sBeep—When this stream fires, the simulator emits a short beep.

■ sSaleComplete—A message that’s sent to the point-of-sale system giving the details of the sale once it’s complete. Recall the input stream sClearSale: the simulated point of sale uses this to convey a response back from the point of sale to clear the sale and unlock the pump for the next fill.

The output cells are as follows:

■ delivery—Specifies whether to deliver fuel and, if so, which fuel and at what speed. Values: OFF, SLOW1, FAST1, SLOW2, FAST2, SLOW3, and FAST3.

■ presetLCD—The LCD that appears above the keypad in which you can enter a preset dollar amount.

■ saleCostLCD—Displays the dollars spent.

■ saleQuantityLCD—Displays the liters delivered.

■ priceLCD1, priceLCD2, priceLCD3—Displays the price for each fuel.

You’re writing the set methods in an unusual way: each method returns a copy of the structure, replacing one field’s value (shown in bold). You’re doing this to make the data structure immutable, meaning it can’t be modified in place. Then you know the values contained in it can’t be changed. This makes the code easier to rea- son about and preserves compositionality. This technique also makes the examples

71 Code, meet outside world

easier to read because you only have to specify the fields you care about; the rest can be left as defaults. We don’t recommend that you use this pattern all the time, but it can often be useful.

NOTE Copying the entire data structure to modify one field may feel ineffi- cient, but it’s not as inefficient as you may think. This pattern isn’t absolutely needed, but we recommend it as a way of making sure you do things the right way. When performance considerations drive software design, the result is usually bad. In fact, most assumptions that people make about performance turn out to be incorrect. For this reason, performance decisions should almost always be made on the basis of profiling.

package pump;

import nz.sodium.*;

public class Outputs { private Outputs(

Cell<Delivery> delivery, Cell<String> presetLCD, Cell<String> saleCostLCD, Cell<String> saleQuantityLCD, Cell<String> priceLCD1, Cell<String> priceLCD2, Cell<String> priceLCD3, Stream<Unit> sBeep,

Stream<Sale> sSaleComplete) { this.delivery = delivery;

this.presetLCD = presetLCD;

this.saleCostLCD = saleCostLCD;

this.saleQuantityLCD = saleQuantityLCD;

this.priceLCD1 = priceLCD1;

this.priceLCD2 = priceLCD2;

this.priceLCD3 = priceLCD3;

this.sBeep = sBeep;

this.sSaleComplete = sSaleComplete;

}

public Outputs() {

this.delivery = new Cell<Delivery>(Delivery.OFF);

this.presetLCD = new Cell<String>("");

this.saleCostLCD = new Cell<String>("");

this.saleQuantityLCD = new Cell<String>("");

this.priceLCD1 = new Cell<String>("");

this.priceLCD2 = new Cell<String>("");

this.priceLCD3 = new Cell<String>("");

this.sBeep = new Stream<Unit>();

this.sSaleComplete = new Stream<Sale>();

}

Listing 4.3 Outputs from the petrol pump logic

public final Cell<Delivery> delivery;

public final Cell<String> presetLCD;

public final Cell<String> saleCostLCD;

public final Cell<String> saleQuantityLCD;

public final Cell<String> priceLCD1;

public final Cell<String> priceLCD2;

public final Cell<String> priceLCD3;

public final Stream<Unit> sBeep;

public final Stream<Sale> sSaleComplete;

public Outputs setDelivery(Cell<Delivery> delivery) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setPresetLCD(Cell<String> presetLCD) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setSaleCostLCD(Cell<String> saleCostLCD) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setSaleQuantityLCD(Cell<String> saleQuantityLCD) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setPriceLCD1(Cell<String> priceLCD1) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setPriceLCD2(Cell<String> priceLCD2) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setPriceLCD3(Cell<String> priceLCD3) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setBeep(Stream<Unit> sBeep) {

return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

}

public Outputs setSaleComplete(Stream<Sale> sSaleComplete) { return new Outputs(delivery, presetLCD, saleCostLCD,

saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);

} }

73 The life cycle of a petrol pump fill

Một phần của tài liệu Manning functional reactive programming (Trang 91 - 96)

Tải bản đầy đủ (PDF)

(362 trang)