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. Related to
this concept is portability, which is the
ability of software to run with minimal change on different
hardware and operating system platforms.
- 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. Applying the
abstraction paradigm to the design of data structures gives rise
to abstract data types (ADTs).
-
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(); }