I-PROGR2-HOME

  1. Einführung

  2. Iteratoren

  3. Funktionsobjekte

  4. Autopointer

  5. Adapter

  6. Testfragen und Aufgaben


I-PROGR2 SS03 - PROGRAMMIEREN2 - Vorlesung mit Übung
Einfache Entwicklungsmuster ('design-pattern')

 Achtung : Skript gibt den mündlichen Vortrag nicht vollständig wieder !
 Achtung : Skript noch nicht abgeschlossen !!!
                         

AUTHOR: Gerd Döben-Henisch
DATE OF FIRST GENERATION: June-16, 2003
DATE OF LAST CHANGE: June-11, 2003
EMAIL: Gerd Döben-Henisch



1. Einführung


Entwicklungsmuster ('design patterns') sind generische Lösungsverfahren für ständig wiederkehrende Probleme im Rahmen des Softwareengineerings. In der heutigen VL sollen Beispiele von Design Patterns für einige sehr häufig auftretende Probleme vorgestellt werden. Wir folgen hier [WEISS 2001:Kap.5]. Die Programmbeispiele sind der Online-Sammlung von WEISS entnommen.


START



2. Iteratoren


Iteratoren sind ganz allgemein die Objekte, die die Wiederholung im Rahmen einer Menge, einer Kollektion, einer Anordnung kontrollieren.

Im einfachen Fall wird der Iterator durch eine einfache Variable i repräsentiert, die z.B. als Index über einen Vektor v laufen kann:

 for( i = 0; i < v.size(); i++ )
    cout << v[ i ] << endl;

Hier ist der Iterator i sehr speziell. Besser wäre es, man hätte einen Iterator, dem man beliebige Mengen zuweisen kann und der auf verschiedene Weise über diesen Mengen Operationen ausführen könnte. D.h. idealerweise versucht man zwischen dem Kode, der den Iterator beschreibt, und dem Kode, der die jeweilige Menge beschreibt, zu trennen; ein Iteratorobjekt sollte über möglichst viele verschiedene Mengen operieren können. In der STL gibt es Iteratorklassen; allerdings sind diese recht komplex. Hier von WEISS drei Beispiele mit zunehmender Komplexität.



//*****************************
//
// Iterator1.cpp
//
//******************************


#include <iostream>
using namespace std;

#include <vector>
using namespace std;


template <class Object>
class VectorIterator;

// Same as the vector, but has a getIterator method.
// No extra data, no overridden methods, so non-virtual
// destructor in original vector is OK!

template <class Object>
class MyVector : public vector<Object>
{
  public:
    explicit MyVector( int size = 0 )
      : vector<Object>( size ) { }

    VectorIterator<Object> getIterator( ) const
      { return VectorIterator<Object>( this ); }
};

// A passive iterator class. Steps through its MyVector.

template <class Object>
class VectorIterator
{
  public:
    VectorIterator( const MyVector<Object> *v )
      : owner( v ), count( 0 ) { }

    bool hasNext( ) const
      { return count != owner->size( ); }

    const Object & next( )
      { return (*owner)[ count++ ]; }

  private:
    const MyVector<Object> *owner;
    int count;
};

int main( )
{
    MyVector<int> v;

    v.push_back( 3 );
    v.push_back( 2 );

    cout << "Vector contents: " << endl;

    VectorIterator<int> itr = v.getIterator( );
    while( itr.hasNext( ) )
        cout << itr.next( ) << endl;

    return 0;
}







gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> g++ -o iterator Iterator1.cpp
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> ./iterator
Vector contents:
3
2
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13>
 






//*****************************
//
// Iterator2.cpp
//
//******************************


#include <iostream>
using namespace std;

#include <vector>
using namespace std;


template <class Object>
class VectorIterator;

// Same as the vector, but has a getIterator method.
// No extra data, no overridden methods, so non-virtual
// destructor in original vector is OK!
template <class Object>
class MyVector : public vector<Object>
{
  public:
    explicit MyVector( int size = 0 )
      : vector<Object>( size ) { }

