Abschließendes Übungsblatt Dieses Übungsblatt wurde von Nora Wester im Rahmen des Praktikums in der Lehre erstellt. Die Übungsaufgaben orientieren sich an den existierenden Übungsblättern und wurden ohne Kenntnisse über die Klausur entworfen. Aufgabe 1 Wie sind die CRC-Karten richtig erstellt, wenn davon auszugehen ist, dass Klasse B 1) eine Erweiterung der Klasse A ist? 2) ein Attribut der Klasse A ist? a ) A b ) B B B A c ) A B Antwort: 1) c) 2) b) A A B Aufgabe 2 Bewerten Sie die nachfolgenden Anforderungen bezüglich ihrer Aussagekraft in Hinblick auf deren Sicherstellung. Wie könnte man die Anforderungen ggf. umformulieren, damit sie sichergestellt werden können. a) Die Suchmaschine darf bei bestimmten, in einer Datenbank festgehaltenen Eingaben, kein Suchergebnis liefern. Werden nur gesperrte Worte eingegeben, wird unter der Eingabezeile eine vereinbarte Meldung eingeblendet. Sind bei der Eingabe auch erlaubte Worte dabei, werden nur die Ergebnisse für diese Worte ausgegeben und die gesperrten ignoriert. b) Der Algorithmus muss so effizient wie möglich sein, darf aber mit den gegebenen Variablen nur maximal 10 Sekunden zum Ausrechnen benötigen. Antwort: a) Hier kann klar sichergestellt werden, dass die Anforderung erfüllt wird. Die Datenbank wird überprüft und die Einzelwörter in der Suchmaschine getestet. Es ist auch klar geregelt, wie die Anwendung verfahren soll, wenn ein gesperrtes Wort eingegeben wird. Dieses Verhalten kann beim Testen mitberücksichtigt werden. Somit muss die Anforderung nicht umformuliert werden. b) Hier ist nicht festgelegt, unter welchen Hardware-Komponenten die angegebene Zeit erreicht werden muss. Auch die Aussage "so effizient wie möglich" ist nicht eindeutig definiert. Somit kann man nicht sicherstellen, dass man genau die Performance, unter den für den Kunden relevanten Bedingungen, erfüllen kann. Eine Umformulierung könnte zum Beispiel so aussehen, dass eine mindest Worst Case Komplexität genannt wird. Alternativ wäre es auch möglich die konkreten HardwareKomponenten, oder einen speziellen Rechner anzugeben, unter denen man die Anforderung sicherstellt. Damit wäre aber der Einsatz des Algorithmus (mit Effizienzvoraussetzungen) unter Umständen sehr eingeschränkt gewährleistbar. Aufgabe 3 Erstellen Sie zu der beschriebenen Anwendung ein Use Case Diagram und ein Domainmodel. Online-Nachhilfe-Portal: Im Online-Nachhilfe-Portal treffen sich Leute, um zusammen zu lernen. Dabei kann man als Mitglied entweder die Rolle des Lehrenden oder des Lernenden inne haben. Jedes Mitglied hat eine Liste, in der er seine Chatrooms einsehen und betreten kann. Außerdem können sich die Mitglieder untereinander durch den systemeigenen Messenger kontaktieren. Jeder Lehrende hat von Anfang an ein Standard-Profil, das er durch Editieren mit seinen Angaben füllen kann. In diesem Profil ist vermerkt, wie viele Lernende er noch betreuen möchte, welchen Abschluss er besitzt und ob er Erfahrung als Nachhilfelehrer hat. Für jedes Fach, das er unterrichtet, gibt es einen eigenen Eintrag in seinem Profil, in dem das Fach, die Klassenstufe und die Höhe der Entlohnung angegeben ist. Diese Einträge können ebenfalls editiert werden. Möchte er ein neues Fach lehren, kann er direkt im Profil einen neuen Eintrag erstellen, möchte er ein bestehendes Fach nicht mehr anbieten, muss er den Eintrag löschen. Sucht ein Lernender einen Lehrenden, so schaut er sich die existierende Profile an. Hat er jemand gefunden, mit dem er zusammenarbeiten möchte, schickt er ihm eine Anfrage mit Angabe von Fach, Klassenstufe und eine kurze Schilderung seiner Wissenslücken. Der Lehrende entscheidet dann anhand der Anfrage, ob er diesen Lernenden unterrichten möchte. Nimmt er ihn an, wird vom System ein Chatroom mit Videotelefonie generiert, der dann in die Listen von Lehrendem und Lernendem eingetragen wird. Außerdem wird eine Nachricht an den Lernenden geschickt. Die Anfrage wird gespeichert und ist für den Lehrenden jeder Zeit einsehbar. Lehnt er ihn ab, so wird vom System nur eine entsprechende Nachricht an den Lernenden gesendet. Ein Ende der Zusammenarbeit kann dem System nur durch den Lehrenden mitgeteilt werden, indem er die entsprechende Anfrage auswählt und löscht. Daraufhin löscht das System den Chatroom und sendet dem Lernenden eine entsprechende Nachricht. Lösung: Use Case Diagram: Domainmodel: Aufgabe 4 Erläutern Sie kurz die Unterschiede zwischen Statement-, Branch- und Path-Coverage. Bei welchem Kriterium kann es unter Umständen unendlich viele Testfälle geben, um vollständiges Coverage zu erreichen und warum? Antwort: Beim Statement-Coverage geht es darum jede Zeile Code mindestens einmal durchlaufen zu haben. Wie man zu den Codezeilen gelangt, ist hier grundsätzlich irrelevant. Beim Branch-Coverage hingegen geht es darum, dass jeder mögliche Zweig bei jeder Bedingung einmal durchlaufen wird. Hierbei werden auch die Zweige getestet, die z.B. in eine Exception führen. Bei Path-Coverage geht man noch einen Schritt weiter als bei Branch-Coverage und verlangt jeglichen möglichen Pfad durch den Quellcode mindestens einmal durchlaufen zu haben. Schleifen und Rekursion kann zu unendlich vielen Pfaden führen und somit ist Path-Coverage in der Regel nicht zu 100 % erreichbar. In der Praxis wird deshalb häufig die Anzahl der Iterationen begrenzt. Aufgabe 5 Schreiben Sie für die Methode tester(int[], int, boolean, int) die minimalen Testcases für BranchCoverage, Decision-Coverage und Condition-Coverage. Geben Sie auch die Ergebnisse an und für Branch-Coverage den Kontrollflußgraphen. protected int tester(int[] array, int value, boolean var, int position){ int temp = array[position]; if(temp == value){ return 0; }else{ if(var && temp/2 == value) return 2; } return -1; } Lösung: Branch-Coverage: array.length <= position || position < 0 N1: int temp = array[position]; N2: throw ArrayIndexOutOfBoundsException N3: if(temp == value) false true N4: return 0; N5: if(var && temp/2 == value) true N6: return 2; false N7: return -1; tester ( new int[0], 0, true, 1) -> ArrayIndexOutOfBoundsException tester ( new int[]{0,1,2}, 1, true, 1) -> 0 tester ( new int[]{0,1,2}, 1, true, 2) -> 2 tester ( new int[]{0,1,2}, 1, false, 2) -> -1 Decision-Coverage: tester ( new int[]{0,1,2}, 1, true, 1) -> 0 tester ( new int[]{0,1,2}, 1, true, 2) -> 2 tester ( new int[]{0,1,2}, 1, false, 2) -> -1 Condition-Coverage: tester ( new int[]{0,1,2}, 1, true, 1) -> 0 tester ( new int[]{0,1,2}, 1, true, 2) -> 2 tester ( new int[]{0,1,2}, 1, false, 2) -> -1 tester ( new int[]{0,4}, 1, true, 1) -> -1 Aufgabe 6 a) Ermitteln Sie die Kopplung und Kohäsion mit Hilfe der LCOM-Metrik der Klasse MyIntArrayFrom<T> und beziehen Sie die statische Methode mit ein. b) Bewerten Sie die Klasse MyIntArrayFrom<T> nach ihrer Qualität und beschreiben Sie wie man sie ggf. verbessern kann. c) Welche Patterns erkennen Sie in der Klasse MyIntArrayFrom<T>? Bestimmen Sie welchen Bestandteil des Patterns die Klasse und ihre Methoden einnehmen. d) Erweitern Sie die Klasse MyIntArrayFrom<T>, so dass angemeldete Klassen über Änderungen der enthaltenen Werte informiert werden. Benutzen Sie dazu ein Ihnen bekanntes Pattern. public abstract class MyIntArrayFrom<T> { public int[] values; public MyIntArrayFrom(T... initalElements) { values = new int[]{}; for(T t : initalElements) ab(t); } public int[] getValues() { return values; } public void setValues(int[] values) { this.values = values; } /** * * @param position * @return the value of values at the specified position */ public int xy(int position){ return values[position]; } /** * * @return the length of values */ public int getLength(){ return values.length; } /** * adds the value at the end of values * @param value */ public void ab(T value){ int[] temp = new int[values.length+1]; System.arraycopy(values, 0, temp, 0, values.length); temp[temp.length-1] = convertToInt(value); values = temp; } public abstract int convertToInt(T value); /** * sorts values with help of the given comperator MySort, which is used to * compare two values * @param sort */ public void sort(MySort sort){ values = sort.sort(values); } public static void main(String[] args) { MyIntArrayFrom<String> array = new MyIntArrayFrom<String>("4"){ @Override public int convertToInt(String value) { return Integer.parseInt(value); } }; array.ab("2"); for (int i = 0; i < array.getLength(); i++) { System.out.println(array.xy(i)); } } } Lösung: a) Kopplung: java.lang.System java.lang.Object java.lang.Integer java.lang.String MySort java.lang.Override java.io.PrintStream (Wegen System.out) Kohäsion: Methoden Instanz-Variabel MyIntArrayFrom(...) values setValues(...) values getValues() values xy(...) values getLength() values ab(...) values convertToInt(...) sort(...) main(...) values LCOM = 3 von 9 Da der LCOM erhöht ist, ist die Kohäsion eher niedrig. Dies liegt aber zum Teil daran, dass die zweite Methodenmenge allein durch die Auskopplung von convertToInt(...) aus ab(...) zustande kommt. Auskopplungen von internen Berechnungen werden von der Metrik nicht erfasst und stellen einen Schwachpunkt der Metrik dar. Die Main-Methode repräsentiert in der Tat eine dritte Funktionalität, welche als Beispielhafte Verwendung der Klasse beschrieben werden könnte. b) Die Klassenvariable values sollte private gehalten werden. Auch die Methode convertToInt(T) wäre eher als protected zu deklarieren, da sie einen Teil der internen Berechnung darstellt. Einige Methodennamen sind nicht sehr vielsagend. So sollte man xy(int) umbenennen in getValueAt(int) und ab(T) in add(T). Der Klassenname selbst sollte, wie in Java üblich, in AbstractMyIntArrayFrom umgeändert werden. Die get- und vor allem set-Methode sind hier unpassend. Die set-Methode sollte nicht vorgesehen sein, da die Klasse ein im Konstruktor erstelltes Array verwaltet, das nicht durch ein x-beliebiges anderes oder durch null ersetzt werden darf. Denn dies würde zu unbeabsichtigten Fehlern in anderen Methoden führen. Die get-Methode enthält die Gefahr, dass das echte Array zurückgegeben wird und es damit von anderen Klassen ohne Kenntnisnahme der Klasse modifiziert werden kann. Somit könnte z.B. die Sortierung verlorengehen. Die Klasse hätte ohne main(String[] args) nur eine Responsibility, was man daran erkennt, dass z.B. die Verantwortlichkeit des Sortierens getrennt gehalten wird. Deswegen sollte man die mainMethode in eine eigene Klasse extrahieren. Die Methoden sind nicht zu groß oder unübersichtlich. c) Strategy-Pattern: MyIntArrayFrom<T> übernimmt hier die Rolle des Contexts und MySort ist die Strategie verwendet in der Methode sort(...). Template Method Pattern: MyIntArrayFrom<T> übernimmt hier die Rolle der AbstractClass mit ab(T) als templateMethod und convertToInt(T) als hook-Method. d) public abstract class AbstractMyIntArrayFrom<T> extends AbstractObservableIntArray{ private int[] values; public AbstractMyIntArrayFrom(T... initalElements) { values = new int[]{}; for(T t : initalElements) add(t); } /** * * @param position * @return the value of values at the specified position */ public int getValueAt(int position){ return values[position]; } /** * * @return the length of values */ public int getLength(){ return values.length; } /** * adds the value at the end of values * @param value */ public void add(T value){ int[] temp = new int[values.length+1]; System.arraycopy(values, 0, temp, 0, values.length); temp[temp.length-1] = convertToInt(value); int[] oldArray = values; values = temp; fireArrayChanged(new IntArrayEvent (this, oldArray, temp, oldArray.length-1, temp.length-1)); } protected abstract int convertToInt(T value); /** * sorts values with help of the given sorter MySort, which is * used to sort an int array. Different sorting algorithms are realized * in classes which implement interface MySort. * @param sort */ public void sort(MySort sort){ int[] temp = values; values = sort.sort(temp); fireArrayChanged(new IntArrayEvent(this, temp, values, 0, temp.length-1)); } } import java.util.LinkedList; import java.util.List; public abstract class AbstractObservableIntArray { private List<IIntArrayListener> listener = new LinkedList<IIntArrayListener>(); public void addIIntArrayListener(IIntArrayListener l){ if(l != null) listener.add(l); } public void removeIIntArrayListener(IIntArrayListener l){ if(l != null) listener.remove(l); } protected void fireArrayChanged(IntArrayEvent e){ IIntArrayListener[] listeners = listener.toArray( new IIntArrayListener[listener.size()]); for(IIntArrayListener l : listeners) l.arrayChanged(e); } } import java.util.EventListener; public interface IIntArrayListener extends EventListener { public void arrayChanged(IntArrayEvent e); } import java.util.EventObject; public class IntArrayEvent extends EventObject{ private private private private int[] newArray; int[] oldArray; int start; int end; public IntArrayEvent(AbstractObservableIntArray int[] newArray, int start, int end) { super(source); this.newArray = newArray; this.oldArray = oldArray; this.start = start; this.end = end; } public int[] getNewArray() { return newArray; } public int[] getOldArray() { return oldArray; } public int getStart() { return start; } public int getEnd() { return end; } } source, int[] oldArray,