CSE332S Object-Oriented Programming in C++ (Lecture 17)
Object Oriented Programming Building Blocks
OOP Building Blocks for Extensible, Flexible, and Reusable Code
Today: Techniques Commonly Used in Design Patterns
- Program to an interface (last time)
- Object composition and request forwarding (today)
- Composition vs. inheritance
- Run-time relationships between objects (today)
- Aggregate vs. acquaintance
- Delegation (later…)
Next Time: Design Patterns
Describe the core of a repeatable solution to common design problems.
Code Reuse: Two Ways to Reuse a Class
Inheritance
Code reuse by inheriting the implementation of a base class.
- Pros:
- Inheritance relationships defined at compile-time - simple to understand.
- Cons:
- Subclass often inherits some implementation from superclass - derived class now depends on its base class implementation, leading to less flexible code.
Composition
Assemble multiple objects together to create new complex functionality, forward requests to the responsible assembled object.
- Pros:
- Allows flexibility at run-time, composite objects often constructed dynamically by obtaining references/pointers to other objects (dependency injection).
- Objects known only through their interface - increased flexibility, reduced impact of change.
- Cons:
- Code can be more difficult to understand, how objects interact may change dynamically.
Example: Our First Design Pattern (Adapter Pattern)
Problem: We are given a class that we cannot modify for some reason - it provides functionality we need, but defines an interface that does not match our program (client code).
Solution: Create an adapter class, adapter declares the interface needed by our program, defines it by forwarding requests to the unmodifiable object.
Two ways to do this:
class unmodifiable {
public:
int func(); // does something useful, but doesn’t match the interface required by the client code
};-
Inheritance
// Using inheritance: class adapter : protected unmodifiable { // open the access to the protected member func() for derived class public: int myFunc() { return func(); // forward request to encapsulated object } }; -
Composition
class adapterComp { unmodifiable var; public: int myFunc() { return var.func(); } };
Thinking About and Describing Run-time Relationships
Typically, composition is favored over inheritance! Object composition with programming to an interface allows relationships/interactions between objects to vary at run-time.
- Aggregate: Object is part of another. Its lifetime is the same as the object it is contained in. (similar to base class and derived class relationship)
- Acquaintance: Objects know of each other, but are not responsible for each other. Lifetimes may be different.
// declare Printable Interface
// declare printable interface
class printable {
public:
virtual void print(ostream &o) = 0;
};
// derived classes defines printable
// interface
class smiley : public printable {
public:
virtual void print(ostream &o) {
o << ":)";
};
};
// second derived class defines
// printable interface
class frown : public printable {
public:
virtual void print(ostream &o) {o << ":(";
};
};- Aggregate
// implementation 1:
// Aggregate relationship
class emojis {
printable * happy;
printable * sad;
public:
emojis() {
happy = new smiley();
sad = new frown();
};
~emojis() {
delete happy;
delete sad;
};
};- Acquaintance
// implementation 2:
// Acquaintances only
class emojis {
printable * happy;
printable * sad;
public:
emojis();
~emojis();
// dependency injection
void setHappy(printable *);
void setSad(printable *);
};