    VectorIterator<Object> getIterator( ) const
      { return VectorIterator<Object>( this ); }
};

// A passive iterator class. Steps through its MyVector.
template <class Object>
class VectorIterator
{
  public:
    VectorIterator( const MyVector<Object> *v )
      : owner( v ), count( 0 ) { }

    void reset( )
      { count = 0; }

    bool isValid( ) const
      { return count < owner->size( ); }

    void advance( )
      { count++; }

    const Object & retrieve( ) const
      { return (*owner)[ count ]; }

  private:
    const MyVector<Object> *owner;
    int count;
};


int main( )
{
    MyVector<int> v;

    v.push_back( 3 );
    v.push_back( 2 );
    VectorIterator<int> itr = v.getIterator( );

    for( int i = 0; i < 2; i++ )
    {
        cout << "Vector contents: " << endl;
        for( itr.reset( ); itr.isValid( ); itr.advance( ) )
            cout << itr.retrieve( ) << endl;
    }

    return 0;
}






gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> g++ -o iterator2 Iterator2.cpp
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> ./iterator2
Vector contents:
3
2
Vector contents:
3
2
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13>







//*****************************
//
// Iterator3.cpp
//
//******************************


#include <iostream>
using namespace std;

#include <vector>
using namespace std;



template <class Object>
class Iterator;

template <class Object>
class VectorIterator;

// Same as the vector, but has a getIterator method.
// No extra data, no overridden methods, so non-virtual
// destructor in original vector is OK!
template <class Object>
class MyVector : public vector<Object>
{
  public:
    explicit MyVector( int size = 0 )
      : vector<Object>( size ) { }

    Iterator<Object> *getIterator( ) const
      { return new VectorIterator<Object>( this ); }
};


// A passive iterator class protocol. Steps through its container.
template <class Object>
class Iterator
{
  public:
    virtual ~Iterator( ) { }

    virtual bool hasNext( ) const = 0;
    virtual const Object & next( ) = 0;
};

// A concrete implementation of the iterator.
// Could have been nested inside of MyVector!
template <class Object>
class VectorIterator : public Iterator<Object>
{
  public:
    VectorIterator( const MyVector<Object> *v )
      : owner( v ), count( 0 ) { }

    bool hasNext( ) const
      { return count != owner->size( ); }

    const Object & next( )
      { return (*owner)[ count++ ]; }

  private:
    const MyVector<Object> *owner;
    int count;
};


int main( )
{
    MyVector<int> v;

    v.push_back( 3 );
    v.push_back( 2 );

    cout << "Vector contents: " << endl;

    Iterator<int> *itr = v.getIterator( );
    while( itr->hasNext( ) )
        cout << itr->next( ) << endl;

    delete itr;

    return 0;
}






gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> g++ -o iterator3 Iterator3.cpp
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> ./iterator3
Vector contents:
3
2
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13>


START



3. Funktionsobjekte


Statt in gewissen Fällen Funktionen direkt aufzurufen, kann es sinnvoller sein, eine Funktions-Klasse zu definieren mit entsprechenden Elementfunktionen, und dann Instanzen dieser Funktionsklassen, nämlich Funktions-Objekte, zu übergeben. Im folgenden Beispiel wird zusätzlich der Operator operator() als elementfunktion definiert und dann als funktion des Objektes aufgerufen.

 

//******************************************
//
// Rectangle.cpp
//
//*****************************************
/**
 * Example of Comparator, with rectangles.
 */

#include <stdlib.h>
#include <iostream>

using namespace std;

#include <vector>
using std::vector;


// A simple rectangle class.

class Rectangle
{
  public:
    Rectangle( int len = 0, int wid = 0 )
      : length( len ), width( wid ) { }

    int getLength( ) const
      { return length; }

    int getWidth( ) const
      { return width; }

    void print( ostream & out = cout ) const
      { out << "Rectangle " << getLength( ) << " by " << getWidth( ); }

  private:
    int length;
    int width;
};

