Autos und Personen

1. Aufgabe

Klassen können im Sinne der objekt-orientierten Modellierung miteinander in Beziehung stehen. Die einfachste Beziehung zweier Klassen ist die Assoziation. Am Beispiel zweier Klassen Person und Car betrachten wir in dieser Aufgabe die Assoziation, dass ein Car-Objekt ein oder mehrere Person-Objekte befördern kann. Beachten Sie folgende Randbedingungen in der Realisierung der beiden Klassen:

  • Objekte der Klasse Person besitzen einen Namen. Außerdem lassen sich zwei Person-Objekte auf Gleichheit vergleichen. Zu diesem Zweck besitzen sie eine Überladung des ==-Operators. Die Gleichheit der Namen ist ausschlaggebend für die Gleichheit zweier Person-Objekte

  • In einem Objekt des Typs Car können kein, ein oder mehrere Personen mitfahren. Dabei gibt es pro Auto eine maximale Grenze an Mitfahrern, die nicht überschritten werden darf. Prinzipiell kann eine Person in ein Auto ein- und aussteigen. Beim Einsteigen ist darauf zu achten, dass die gleiche Person nicht mehrmals in ein Auto einsteigt. Im echten Leben ist dies zwar kaum möglich, in der Software-Technik wollen wir mit dieser Vorgabe triviale Fehler in der technischen Realisierung ausschließen

Für die softwaretechnische Verwaltung aller Personen, die in einem Auto mitfahren, entwickeln wir eine Klasse PassengersList. Ein PassengersList-Objekt kann ein oder mehrere Person-Objekte aufnehmen. Dabei obliegt es Ihrer Entscheidung, ob das PassengersList-Objekt Zeiger oder Kopien der Person-Objekte verwaltet. Natürlich ist nach der sinnvollsten Realisierung gefragt. Folgende Code-Snippets sollten mit Ihrer Implementierung ausführbar sein:

1. Beispiel:

void main()
{
    Person anton("Anton");
    Person franz("Franz");
    Person gustl("Gustl");
    Car benz;
    benz.GetIn(anton);
    benz.GetIn(gustl);
    benz.GetIn(franz);

    cout << benz << endl;
    cout << benz.NumOfPassengers() << " passengers are in the car." << endl;
}

Ausgabe:

List of passengers:
-------------------
1: Anton
2: Gustl
3: Franz
===================

3 passengers are in the car.

2. Beispiel:

void main()
{
    Car vw;
    Person anton("Anton");
    Person franz("Franz");
    Person gustl("Gustl");

    vw.GetIn(anton);
    vw.GetIn(franz);
    cout << vw.NumOfPassengers() << " passengers are in the car." << endl;

    bool result = vw.GetOff(gustl);
    cout << gustl <<  " could leave the car: " << result << endl;

    vw.GetIn(gustl);
    cout << vw.NumOfPassengers() << " passengers are in the car." << endl;

    result = vw.GetIn(gustl);
    cout << gustl << " entered the car a second time: " << result << endl;
    cout << vw.NumOfPassengers() << " passengers are in the car." << endl;
}

Ausgabe:

2 passengers are in the car.
Gustl could leave the car: 0
3 passengers are in the car.
Gustl entered the car a second time: 0
3 passengers are in the car.

3. Beispiel:

void main()
{
    Car vw;
    Person franz("Franz");

    cout << "Is Franz in the car: " << vw.IsCarried(franz) << endl;
    vw.GetIn(franz);
    cout << "Is Franz in the car: " << vw.IsCarried(franz) << endl;
    cout << "There is(are) " << vw.NumOfPassengers()
        << " passenger(s) in the car." << endl;
    cout << endl << vw << endl;
}

Ausgabe:

Is Franz in the car: 0
Is Franz in the car: 1
There is(are) 1 passenger(s) in the car.

List of passengers:
-------------------
1: Franz
===================

4. Beispiel:

01: void main()
02: {
03:     Car  cinquecento (2);
04:     Person franz("Franz");
05:     Person susan("Susan");
06:     Person werner("Werner");
07: 
08:     cinquecento.GetIn(franz);
09:     cout << "Number of passenger(s): "  << cinquecento.NumOfPassengers() << '.' << endl;
10: 
11:     cinquecento.GetIn(susan);
12:     cout << "Number of passenger(s): " << cinquecento.NumOfPassengers() << '.' << endl;
13: 
14:     cinquecento.GetIn(werner);
15:     cout << "Number of passenger(s): " << cinquecento.NumOfPassengers() << '.' << endl;
16: 
17:     cout << "Is Susan in the car: " << cinquecento.IsCarried(susan) << endl;
18:     cout << "Is Werner in the car: " << cinquecento.IsCarried(werner) << endl;
19: }

