Software Engineering for Smart Data Analytics & Smart Data Analytics for Software Engineering

User Tools

Site Tools

Assignment 5

Task 17: (exam style task)

Of Patterns and Images

Consider the following instance of the proxy pattern representing the structure of images in a website:

Correction: The load method should not be part of the class Image. There is just one load method needed, that could be placed in any of the two classes or could even be a static creation method in the real image. No client should need to know about this method.

  1. Draw a sequence diagram showing a website w making two calls to the method Image.draw(): The website w contains two embedded images i1 and i2, where the browser already successfully loaded i1, but i2 could not be loaded yet.
  2. Transform the classes including the aggregation association, the inheritance relation as well as the class' methods to Java source code. Do not specify the internal details of the draw() method.

Task 18:

Breaking cycles

Take a look at the cyclic dependency expressed in the following UML class diagram:

Can you think of a way to break this cycle without loosing any functionality?

Hint: there is a well-known design pattern that is typically used in situations like this.

Task 19:

The Art of States

Back to our coffee machine. In the last assignment, we introduced a simple framework representing the boundaries and entities of the system. In this assignment, we want to actually implement the use case as exactly as possible. How do we achieve this?

We already mentioned that the control class is responsible for actually “running” a use case. This class defines how the system reacts to events (e.g. user actions, time passage, …) from the outside.

In most non-trivial systems, the same event may trigger different reactions depending on what state it is currently in. On the other hand, certain events also may trigger transitions between different states. And once we are reasoning about states and transitions between them, we certainly should take advantage of UML State Chart Diagrams.

We prepared a state chart diagram that already captures most of the BuyDrink use case.

Hint: It is a good Idea to solve this task in parallel with Task 20 - most of the what is missing in the diagrams is already implemented in the source code we prepared for you and vice versa.

State Machine “Buy Drink”

Sub State Machine “Process Order (Paper Cup)“

Sub State Machine “Pay Cash”

Task 20: (practical Task)

Now that we know exactly how the CM2K should react to events depending on its state, we need a way to map this knowledge into code.

To get you started, we already prepared part of the mapping for you.

  • Check out the project A5T20_CoffeeMaker from your own team repository.
  • Run the class control.SwingCoffeeMakerApp
  • Walk through the original scenarios from task 6. You will find that a couple of things are still missing. It's your task to complete the implementation.

The following paragraph sheds some light on our particular implementation. You probably should read it to better understand how to update the code. The remaining two paragraphs contain additional motivation for our approach, as well as some reflection about what we did so far - and more importantly why we did it. They are not essential for this task.

Notes on the implementation

We use a single control class BuyDrinkControl to take care of the whole use case. However, we factor out the behaviour associated to individual states into separate classes, to improve readability and maintainability.

We employ a variant of what is known as the State Pattern. Applied to our system, it looks like this:

The central idea is this: BuyDrinkControl delegates the state-dependend processing of messages to an object of type ControlState that represents its current state. For (nearly) each state in the state chart diagram, we have a subtype of ControlState.

Note that in our particular variant of the pattern, the concrete states decide what transitions are to occur in response to some event. This is determined by the return value of the delegatee methods. There also exist other variants of the same pattern, where the context class, i.e. BuyDrinkControl, is responsible for this.

Why are we using the State Pattern?

Implementing a state machine is really straight forward, even with plain old procedural-style programming: Use a global variable to represent the state and than apply lots of SWITCH or IF-THEN-ELSE constructs to implement the state-dependent behaviour - voilà, there's your state machine. Not very OO, though.

What we do not like about this approach, is that the connection between the source code and our model is obscured to some degree: The code realizes the behaviour associated to a given state is scattered all over the place. And vice versa:the realization of behaviour associated to many different states is tangled within each single method.

We think that the state pattern solves this problem in a rather elegant way.

Traceability? Should we care about this?

That's right, traceability. Now what is that all about? Let's take a look on the artefacts we created so far:

  • We started of with a couple of scenarios and a Domain Object Model.
  • The scenarios are instances of a use case. We have defined a model of this use case.
  • We refined our DOM, adding missing boundaries and controls. The result is also refered to as Analysis Model.
  • We have a state chart diagram defining the behaviour of a control object that complies with this use case.
  • We created a couple of solution classes that are - more or less - corresponding to the entities and boundaries in our DOM.
  • As for the controls, we found a particular pattern of design in the solution domain that allows us to associate solution classes (and their respective source code) to states in our state machine model.

See how all the pieces neatly fit together? At least they should. Ideally, we should be able to trace back any change in the code to some change in the requirements model - some change in a use case, some scenario being added or removed, stuff like this. On the other hand, if we change something in the model, we want to know exactly what parts of the code are affected.

Why is traceability this desirable? Because change is the only constant, and we need to deal with it efficiently. Let's see an example.

In its current implementation, the system does not handle credit card payment yet. So we have at least one scenario that is not covered correctly. What is the problem? We check the use case - it coveres the respective scenarios allright. Next we check the state chart diagram - the respective events are correctly handled there, too. So it must be the implementation that does not adhere to the specification.

How do we find the code that needs fixing? Simple. We use the state chart diagram as a map and walk along the “path” induced by the events in the scenario. The state you arrive in when the error occurs, is the one you need to worry about. In our case, it's “wait for money” - it does not correctly handle the “insert card” event. This is what we need to find and fix in the code. Since in our case, the mapping of states to code is obvious, finding should be a matter of seconds, and fixing one of minutes.

Another example: The customer found some error in the requirements specification which requires us to change the use case model. Let's say some scenario is added. We know how to update the use case model. Next we have to run the scenario through our state machine and probably need to make some changes there to make sure it is handled correctly. Finally, we have adapt to our implementation to reflect the changes in the model. Traceability ensures that all these steps can be done with minimal effort and risk of breaking something.

There are many more examples. Yet another important one will probably be addressed in the next assignment: Unit Testing. Imagine having unit tests for all of your scenarios. Now you change something, propagate the change through your model down into the code. You run your tests. A few of them fail. No big deal: you know exactly which scenarios are affected. Walk through the model once more, fix the problem, rerun the tests, and so further.

It may sound cumbersome and time-consuming. It is. The claim we make is that it is even more time consuming to ignore traceability. The actual level of detail in your models is subject to discussion - many modern approaches suggest more light-weight models. As almost anything, this depends very much on the concrete problem at hand.

teaching/lectures/oosc/2008/exercises/assignment5.txt · Last modified: 2018/05/09 01:59 (external edit)

SEWiki, © 2020