CIS-255 Home http://www.c-jump.com/bcc/c255c/c255syllabus.htm
What are the goals of OO software design?
Who needs it?
How does OO design try to achieve its goals?
How well could anyone achieve those goals?
Maintainability
Extensibility
Reuse
Postulate: The key to all of these is to design using loosely coupled modules.
Designers must focus on how pieces of their designs are coupled.
Encapsulation
class Employee { std::string m_name; };//class Employee
Inheritance
class Manager : public Employee
Simple use (as client)
Account acct1; acct1.deposit( 1000000 );
Template instantiation:
std::map< std::string, int > phone_directory;
Separate interface from implementation.
Keep implementations private.
Provide well balanced interface - simple, yet complete.
Use inheritance to provide a layer of abstraction between modules.
Carefully maintain invariant states.
There are common idioms in any design methodology.
Learning a design methodology is mostly learning these idioms and when to use them.
The same can be said of a programming language.
A good exposition of OO design patterns is Design Patterns by the Gang of Four.
Iterators
Abstract factory functions
Mix-ins (a.k.a. Decorators )
One of the most important forms of coupling involves the process of object creation.
Creation is also somewhat more difficult to do abstractly, since...
...there's no such thing as a virtual constructor.
Explanation: A constructor cannot be virtual, because
at the time when the constructor is invoked the virtual table would
not be available in the memory.
Hence we cannot have a virtual constructor.
There are two creation design patterns to consider:
A prototype pattern , which deals with the best way to create objects, and
A singleton pattern , to restrict instantiation of a class to one object.
Problem:
Need a single instance of some class for use by many modules (similar to a global variable)
Lifetime of this instance is for the entire program.
May want to extend this by subclassing later.
Want to do that without changing the client code.
Solution:
use static members.
|
|
Additional problems to consider:
We want to be able to substitute new kinds of singleton without changing client code.
Clients will expect the same interface.
Possibility of deciding what kind of singleton to create based on external specification,
such as environment variable.
Solution:
use inheritance and registration.
|
|
Problem:
A class has a large amount of dynamically allocated data.
We do not want copies of such objects being made, because:
there is no reason to have duplicate copies to begin with;
making copies would be a performance issue,
and a memory issue.
Solution:
Prevent copying by declaring copy constructor and assignment private.
However, the class itself could still access private class members!
Therefore,
declare them as member functions, but do not define them;
provide only function prototypes, but do not define the actual code.
In attempt to copy objects within the class,
the compile process succeeds, but
the linker will fail to find the code it needs, resulting in
unresolved external symbol error.
Since our reasons might not be immediately obvious to the user,
each private member needs a short sentence of explaination:
class Example { public: // constructor explicit Example(); // destructor ~Example(); private: // Copy constructor // is declared private so interface cannot use it; // not defined so implementation cannot use it: explicit Example( Example const& original ); // Assignment operator // is declared private so interface cannot use it; // not defined so implementation cannot use it: Example& operator=( Example const& original ); };//class Example
Problem:
We have objects derived from some common base.
We need a clone operation for creating new objects of the same type without knowing their real type.
Solution:
We need something that works like a virtual copy constructor.
|
|
Suppose class A declares member function A::f( ) as a virtual function, and class B derives from A.
Ordinarily, return types are allowed to vary between overrides of f( ).
However, for virtual functions the rules are more complex,
that is, B may override f( ) with a different return type, if:
The function B::f( ) returns a reference or pointer to a class of some type T,
and A::f( ) returns a pointer or a reference to an unambiguous direct or indirect base class of T.
The const qualification of the pointer or reference returned by B::f( ) has the same,
or less const qualification of the pointer or reference returned by A::f( ).
The return type of B::f( ) must be complete at the point of declaration of B::f( ),
or it can be of type B.
Problem:
We need an interface of abstract class Base.
We already have another class Done, that implements the needed behavior, but with the wrong interface.
Solution:
Derive new class Adapter from Base.
Add Done object as a member of Adapter class.
Use thin layer of code around Done object to implement the desired Base interface.
Consider adapters when interfaces are quite similar.
class Adapter : public Base { public: // Base functions //... private: Done m_done; };//class Adapter
Or possibly:
class Adapter : public Base, private Done
Or build a two-way adapter, using multiple inheritance where both bases are public:
class Adapter : public Base, public Done
Abstract adapter adds convenience of Base interface to an existing Done implementation:
class Adapter : public Base { public: Adapter( Done* dp ); // Base functions //... private: Done* m_done; };//class Adapter
The memento design pattern provides ability to restore (rollback) an object to its previous state.
An object of class Item has considerable hidden state information.
We want to remember checkpoint state, modify the state, then (possibly) restore the state to the checkpoint state.
Minimal impact on the original Item class interface is preferred!
|
|