Ausgabe:

Number of passenger(s): 1.
Number of passenger(s): 2.
Number of passenger(s): 2.
Is Susan in the car: 1
Is Werner in the car: 0

2. Lösung

Quellcode: Siehe auch github.com/peterloos/Cpp_CarsAndPersons.

In einem Auto können kein, ein oder mehrere Passagiere mitfahren. Wir sprechen in diesem Fall von einer 1:n Beziehung, bei der ein Objekt mit n anderen Objekten in Beziehung steht. Die Beziehung von einem Auto und seinen Insassen ist ein typisches Beispiel hierfür. Da ein Auto zu bestimmten Zeitpunkten keinen Insassen haben kann, wurde in Abbildung 1 die Angabe 0..n verwendet. 1:n Beziehungen lassen sich software-technisch vergleichsweise einfach mit Container-Objekten realisieren. Diese gibt es zum einen in der C++-Standardklassenbibliothek vorgefertigt, zum anderen lassen sich derartige Klassen auch vergleichsweise einfach mit eigenen Mitteln erstellen.

In der nachfolgenden Musterlösung finden Sie gleich zwei Vorschläge vor: Zum einen in Gestalt einer Klasse PassengersList, die eine speziell auf Person-Objekte zugeschnittene Lösung darstellt. Mit Hilfe von Schablonen (Templates) erhält man Container-Objekte, die Objekte beliebigen Typs verwalten können – natürlich auch Person-Objekte.

1:n-Beziehung zwischen Autos und Insassen.

Abbildung 1. Eine 1:n-Beziehung zwischen Autos und Insassen.


Die Implementierung der Person-Klasse ist sehr einfach, da vom Namen abgesehen so gut wie keine Funktionalität von dieser Klasse gefordert ist. Da im Instanzdatenbereich Zeiger vorhanden sind – und bei der Konstruktion des Objekts auch dynamisch Daten allokiert werden – sind der Kopier-Konstruktor und der Wertzuweisungsoperator Pflichtteil in der Implementierung. Auch unter dem Einwand, dass im konkret vorliegenden Anwendungsfall beide Methoden nicht zum Einsatz kommen. In einer anderen Anwendung könnte dies ja ganz anders aussehen:

01: class Person
02: {
03: private:
04:     char* m_name;
05: 
06: public:
07:     // c'tor / d'tor
08:     Person();
09:     Person(char* name);
10:     Person(const Person& person);
11:     ~Person();
12: 
13:     // comparison operators
14:     friend bool operator== (const Person&, const Person&);
15:     friend bool operator!= (const Person&, const Person&);
16: 
17:     // output operator
18:     friend ostream& operator<< (ostream&, const Person&);
19: 
20:     // assignment operator
21:     Person& operator = (const Person&);
22: };

Beispiel 1. Klasse Person: Schnittstelle.


01: #include <string.h>
02: #include <iostream>
03: using namespace std;
04: 
05: #include "Person.h"
06: 
07: Person::Person()
08: {
09:     m_name = (char*) 0;
10: }
11: 
12: Person::Person(char* name)
13: {
14:     int len = strlen(name);
15:     m_name = new char[len + 1];
16:     strcpy_s(m_name, len + 1, name);
17: }
18: 
19: Person::Person(const Person& person)
20: {
21:     int len = strlen(person.m_name);
22:     m_name = new char[len + 1];
23:     strcpy_s(m_name, len + 1, person.m_name);
24: }
25: 
26: 
27: Person::~Person()
28: {
29:     delete[] m_name;
30: }
31: 
32: bool operator== (const Person& p1, const Person& p2)
33: {
34:     return (strcmp(p1.m_name, p2.m_name) == 0);
35: }
36: 
37: bool operator!= (const Person& p1, const Person& p2)
38: {
39:     return !(p1 == p2);
40: }
41: 
42: ostream& operator<< (ostream& os, const Person& person)
43: {
44:     os << person.m_name;
45:     return os;
46: }
47: 
48: // assignment operator
49: Person& Person::operator= (const Person& person)
50: {
51:     if (this != &person)
52:     {
53:         delete[] m_name;
54: 
55:         int len = strlen(person.m_name);
56:         m_name = new char[len + 1];
57:         strcpy_s(m_name, len + 1, person.m_name);
58:     }
59: 
60:     return *this;
61: }

Beispiel 2. Klasse Person: Implementierung.


Für das Auto kommt nun folgender Vorschlag ins Spiel:

01: class Car
02: {
03: private:
04:     static const int DefaultNumPassengers = 5;
05: 
06:     PassengersList m_Passengers;
07:     int m_MaxNumPassengers;
08: 
09: public:
10:     Car();
11:     Car(int maxNumPassengers);
12: 
13:     bool GetIn(const Person& person);
14:     bool GetOff(const Person& person);
15:     int NumOfPassengers() const;
16:     bool IsCarried(const Person& person);
17: 
18:     friend ostream& operator<< (ostream&, const Car&);
19: };

