Skip to Content
CSE332SObject-Oriented Programming Lab (Lecture 17)

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 };
  1. 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 } };
  2. 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 << ":("; }; };
  1. Aggregate
// implementation 1: // Aggregate relationship class emojis { printable * happy; printable * sad; public: emojis() { happy = new smiley(); sad = new frown(); }; ~emojis() { delete happy; delete sad; }; };
  1. Acquaintance
// implementation 2: // Acquaintances only class emojis { printable * happy; printable * sad; public: emojis(); ~emojis(); // dependency injection void setHappy(printable *); void setSad(printable *); };
Last updated on