This chapter teaches you how to restructure the car-ownership model with UML inheritance and automate a sale by using a state machine on Car.
This is Chapter 9 of the MDriven Bootcamp. Continue from Chapter 8, or return to Chapter 1.
What you will build
You will replace the earlier ownership relationships on Person with a shared abstract CarOwner type. A person, dealer, factory, and scrap yard can then participate in car ownership through the same associations.
You will also give each Car a state machine with this sale lifecycle:
BrandNew --InitiateSale--> InOwnershipTransaction --CloseSale--> OwnershipStable
^ |
|----InitiateSale----|
When a sale begins, the model creates a CarTransferOwnershipDocument. The sale can close only after the document has a buyer. When it closes, the car moves from the seller's current cars to the buyer's current cars, while the seller retains ownership history.
Before you begin
- Open the model you completed in Chapter 8.
- Save frequently with
Ctrl+S. - Run the web application after each major model or ViewModel change. This makes association-end and AutoForm errors easier to isolate.
Refine the existing layout
Continue the placing-container work from the previous chapter.
- Remove the 20-pixel margin from the buttons.
- Select the relevant placing container and set Align Items to
endso that the controls align at the bottom line. - Test combinations of Justify Content and Align Items in the web application.
For example, change the browser width after each setting. Observe whether the buttons are distributed along the main layout direction by Justify Content and aligned across that direction by Align Items. Keep the configuration that gives the intended bottom alignment without relying on button margins.
Model a common car owner
Add the inheritance hierarchy
Create a common owner type so that people and organizations can own cars.
- Add the classes
CarDealerandCarOwner. - Add generalization arrows from
CarDealertoCarOwnerand fromPersontoCarOwner. - Add the attribute
Name:StringtoCarOwnerand save the model.
A generalization makes the class at the arrow source a subclass and the common class a superclass. Here, Person and CarDealer are subclasses; CarOwner is their superclass. Read UML Inheritance before continuing if this distinction is unclear.
Resolve the duplicate Name attribute
After adding CarOwner.Name, MDriven Designer reports Name as a doublet because Person already has that attribute and inherits from CarOwner.
- Remove
NamefromPerson. - Keep
Name:StringonCarOwner. - Save and verify that the duplicate-name error is gone.
For example, a Person can still display its name because it inherits CarOwner.Name; you maintain that attribute in one place.
Move ownership history to CarOwner
The existing historical-ownership relationship must work for all kinds of owners, not only people.
- Move the multi-link association between
CarandPerson—the association that has theHistoricOwnershipassociation class—toCarandCarOwner. - Rename the
Carassociation end fromCarsPersonUsedToOwntoCarsOwnerUsedToOwn. - Apply the changes and inspect the reported errors.
- Recreate and refresh the affected AutoForms to update references to the old association end.
- Select
CarOwner. In the Property Inspector, set IsAbstract toTrue.
An abstract class is a shared type that you do not create directly. In the diagram, CarOwner is shown in italics. You will create a Person, CarDealer, CarFactory, or ScrapYard instead.
Model current ownership and sale documents
Add current ownership
- Add a new association between
CarOwnerandCar. - Name the
Carassociation endCurrentlyOwnedCars. - Delete the old relationship that represented the currently owned car.
- Update the AutoForms, save the model, and resolve any remaining errors.
- In the
Personform, delete the ComboBox that displayed the removed current-car relationship.
For example, a factory can have several values in CurrentlyOwnedCars. This is why current ownership belongs on CarOwner, rather than being limited to Person.
Add a transfer document
- Add a class named
CarTransferOwnershipDocument. - Add an association from
Car(0..1) toCarTransferOwnershipDocument(*). - Add an association between
CarOwnerandCarTransferOwnershipDocument. Name the multi-valued endDocumentsForSales(*) and the single-valued endSeller(0..1). - Add another association between
CarOwnerandCarTransferOwnershipDocument. Name the multi-valued endDocumentsForPurchase(*) and the single-valued endBuyer(0..1). - Add an association between
CarTransferOwnershipDocumentandCar. Name the single-valued document endCurrentTransaction. Set theCarend to not navigable.
The document records the two roles separately. For example, BMW in Munich can be the Seller, while a Person is the Buyer.
Add more owner types
- Add
CarFactoryand make it a subclass ofCarOwner. - Add
ScrapYardand make it a subclass ofCarOwner.
Add the Car state machine
A state machine defines the valid states of an object and the transitions that move it between states.
- Add a state machine to class
Car. - Add the state
BrandNew. - Add the state
InOwnershipTransaction. - Add a transition from
BrandNewtoInOwnershipTransactionand name its triggerInitiateSale. - Add the state
OwnershipStable. - Add a transition from
InOwnershipTransactiontoOwnershipStableand name its triggerCloseSale. - Add a transition from
OwnershipStabletoInOwnershipTransaction. Reuse theInitiateSaletrigger.
Use the spelling InOwnershipTransaction consistently. Earlier exercise text used InOwnerShipTransaction; do not create two differently named states.
Add actions and guards
An entry action runs when an object enters a state. A guard is an OCL expression that must evaluate as true before a transition may run. Learn the expression syntax in OCL.
Create the transaction when a sale starts
- Select
InOwnershipTransaction. - In the Property Inspector, click the ellipsis button for Entry to open the Action Editor for that state.
- Add the course-provided Action Language expression that:
- creates a
CarTransferOwnershipDocument; - adds it to the car's collection of ownership-transfer documents; and
- assigns the new document's
Sellerfrom the car's current owner.
- creates a
- Extend that entry action to assign the created document to
CurrentTransaction.
The result should be that triggering InitiateSale creates one transaction document for that car and identifies its seller.
Require a buyer before closing
- Select the
CloseSaletransition. - Open its Guard OCL editor.
- Enter:
self.CurrentTransaction.Buyer->notempty
This guard prevents the sale from closing while the current transaction document has no buyer.
Transfer current ownership and retain history
- Select
OwnershipStableand open its Entry Action Editor. - Add the course-provided Action Language expression that removes the car from the seller's
CurrentlyOwnedCarsand adds it to the buyer'sCurrentlyOwnedCars. - Extend the same entry action with the course-provided update that records the seller's
CarsOwnerUsedToOwnhistory.
For example, after a sale closes, the buyer sees the car in CurrentlyOwnedCars. The seller no longer sees it there, but the historical ownership relationship still records that the seller previously owned it.
Prevent a sale without an owner
- Select the transition from
BrandNewtoInOwnershipTransaction. - Open its Guard OCL editor.
- Enter:
self.CarOwner->notempty
This guard means a car needs a CarOwner before the initial sale transaction can begin.
Add trigger actions
- On the
InitiateSaletrigger, add a ClassAction for the trigger. - On the
CloseSaletrigger, add a ClassAction for the trigger. - Refresh the AutoForms.
- Save the model and test it in the web application.
Test the complete ownership flow
- Create a
CarFactoryobject. - Set its name to
BMW in Munich. - Add a
Carto that factory. - Trigger
InitiateSalefor the car. Verify that aCarTransferOwnershipDocumentis created. - Assign a buyer on the
CarTransferOwnershipDocument. - Trigger
CloseSale. - Verify that current ownership moved from the factory to the buyer and that historical ownership was retained.
- Set the default representation of
CarOwnerto:
self.Name
The default representation makes owner selections readable in generated forms: users see names such as BMW in Munich rather than an internal object representation.
Troubleshooting
| Symptom | Check |
|---|---|
Name is reported as a doublet
|
Remove Name from Person. It inherits Name from CarOwner.
|
| AutoForms report errors after an association rename or move | Refresh or recreate the affected AutoForms. They can still reference the removed Person-based association end.
|
CloseSale is unavailable
|
Open the current transaction and assign Buyer. The guard requires self.CurrentTransaction.Buyer->notempty.
|
InitiateSale is unavailable from BrandNew
|
Ensure the car has a CarOwner; the transition guard requires self.CarOwner->notempty.
|
Next chapter
Continue to Chapter 10.