Beispiel 3. Klasse Car: Schnittstelle.


01: #include <iostream>
02: using namespace std;
03: 
04: #include "Person.h"
05: #include "PassengersList.h"
06: #include "Car.h"
07: 
08: Car::Car() : m_Passengers(DefaultNumPassengers)
09: {
10:     m_MaxNumPassengers = DefaultNumPassengers;
11: }
12: 
13: Car::Car(int maxNumPassengers) : m_Passengers (maxNumPassengers)
14: {
15:     m_MaxNumPassengers = maxNumPassengers;
16: }
17: 
18: bool Car::GetIn(const Person& person)
19: {
20:     if (m_Passengers.Count() == m_MaxNumPassengers)
21:         return false;
22: 
23:     if (m_Passengers.Contains(&person))
24:         return false;
25: 
26:     m_Passengers.Add(&person);
27:     return true;
28: }
29: 
30: bool Car::GetOff(const Person& person)
31: {
32:     if (! m_Passengers.Contains(&person))
33:         return false;
34: 
35:     m_Passengers.Remove(&person);
36:     return true;
37: }
38: 
39: int Car::NumOfPassengers() const
40: {
41:     return m_Passengers.Count();
42: }
43: 
44: bool Car::IsCarried(const Person& person)
45: {
46:     return m_Passengers.Contains(&person) ? true : false;
47: }
48: 
49: ostream& operator<< (ostream& os, const Car& car)
50: {
51:     os << "List of passengers:" << endl;
52:     os << "-------------------" << endl;
53:     os << car.m_Passengers;
54:     os << "===================" << endl;
55: 
56:     return os;
57: }

Beispiel 4. Klasse Car: Implementierung.


Für die Insassenliste gibt es mehrere Möglichkeiten einer Realisierung. Die vermutlichste einfachste – von den vorgefertigten Standardklassen einmal abgesehen – finden Sie in Listing 5 und Listing 6 in Form einer Container-Klasse PassengersList vor, die eine vorgegebene Anzahl von Person-Elementen verwalten kann. Damit sind wir in der Lage, für Fahrzeuge beliebigen Typs eine individuelle, unterschiedlich große Passagierliste zu erzeugen, was genau unserem Anforderungsprofil entspricht. Alles weitere siehe Listing 5 und Listing 6:

01: class PassengersList
02: {
03: private:
04:     static const int DefaultListLength = 4;
05: 
06: private:
07:     Person const ** m_elements;
08:     int m_length;
09:     int m_count;
10: 
11: public:
12:     // c'tor/d'tor
13:     PassengersList();
14:     PassengersList(int length);
15:     ~PassengersList();
16: 
17:     // public interface
18:     bool Add(Person const* elem);
19:     bool Remove(Person const* elem);
20:     bool Contains(const Person* elem);
21:     bool IsEmpty() const;
22:     int Count() const;
23: 
24:     // output operator
25:     friend ostream& operator<< (ostream&, const PassengersList&);
26: };

Beispiel 5. Klasse PassengersList: Schnittstelle.


01: #include <string.h>
02: #include <iostream>
03: using namespace std;
04: 
05: #include "Person.h"
06: #include "PassengersList.h"
07: 
08: PassengersList::PassengersList()
09: {
10:     m_elements = new const Person*[DefaultListLength];
11:     m_length = DefaultListLength;
12:     m_count = 0;
13: }
14: 
15: PassengersList::PassengersList(int length)
16: {
17:     m_elements = new const Person*[length];
18:     m_length = length;
19:     m_count = 0;
20: }
21: 
22: PassengersList::~PassengersList()
23: {
24:     delete[] m_elements;
25: };
26: 
27: bool PassengersList::Add(Person const* elem)
28: {
29:     if (m_count == m_length)
30:         return false;
31: 
32:     m_elements[m_count] = elem;
33:     m_count++;
34:     return true;
35: }
36: 
37: bool PassengersList::Remove(Person const* elem)
38: {
39:     // search slot
40:     for (int i = 0; i < m_count; i++)
41:     {
42:         // compare objects, not pointer
43:         if (*m_elements[i] == *elem)
44:         {
45:             // found slot - copy last element into this slot
46:             m_elements[i] = m_elements[m_count - 1];
47:             m_count--;
48:             return true;
49:         }
50:     }
51: 
52:     // element not found
53:     return false;
54: }
55: 
56: bool PassengersList::Contains(const Person* elem)
57: {
58:     // search slot
59:     for (int i = 0; i < m_count; i++)
60:     {
61:         // compare objects, not pointers
62:         if (*m_elements[i] == *elem)
63:         {
64:             return true;
65:         }
66:     }
67: 
68:     // element not found
69:     return false;
70: }
71: 
72: bool PassengersList::IsEmpty() const
73: {
74:     return (m_count == 0);
75: }
76: 
77: int PassengersList::Count() const
78: {
79:     return m_count;
80: }
81: 
82: ostream& operator<< (ostream& os, const PassengersList& list)
83: {
84:     for (int i = 0; i < list.m_count; i++)
85:     {
86:         os << *list.m_elements[i] << endl;
87:     }
88: 
89:     return os;
90: }

