Communicating with the point-of-sale system

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

In this section, you’ll communicate asynchronously with a remote point-of-sale system through two streams:

■ sSaleComplete—An output stream telling the point-of-sale system that a sale has completed

■ sClearSale—An input stream telling the pump that the point-of-sale system has finished processing the sale and the pump is allowed to process a new fill Between these two events, you lock the pump so it can’t fill any more. This complicates the definition of when the fill begins and ends. You now have three state transitions:

■ Lifting the nozzle to start a fill

■ Hanging up the nozzle to end a fill and notifying the point-of-sale system

■ Clearing the sale at the point-of-sale system

Listing 4.9 gives the petrol pump implementation, and listing 4.10 shows the new NotifyPointOfSale. To make the code robust, you use scoping: you ensure that the LifeCycle is passed directly to new NotifyPointOfSale so it isn’t hanging around in a variable visible in create(). NotifyPointOfSale can then output a modified view of the fill life cycle, and it’s difficult for you to use the original life cycle by mistake. Lim- iting scope in various ways is an important key to avoiding bugs in FRP code.

Captured price for the fill

Price to show when not filling

Shows captured price when filling

Prices for fuels not selected are blank during the fill.

When not filling

83 Communicating with the point-of-sale system

If you view the petrol pump conceptually, point-of-sale communications are a con- cept in that view. Thus it makes sense for you to put the logic that pertains to it into a NotifyPointOfSale module (that is, class). FRP code is naturally loosely coupled, so it leaves you free to arrange the order and grouping of FRP statements to fit a concep- tual view of the logic.

package chapter4.section8;

import pump.*;

import chapter4.section4.LifeCycle;

import chapter4.section7.Fill;

import chapter4.section7.ShowDollarsPump;

import nz.sodium.*;

import java.util.Optional;

public class ClearSalePump implements Pump { public Outputs create(Inputs inputs) {

StreamLoop<Fuel> sStart = new StreamLoop<>();

Fill fi = new Fill(

inputs.sClearSale.map(u -> Unit.UNIT), inputs.sFuelPulses, inputs.calibration, inputs.price1, inputs.price2, inputs.price3, sStart);

NotifyPointOfSale np = new NotifyPointOfSale(

new LifeCycle(inputs.sNozzle1, inputs.sNozzle2, inputs.sNozzle3), inputs.sClearSale,

fi);

sStart.loop(np.sStart);

return new Outputs()

.setDelivery(np.fuelFlowing.map(

of ->

of.equals(Optional.of(Fuel.ONE)) ? Delivery.FAST1 : of.equals(Optional.of(Fuel.TWO)) ? Delivery.FAST2 : of.equals(Optional.of(Fuel.THREE)) ? Delivery.FAST3 : Delivery.OFF)) .setSaleCostLCD(fi.dollarsDelivered.map(

q -> Formatters.formatSaleCost(q))) .setSaleQuantityLCD(fi.litersDelivered.map(

q -> Formatters.formatSaleQuantity(q)))

.setPriceLCD1(ShowDollarsPump.priceLCD(np.fillActive, fi.price, Fuel.ONE, inputs))

.setPriceLCD2(ShowDollarsPump.priceLCD(np.fillActive, fi.price, Fuel.TWO, inputs))

.setPriceLCD3(ShowDollarsPump.priceLCD(np.fillActive, fi.price, Fuel.THREE, inputs))

.setBeep(np.sBeep)

.setSaleComplete(np.sSaleComplete);

} }

Listing 4.9 Pump implementation in which you clear the sale

The accumulator is cleared by the point-of-sale system instead of when the nozzle is lifted.

NotifyPointOfSale()’s

implementation of sStart Constructs point-of-

sale logic (next listing)

Listing 4.10 shows the NotifyPointOfSale class. In a cell called phase, you keep track of the three phases of a fill: idle, filling and waiting for the point-of-sale system. We represent these with an enum:

private enum Phase { IDLE, FILLING, POS };

gate() is a method on Stream that allows events through only when its cell argument is true. It isn’t treated as a primitive in this book because it’s built from snapshot and filter. You use gate() to ensure that nozzle lift and hang-up are only let through in the right phases:

sStart = lc.sStart.gate(phase.map(p -> p == Phase.IDLE));

sEnd = lc.sEnd.gate(phase.map(p -> p == Phase.FILLING));

This protects against things happening at the wrong times. For instance, if a nozzle is lifted while we are waiting for the point-of-sale system, it's ignored.

sSaleComplete is a little fiddly because you get the selected fuel from fuel- Flowing, which has no value when you aren’t filling. You have to deal with this unde- sirable edge case. You can read the code carefully and reason that it will always have a value when you need it, but it’s far better to eliminate these kinds of invalid states.

This is the kind of improvement that switch will allow you to make when we cover it in chapter 7. The rest is explained in the code annotations.

public class NotifyPointOfSale { public final Stream<Fuel> sStart;

public final Cell<Optional<Fuel>> fillActive;

public final Cell<Optional<Fuel>> fuelFlowing;

public final Stream<End> sEnd;

public final Stream<Unit> sBeep;

public final Stream<Sale> sSaleComplete;

private enum Phase { IDLE, FILLING, POS };

public NotifyPointOfSale(

LifeCycle lc,

Stream<Unit> sClearSale, Fill fi) {

CellLoop<Phase> phase = new CellLoop<>();

sStart = lc.sStart.gate(phase.map(p -> p == Phase.IDLE));

sEnd = lc.sEnd.gate(phase.map(p -> p == Phase.FILLING));

phase.loop(

sStart.map(u -> Phase.FILLING) .orElse(sEnd.map(u -> Phase.POS)) .orElse(sClearSale.map(u -> Phase.IDLE)) .hold(Phase.IDLE));

fuelFlowing = sStart.map(f -> Optional.of(f)).orElse(

sEnd.map(f -> Optional.empty())).hold(Optional.empty());

Listing 4.10 Module to handle notifying the point-of-sale system

Three phases of a fill

Allows fill start only when IDLE

Allows fill end only when FILLING

Changes phase on three different events

Cleared when the nozzle is set down

85 Modularity illustrated: a keypad module

fillActive = sStart.map(f -> Optional.of(f)).orElse(

sClearSale.map(f -> Optional.empty())).hold(Optional.empty());

sBeep = sClearSale;

sSaleComplete = Stream.filterOptional(sEnd.snapshot(

fuelFlowing.lift(fi.price, fi.dollarsDelivered, fi.litersDelivered, (oFuel, price_, dollars, liters) ->

oFuel.isPresent() ? Optional.of(

new Sale(oFuel.get(), price_, dollars, liters)) : Optional.empty())

));

} }

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

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

(362 trang)