Sunday, July 4, 2021

Two State Objects

Most Object Oriented Programming examples I've seen use Objects that describe things that already exist.  A dog may have blue eyes or a car has a make and model, but how are Object characteristics derived?  Of course there is a place for representing existing things with Objects, but in my experience it is how that Object got to that state of existence is where things get interesting.

In this exploration of Objects getting to their state of existence I will primarily focus on code responding to some event.  Of which there are endless examples:  An Order for goods or services that will need a bill.  A fingerprint submitted for recognition.  Submitting a request for insurance coverage for approval.  Submitting an insurance claim for approval and hopefully payment.  An Airline reservation request.  

Taking the Airline reservation request as an example.  The first Object is the Reservation Request (or event) that shows up as already existing.  Then I start with the desired result, the Quote for the Flight.


The Quote Object will use other Objects to build its results, and those Objects will in turn use other Objects to build their results.  Perhaps something like this.


Two State Objects

Throughout the programs processing the State of the Object’s data is either determined and available or not yet required, which is why I refer to them as two state Objects.  Their state is ultimately dependent on the event.  A different event results in a different state for each Object.

Dependency Injection

It helps to use some type of dependency injection to connect the Objects.  Dependencies change when requirements of the system change.  It is easier to change these connections in one place rather than passing Objects from one Object to another.  Dependency injection can also help unit test these classes independently with mocked up dependencies, but that is another topic.

If we just manually inject dependencies it could look like this.

flight        = new Flight(request)
customer      = new Customer(request)
awardsProgram = new AwardsProgram(request, customer)
pricing       = new Pricing(flight, customer, awardsProgram)
seat          = new Seat(pricing)
taxes         = new Taxes(pricing)

Notice the Objects are constructed when the event shows up.  The state that each Object will become again is ultimately dependent on the event that we process.  But at this point in processing of the event, no Object state has been determined.  The Objects have only been constructed and dependencies injected.

To further explore two state objects I’ll add some method calls to our fabricated quoting program.  This airline likes to seat high price ticket holders up front hoping for more food and alcohol sales, so we have the seat & taxes Objects also depending on price.


Member Variables

Objects will provide some of the same data more than once throughout the program’s processing.  Customer provides its data to awardsProgram and pricing.  Pricing provides the same calculated price to the quote, seat and taxes.  That price should be kept for every dependent Object to reference.  Similar to a caching strategy we can retain the values for reuse.  Since these are just two state Objects, the calculated values can simply be kept in the Object’s member variables.  Pricing could first just check if(price != null) then return price otherwise calculate the price, set the price member variable and return the value.

In most cases an Object has multiple values to provide.  Sometimes those values have separate logic and can still just check if individually are null.  The pricing Object is actually a little more complex and perhaps coded like this to determine the price (and other member variables).


class Pricing
{
public decimal getPrice() 
{
return getResult().price
}

public decimal getAwardsOnQuote() 
{
return getResult().awardsOnQuote
}

private PriceResult getResult()
{
//
// Check if already Calculated
//
if (this.result != null)
return this.result

price = flight.getDistance() * 0.20

switch flight.getHistoricDemand()
case: high        price = price * 1.50
case: medium      price = price
case: low         price = price * 0.80

if (flight.getCapacity() < 50)
price = price * 0.80
if (customer.isGoldMember())
price = price * 0.90
if (awards.isAwardsEligible() && awards.getAwardsAmount() > price)
{
price = 0
awardsOnQuote = price
}
else
awardsOnQuote = 0

//
// Set and Return
//
this.result = new PriceResult() {
price = price
awardsOnQuote = awardsOnQuote
}

return this.result
}
}


Advantages

No code is required to order the flow of execution.  Dependencies are already injected, so just use the Objects as needed.  Those Objects will in turn calculate or lookup any data as they need.  The price could be used first or the seat, it does not make a difference in the code.

So you can build the quote Object like this.

return new Quote() {
price  = pricing.getPrice()
awards = pricing.getAwardsOnQuote()
seat   = seat.getSeat()
taxes  = taxes.getTaxes()
}

Or perhaps we need to check the Seat first.  Even though the Seat is dependent on Price, the dependencies are already in place.

if (seat.getSeat() == null)
throw new Exception(“No seat available”)

return new Quote() {
price  = pricing.getPrice()
awards = pricing.getAwardsOnQuote()
seat   = seat.getSeat()
taxes  = taxes.getTaxes()
}

No code is required to pass around data.  For example, no need to pass the price to other methods like seating or taxing and beyond.

Dependency changes become easy to handle.  If for example we have new requirements to add City taxes we can easily add the flight Object as a new dependency to the taxes Object.  The taxes class can now reference the City the flight originates from.  No change to any other class to pass around data, they remain independent.  As applications get larger without dependency injection sometimes we end up needing to pass data like the flight information from one method to another to get the flight information to the taxes Object.  It is much easier when we can edit dependencies and source the data directly from the Object that determines it.

These Objects are defined by the data they provide, not by how the data is determined or calculated.  Classes known for and named as providing the what, not as the how.

Prefer getting data from the Object creating it, over passing the data around. The Objects are then the goto source for the data that each provides.


3 comments:

Two State Objects