Beispiel 6. Klasse PassengersList: Implementierung.


Wenn wir die Klasse PassengersList aus Listing 5 und Listing 6 in unseren Car-Objekten verwenden wollen, müssen wir die Konstruktoren der Car-Klasse genauer betrachten. Mit Hilfe des C++-Sprachfeatures der so genannten memberwise initialization list lässt sich ein PassengersList-Unterobjekt eines Car-Objekts individuell mit einer Längenangabe ausstatten:

Car::Car() : m_Passengers(DefaultNumPassengers)
{
    m_MaxNumPassengers = DefaultNumPassengers;
}

oder auch

Car::Car(int maxNumPassengers) : m_Passengers (maxNumPassengers)
{
    m_MaxNumPassengers = maxNumPassengers;
}

Zum Abschluss lassen wir den C++-Themenbereich der „Schablonen“ nicht außer Acht. Container-Klassen verlangen geradezu immer nach Schablonen. In Listing 7 finden Sie die generalisierte Version einer Klasse List<> vor, die nicht nur Person-Objekte, sondern Objekte eines beliebigen Typs aufnehmen kann:

001: template <class T>
002: class List
003: {
004: private:
005:     static const int DefaultListLength = 4;
006: 
007: private:
008:     T const **  m_elements;
009:     int m_length;
010:     int m_count;
011: 
012: public:
013:     List();
014:     List(int length);
015:     ~List();
016: 
017:     bool Add(T const* elem);
018:     bool Remove(T const* elem);
019:     bool Contains(T const* elem);
020:     bool IsEmpty() const;
021:     int Count() const;
022: 
023:     friend ostream& operator<< <>(ostream&, const List&);
024: };
025: 
026: template <class T>
027: List<T>::List()
028: {
029:     m_elements = new Person*[DefaultListLength];
030:     m_length = DefaultListLength;
031:     m_count = 0;
032: }
033: 
034: template <class T>
035: List<T>::List(int length)
036: {
037:     m_elements = new const Person*[length];
038:     m_length = length;
039:     m_count = 0;
040: }
041: 
042: 
043: template <class T>
044: List<T>::~List()
045: {
046:     delete[] m_elements;
047: };
048: 
049: template <class T>
050: bool List<T>::Add(T const* elem)
051: {
052:     if (m_count == m_length)
053:         return false;
054: 
055:     m_elements[m_count] = elem;
056:     m_count++;
057:     return true;
058: }
059: 
060: template <class T>
061: bool List<T>::Remove(T const* elem)
062: {
063:     // search slot
064:     for (int i = 0; i < m_count; i++)
065:     {
066:         // compare objects, not pointer
067:         if (*m_elements[i] == *elem)
068:         {
069:             // found slot - copy last element into this slot
070:             m_elements[i] = m_elements[m_count - 1];
071:             m_count--;
072:             return true;
073:         }
074:     }
075: 
076:     // element not found
077:     return false;
078: }
079: 
080: template <class T>
081: bool List<T>::Contains(T const* elem)
082: {
083:     // search slot
084:     for (int i = 0; i < m_count; i++)
085:     {
086:         // compare objects, not pointers
087:         if (*m_elements[i] == *elem)
088:         {
089:             return true;
090:         }
091:     }
092: 
093:     // element not found
094:     return false;
095: }
096: 
097: template <class T>
098: bool List<T>::IsEmpty() const
099: {
100:     return (m_count == 0);
101: }
102: 
103: template <class T>
104: int List<T>::Count() const
105: {
106:     return m_count;
107: }
108: 
109: template <class T>
110: ostream& operator<< (ostream& os, const List<T>&  list)
111: {
112:     for (int i = 0; i < list.m_count; i++)
113:     {
114:         os << *list.m_elements[i] << endl;
115:     }
116: 
117:     return os;
118: }

Beispiel 7. Template (Schablone) List<>.


Entscheidet man sich in der Realisierung der Car-Klasse für den Gebrauch der List<>-Schablone, so ist in Listing 3 einzig und allein die die sechste Zeile

PassengersList m_Passengers;

abzuändern in

List<Person> m_Passengers;