Child pages
  • ST1 - Workshop M3: Zustandsmodellierung


Workshop M3: Zustandsmodellierung (01.02.)

In dieser Übung beschäftigen wir uns mit Zustandsmodellierung - Aufstellen eines Statusdiagramms auf der Basis eines erklärenden Textes, und dann eine Implementierung dazu. 

Als Beispiel nehmen wir Prüfungen (Exam) in unserem Campus-Management-System (CAMS). Der entsprechende Ausschnitt aus dem logischen Datenmodell sieht so aus: 

Sie sollen jetzt für das Entity Exam ein Zustandsdiagramm aufstellen und das dann implementieren. Die Beschreibung der fachlichen Abläufe ist wie folgt gegeben (auf Englisch): 

An exam is created with a title. From the moment of creation, the exam is open for registration. In that that period, students can register for taking the exam. They can also cancel their participation without any reasons. Approximately one week before the exam, a supervisor manually closes the registration period (there is no automatic mechanism). From now on until the exam start, students can neither register anymore, nor cancel their participation. 

At the time of the exam, again a supervisor manually starts the exam. The students can now upload a solution, provided that they are indeed registered for the exam. It is possible to upload multiple solutions; only the latest is stored. At the end of the exam (usually 2h), a supervisor (again manually) closes the exam. From now on, no more solutions can be uploaded by the students.

The small team of supervisors start the correction. They take an uploaded student solution, and upload a correction (as a seperate file). It is possible to upload multiple corrections; only the latest one is stored.

As soon as all student solutions have been corrected, one of the supervisors performs one last check on all corrections. If something is wrong, he marks it as "disputed". For the those corrections, the supervising team uploads new versions. The supervisor again looks over all corrections etc. This loop is repeated until the correction phase is finished, i.e. there all corrections have the "disputed" flag = FALSE.

When that state has been reached, a supervisor publishes the exam results. No more corrections can be uploaded, and the students can access their solutions including the attached corrections. After some time period, a supervisor archives the exam. From then on, no more access to solutions and corrections is possible. 

Ihre Aufgabe

  1. Stellen Sie ein Statusdiagramm für das Entity Exam auf, das den obigen Text wiedergibt. Hinweis: Sie sollten nicht mehr als sechs Zustände benötigen (ohne Initial und Final State). 
  2. Implementieren Sie das Entity Exam. Orientieren Sie sich am Text, welche Methoden Sie implementieren müssen. 
  3. Schreiben Sie Unit Tests für die folgenden Fälle (auf Englisch): 
    1. Students can register for an exam until the registration period is closed. Same for cancelling participation.
    2. Students can only cancel their participation if they have registered first.
    3. Students can upload a solution only after the exam has been opened, and before it is closed.
    4. A supervisor can only publish the results if all corrections are available, and none of it is marked "disputed".
    5. Students can access their solutions only after they have been published, and before the exam is archived.

Anmerkungen: 

  • Auch wenn hier von Entity die Rede ist, brauchen Sie die Persistenz für diese Übung nicht zu berücksichtigen. D.h. es reicht, einfache POJOs zu implementieren. (Annotation mit @Entity oder @Embeddable schadet nicht, kann aber weggelassen werden).
  • "Supervisor" ist nicht Teil des LDM. Sie brauchen also nicht nachzuhalten, wer eine Korrektur vorgenommen hat.
  • Die Klassen aus dem LDM von oben können Sie (bis auf Exam) mit den genannten Attributen implementieren; insbesondere brauchen Sie keine Punkte für die Klausur vorzusehen. 
  • Ein Exam wird einfach per Konstruktur erzeugt. Stellen Sie sicher, dass es dann im richtigen Startzustand ist. 
  • Bei Exam brauchen Sie noch weitere Member-Variablen für die Abbildung der Zustände; das ist Ihnen überlassen. 
  • Wenn Sie einen Methodenaufruf in einem invaliden Zustand haben, werfen Sie einfach eine RuntimeException.
  • In einem Unit-Test können Sie so einfach auf eine erwartete Exception prüfen: 

assertThrows( RuntimeException.class, () -> {
// ... action expected to throw an exception ...
});

Beispiellösung für Aufgabe 1

Beispiellösung zu Aufgabe 2

State-Enum

package thkoeln.st2.university.exam.domain;

public enum ExamStateType {
REGISTRATION_OPEN,
REGISTRATION_CLOSED,
STARTED,
CLOSED,
PUBLISHED,
ARCHIVED
}

Interfaces

package thkoeln.st2.university.exam.domain;

import thkoeln.st2.university.student.domain.Student;

public interface StudentActivities {
public void register( Student student );
public void cancelParticipation( Student student );
public void uploadSolution( Student student, Solution solution );
public Solution accessSolution( Student student );
}



package thkoeln.st2.university.exam.domain;

public interface SupervisorActivities {
public void closeRegistration();
public void start();
public void close();
public void uploadCorrection( Solution solution, Correction correction );
public void markDisputedCorrection( Solution solution, Boolean disputedFlag );
public boolean areCorrectionsComplete();
public boolean areCorrectionsUndisputed();
public void publish();
public void archive();
}

Exam-Entity (Auszüge)

//...
@Entity
@Setter
@Getter
@NoArgsConstructor
public class Exam extends AbstractEntity implements StudentActivities, SupervisorActivities {
private String title;
private ExamStateType state = REGISTRATION_OPEN;

@OneToMany
private final Set<Solution> solutions = new HashSet<>();

@OneToMany
private final Set<Student> students = new HashSet<>();

@Override
public void cancelParticipation( Student student ) {
if ( state() != REGISTRATION_OPEN )
throw new RuntimeException( "Illegal state for cancelParticipation: " + state() );
if ( !students.contains( student ) )
throw new RuntimeException( "Attempt to cancel for an unregistered student" );

students.remove( student );
}

@Override
public Solution accessSolution(Student student) {
if ( state() != PUBLISHED )
throw new RuntimeException( "Illegal state for accessSolution: " + state() );

// unelegant implementation ...
Iterator<Solution> iterator = solutions.iterator();
while ( iterator.hasNext() ) {
Solution currentSolution = iterator.next();
if( currentSolution.getStudent() == student ) return currentSolution;
}
// not found
return null;
}

@Override
public void closeRegistration() {
if ( state() != REGISTRATION_OPEN )
throw new RuntimeException( "Illegal state for closeRegistration: " + state() );
state = ExamStateType.REGISTRATION_CLOSED;
}

// ...
}

Beispiellösung zu Aufgabe 3

Testklasse (Auszüge)

public class ExamStatusTests {
private Solution solution1, solution2;
private Correction correction1, correction2;
private Exam exam;

private Student amelie, georg;

@BeforeEach
public void setUp() {
amelie = new Student();
amelie.setName( "Amelie" );
georg = new Student();
georg.setName( "Georg" );
exam = new Exam();
exam.setTitle( "Klausur" );
solution1 = new Solution( "solution1", null, null );
solution2 = new Solution( "solution2", null, null );
correction1 = new Correction();
correction1.setComment( "Correction for Solution1");
correction2 = new Correction();
correction2.setComment( "Correction for Solution2");
}

@Test
public void testRegistrationAndCancellation() {
exam.register( amelie );
assertTrue( exam.getStudents().contains( amelie ) );
exam.cancelParticipation( amelie );

assertThrows( RuntimeException.class, () -> {
exam.cancelParticipation( georg );
});
}

// ...
}