Chapter 2: Object-Oriented Design
2.1 Goals and Principles
2.1.1 Object-Oriented Design Goals
- Robustness: capable of
handling unexpected inputs
that are not explicitly defined for its application
- Adaptability: software
needs to evolve over time in
response to changing conditions in its environment
- Reusability: code
should
reusable as a component of different systems in various applications
2.1.2 Object-Oriented Design Principles
-
Abstraction: to distill
the complicated system down to its most fundamental parts and describe
these parts in a simple, precise language
-
Encapsulation: different
components of a software system should not reveal the internal details
of their respective implementation
-
Modularity: refers to an
organizing structure in which different components of a software system
are divided into separate functional units
2.2.1 Inheritance in C++
Inheritance allows the design of generic classes that can be
specialized to more particular classes, with the specialized classes
reusing the code from the generic class.
class Person { // base class
private:
string name; // name
string ssn; // social security number
public:
//...
void print(); // print data
string getName(); // retrieve name
};
class Student : public Person { // derived class
private:
string major; // major subject
int gradYear; // graduate year
public:
//...
void print(); // print data
void changeMajor(string newMajor); // change major
};
Member Functions
Person person(...); // define a person
Student student(...); // define a student
cout << student.getName();// invokes Person::getName()
person.print(); // invokes Person::print();
student.print(); // invokes Student::print();
person.changeMajor("Math"); // ERROR!
student.changeMajor("Math"); // OK
void Person::print() // definition of Person print
{ cout << name << " " << ssn; }
void Student::print() // definition of Student print
{ Person::print(); // first print Person data
cout << major << gradYear;
}
Illustrating Class Protection
class Base {
private:
int priv;
protected:
int prot;
public:
int publ;
};
class Derived: public Base {
void someMemberFunction()
{ cout << priv; // ERROR: private member
cout << prot; // OK
cout << publ; // OK
}
};
class Unrelated {
Base X;
void anotherMemberFunction()
{ cout << X.priv; // ERROR: private member
cout << X.prot; // ERROR: protected member
cout << X.publ; // OK
}
};
Constructors and Destructors
Person::Person(const string &nm, const string &ss)
: name(nm), ssn(ss) {} // initializer list
Student::Student(const string &nm, const string &ss,
const string &maj, int year)
: Person(nm, ss), // initialize Person data members
major(maj), // initialize member
gradYear(year) {} // initialize gradYear
Person::~Person() // Person destructor
{ ... }
Student::~Student() // Student destructor
{ ... }
Student* s = new Student(...);
//...
delete s; // calls ~Student() then ~Person()
[person.cpp]
Static Binding
Person* pp[100];
pp[0] = new Person(...);
pp[1] = new Student(...);
cout << pp[1]->getName(); // OK
pp[0]->print(); // calls Person::print()
pp[1]->print(); // calls Person::print()
pp[1]->changeMajor(...); // ERROR!
Dynamic Binding and Virtual
Functions
class Person { // base class
virtual void print(); // print data
//...
};
class Student : public Person { // derived class
virtual void print(); // print data
//...
};
Person* pp[100];
pp[0] = new Person(...);
pp[1] = new Student(...);
pp[0]->print(); // calls Person::print()
pp[1]->print(); // calls Student::print()
pp[1]->changeMajor(...); // OK
[person.cpp]
Virtual
Destructors
If a class defines any virtual functions, it should define a
virtual destructor, even if it is empty.
delete [] pp;
2.2.2 Polymorphism
The ability of a variable to take different types!
2.3.1 Function Templates
- Minimum of two integers:
int min(int a, int b)
{ return (a < b ? a : b); }
- Minimum of doubles:
double min(double a, double b)
{ return (a < b ? a : b); }
- Generic function for an arbitrary type T, called function template:
template<typename T> // parameter list
T min(T a, T b) // returns the minimum of a and b
{ return (a < b ? a : b); }
- We can invoke our template function to compute the minimum of
objects
of many different types. We could use any type, provided that the less
than operator (<) is
defined for this type. The
compiler looks at the argument types,
and determine which form of the function to instantiate:
cout << min(3, 4); // invokes min<int>(3,4)
cout << min(6.9, 3.5); // invokes min<double>(6.9, 3.5)
cout << min('t', 'g'); // invokes min<char>('t', 'g')
2.3.2 Class Templates
template <typename Object>
class BasicVector {
Object* a; // array storing an element
int capacity; // length of array a
public:
BasicVector(int cap = 10) // constructor
{ capacity = cap;
a = new Object[capacity];// allocate array storage
}
Object& elemAtRank(int r) // access element at index r
{ return a[r]; }
// ...
};
BasicVector<int> iv(5); // vector of 5 integers
BasicVector<double> dv(20); // vector of 20 doubles
BasicVector<string> sv(10); // vector of 10 strings
//...
iv.elemAtRank(3) = 8; // iv[3] = 8;
dv.elemAtRank(14) = 2.5; // dv[14] = 2.5
sv.elemAtRank(7) = "hello"; // sv[7] = "hello"
Exceptions are unexpected events that occur during the execution of a
program.
2.4.1 Exception Objects
Using Inheritance to Define New
Exception Types
class MathException { // generic math exception
private:
string errMsg; // error message
public:
MathException(const string& err) { errMsg = err; }
};
class ZeroDivisionException : public MathException {
public:
ZeroDivisionException(const string& err)
: MathException(err) {}
};
class NegativeRootException : public MathException {
public:
NegativeRootException(const string& err)
: MathException(err) {}
};
2.4.2 Throwing and Catching
Exceptions
try {
// ...
if (divisor == 0) throw ZeroDivisionException("Division by zero");
//...
}
catch (ZeroDivisionException& zde)
{ // handle division by zero
}
catch (MathException& me)
{ // handle any math exception other than division by zero
}
Once execution of the catch block completes, control flow continues
with the first statement after the last
catch block.
2.4.3 Exception Specification
void calculator() throw(ZeroDivisionException, NegativeRootException)
{
//...
}
void funct1(); // can throw any exception
void funct2() throw(); // can throw no exception
Generic Exception Class
class RuntimeException { // generic run-time exception
private:
string errorMsg;
public:
RuntimeException(const string& err) { errorMsg = err; }
string getMessage() const { return errorMsg; }
};
inline std::ostream& operator<<(std::ostream& out, const RuntimeException& e)
{ return out << e.getMessage(); }