ostream & operator<< ( ostream & out, const Rectangle & rhs )
{
    rhs.print( out );
    return out;
}


// Compare object: ordering by length.
class LessThanByLength
{
  public:
    bool operator( ) ( const Rectangle & lhs, const Rectangle & rhs ) const
      { return lhs.getLength( ) < rhs.getLength( ); }
};


// Compare object: ordering by area.
class LessThanByArea
{
  public:
    bool operator( ) ( const Rectangle & lhs, const Rectangle & rhs ) const
      { return lhs.getLength( ) * lhs.getWidth( ) <
               rhs.getLength( ) * rhs.getWidth( ); }
};

// Generic findMax, with a function object.
// Precondition: a.size( ) > 0.
template <class Object, class Comparator>
const Object & findMax( const vector<Object> & a, Comparator isLessThan )
{
    int maxIndex = 0;

    for( int i = 1; i < a.size( ); i++ )
        if( isLessThan( a[ maxIndex ], a[ i ] ) )
            maxIndex = i;

    return a[ maxIndex ];
}

// main: create four rectangles.
// find the max, two ways.
int main( )
{
    vector<Rectangle> a;

    a.push_back( Rectangle( 1, 10 ) );
    a.push_back( Rectangle( 10, 1 ) );
    a.push_back( Rectangle( 5, 5 ) );
    a.push_back( Rectangle( 4, 6 ) );

    cout << "Largest length:\n\t" << findMax( a, LessThanByLength( ) ) << endl;
    cout << "Largest area:\n\t" << findMax( a, LessThanByArea( ) ) << endl;

    return 0;
}







gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> g++ -o rectangle Rectangle.cpp
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> ./rectangle
Largest length:
        Rectangle 10 by 1
Largest area:
        Rectangle 5 by 5
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13>
 

START



4. Klasse Autopointer


Probleme mit Zeigern können speziell dort auftreten, wo eine Funktio f() Objekte mit new explizit generiert und diese auch wieder expolizit mit delete beseitigen muss:

void f()
{
 ClassA* ptr = new ClassA;
 ...
 delete ptr;
}

Die Gefahr ist gross, dass dieses 'delete' vergessen wird, falls zuvor ein 'return' oder eine 'exception' auftritt. Eine Lösung bestände darin, statt einen gewöhnlichen Zeiger einzuführen, eine Zeiger-Klasse einzuführen, die 'Besitzer' des neuen Objektes ist. Ein Löschen der Klasse hat dann automatisch zur folge, dass der Dekonstruktor dieser Klasse aufgerufen wird und das zugehörige Objekt löscht; solche Zeiger nennt man auch Intelligente Zeiger ('smart pointer').

#include <memory> //Headerdatei fuer auto_ptr

void f()
{
 std::auto_ptr<ClassA> ptr1(new ClassA); //OK
 std::auto_ptr<ClassA> ptr2 = new ClassA; //ERROR
 ..
 //kein explizites delete !
}




//******************************************
//
// Autopointer.cpp
//
//*****************************************
#include <stdlib.h>
#include <iostream>
using namespace std;

// Class that wraps a local pointer variable.
template <class Object>
class AutoPointer
{
  public:
    explicit AutoPointer( Object *rhs = NULL )
      : isOwner( rhs != NULL ), pointee( rhs ) { }
    AutoPointer( AutoPointer & rhs ) : isOwner( rhs.isOwner )
      { pointee = rhs.release( ); }

    ~AutoPointer( )
      { free( ); }

    const AutoPointer & operator= ( AutoPointer & rhs )
    {
        if( this != &rhs )
        {
            Object *other = rhs.get( );
            if( other != pointee )        // Different pointees, so
            {
                free( );                  // Give up current pointer
                isOwner = rhs.isOwner;    // Assume ownership
            }
            else if( rhs.isOwner )        // Same pointers
                isOwner = true;           // If rhs owned it, now I do
            pointee = rhs.release( );     // Copy pointee, rhs gives up ownership
        }
        return *this;
    }

    Object & operator* ( ) const
      { return *get( ); }
    Object * operator->( ) const
      { return get( ); }

    Object * get( ) const
      { return pointee; }
    Object * release( )
      { isOwner = false; return pointee; }

  private:
    Object *pointee;
    bool isOwner;

    void free( )
      { if( isOwner ) delete pointee; }
};

// Test program: Use an Integer class with a noisy destructor.

struct Integer
{
   Integer( int a ) : x( a ) { }

   ~Integer( ) { cout << "DESTRUCT " << x << endl; }

   int x;
};


int main( )
{
    AutoPointer<Integer> p1( new Integer( 3 ) );
    AutoPointer<Integer> p2 = p1;
    AutoPointer<Integer> p3;

    cout << (*p1).x << " " << (*p2).x << endl;
    p3 = p1;
    cout << p1->x << " " << p2->x << " " << p3->x << endl;
    // 3 is owned by p2.

    AutoPointer<Integer> p4( new Integer( 4 ) );
    cout << p1->x << " " << p2->x << " " << p3->x << " " << p4->x << endl;
    p2 = p4;

    cout << "3 is no longer owned! p1 and p3 are stale!!!" << endl;
    cout << p2->x << " " << p4->x << endl;

    return 0;
}







gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> g++ -o autoptr AutoPtr.cpp
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> ./autoptr
3 3
3 3 3
3 3 3 4
DESTRUCT 3
3 is no longer owned! p1 and p3 are stale!!!
4 4
DESTRUCT 4
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13>


START



5. Adapter




Aufgabe von Adaptern ist es, eine Schnittstelle ('interface') auf eine andere anzupassen. manchmal geht es nur dartum, andere Namen für die gleichen Funktionen zu verwenden.





//****************************
//
// StorageCell.hpp
//
//*********************************




#ifndef STORAGE_CELL_H
#define STORAGE_CELL_H

#include "MemoryCell.hpp"


// A class for simulating a memory cell.
template <class Object>
class StorageCell : private MemoryCell<Object>
{
  public:
    explicit StorageCell( const Object & initialValue = Object( ) )
      : MemoryCell<Object>( initialValue ) { }
    const Object & get( ) const
      { return read( ); }
    void put( const Object & x )
      { write( x ); }
};


#endif






//****************************
//
// MemoryCell.hpp
//
//*********************************




#ifndef MEMORY_CELL_H
#define MEMORY_CELL_H


// A class for simulating a memory cell.
template <class Object>
class MemoryCell
{
  public:
    explicit MemoryCell( const Object & initialValue = Object( ) );
    const Object & read( ) const;
    void write( const Object & x );

  private:
     Object storedValue;
};

#include "MemoryCell.cpp"  // Because sep. compilation doesn't always work

#endif






//****************************
//
// MemoryCell.cpp
//
//*********************************

// Construct the MemoryCell with initialValue.
template <class Object>
MemoryCell<Object>::MemoryCell( const Object & initialValue )
  : storedValue( initialValue )
{
}

// Return the stored value.
template <class Object>
const Object & MemoryCell<Object>::read( ) const
{
    return storedValue;
}

// Store x.
template <class Object>
void MemoryCell<Object>::write( const Object & x )
{
    storedValue = x;
}








//****************************
//
// StorageCell_usage.cpp
//
//*********************************

#include <iostream>
using namespace std;
#include <string>


#include "StorageCell.hpp"

int main( )
{
    StorageCell<int>    m1;
    StorageCell<string> m2( "hello" );

    m1.put( 37 );
    m2.put( m2.get( ) + " world" );
    cout << m1.get( ) << endl << m2.get( ) << endl;

    // The next line does not compile if uncommented.
    // cout << m1.read( ) << endl;

    return 0;
}







gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> g++ -o storage StorageCell_usage.cpp
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13> ./storage
37
hello world
gerd@goedel:~/WEB-MATERIAL/fh/I-PROGR2/I-PROGR2-EX/EX13>

 

START



6. Aufgaben



START