The first lab exercise in Design Patterns:
dynamic polymorphism and basic patterns

1)

This exercise considers dynamic polymorphism in C. You need to write low level code (functions createDog, animalPrintGreeting and animalPrintMenu) which would enable correct execution of the following test function.

void testAnimals(void){
  struct Animal* p1=createDog("Hamlet");
  struct Animal* p2=createCat("Ophelia");
  struct Animal* p3=createDog("Polonius");

  animalPrintGreeting(p1);
  animalPrintGreeting(p2);
  animalPrintGreeting(p3);

  animalPrintMenu(p1);
  animalPrintMenu(p2);
  animalPrintMenu(p3);

  free(p1); free(p2); free(p3);
}

The test function testAnimals should generate the following output.

  Hamlet greets: howl!
  Ophelia greets: meow!
  Polonius greets: howl!
  Hamlet likes boiled beef
  Ophelia likes canned tuna
  Polonius likes boiled beef

Assume that the functions which describe the behaviour of particular animals are defined as follows.

char const* dogGreet(void){
  return "howl!";
}
char const* dogMenu(void){
  return "boiled beef";
}
char const* catGreet(void){
  return "meow!";
}
char const* catMenu(void){
  return "canned tuna";
}

Note that the behaviours of animalPrintGreeting and animalPrintMenu correspond to the kind of the supplied object. While designing your solution, please consider the following instructions.

Please note that the declaration PTRFUN pfun; in C (but not in C++!) defines a pointer to a function with unspecified arguments. Consequently, pfun may point to any function returning char const* (details). While using the pointer pfun we must pay attention that count and types of supplied arguments must match the definition of the function to which our pointer points at (otherwise, the program behaviour will be undefined).

Your solution must ensure that the memory footprint of the created objects does not depend on the number of virtual methods. In other words, adding a new virtual method should not increase sizeof(struct Animal).

Show that concrete objects can be created both on the heap and ond the stack (details). You can allocate memory on the stack by instantiating a local variable or a local char array. Conversely, you can allocate on the heap by invoking malloc.

Write a function to create n dogs in a contiguous memory buffer, where n is the argument of the function (e.g. for a sled competition). Show how we can do this with one call to malloc and the required number of calls to constructDog.

After completing the exercise, establish the relation with the terminology of the object-oriented languages. Which elements of your solution correspond to the data members, methods, virtual methods, constructors and virtual tables?

If you are fascinated by object-oriented programming in C and want to learn more about it - have a look at the following book. However, before you make the final decision to switch from C++ to C, we recommend considering exceptions, templates, STLs, and new features offered by the 2011 and 2014 standards.

2) This exercise considers the memory price of dynamic polymorphism. The exercise shall be carried out in C++, but similar conclusions would hold in other languages as well. Consider the two classes CoolClass and PlainOldClass as follows.

  class CoolClass{
  public:
    virtual void set(int x){x_=x;};
    virtual int get(){return x_;};
  private:
    int x_;
  };
  class PlainOldClass{
  public:
    void set(int x){x_=x;};
    int get(){return x_;};
  private:
    int x_;
  };
Check the memory requirements of the two types by printing sizeof(PlainOldClass) and sizeof(CoolClass). Explain the obtained results.

3) This exercise considers the time price of dynamic polymorphism. The exercise shall be carried out in C++, but similar conclusions would hold in other languages as well. Consider the following modified version of CoolClass and the following main program.

  class Base{
  public:
    //if in doubt, google "pure virtual"
    virtual void set(int x)=0;
    virtual int get()=0;
  };
  class CoolClass: public Base{
  public:
    virtual void set(int x){x_=x;};
    virtual int get(){return x_;};
  private:
    int x_;
  };
  int main(){
    PlainOldClass* poc=new PlainOldClass;
    Base* pb=new CoolClass;
    poc->set(42);
    pb->set(42);
  }  
Place the code above into a file named mytest.cpp, and add the definition of the class PlainOldClass shown in the previous exercise. Compile the file by instructing the compiler to skip optimizations and to produce the assembly code (for instance, you might invoke gcc with g++ -O0 -S -masm=intel mytest.cpp). Now check the way how the compiler implemented calls to Base::set and PlainOldData::set. A neat way to complete that would be to search the constant 42 within the file with the generated assembly code (in the proposed example that file would be mytest.s).

Explain the difference between the implementations of these two calls. Which of the two implementations contains less instructions? In which of the two cases would an optimizing compiler be able to generate the code without using the instruction CALL, that is by inlining the method body into the calling code?

Extra assignment for the patient: try to locate the assembly code which defines and initializes the virtual table for the class CoolClass. Note that the tool c++filt may be helpful for decoding decorated identifiers.

4) This exercise considers the particular behaviour of the polymorphic calls during the object construction. Try to explain the program output by analyzing the compiled machine code.

#include <stdio.h>

class Base{
public:
  Base() {
    myMethod();
  }

  virtual void myVirtualMethod() {
    printf("I am Base::myVirtualMethod!\n");
  }

  void myMethod() {
    printf("The virtual method says: ");
    myVirtualMethod();
  }
};

class Derived: public Base{
public:
  Derived(): Base() {
    myMethod();
  }
  virtual void myVirtualMethod() {
    printf("I am Derived::myVirtualMethod!\n");
  }
};

int main(){
  Derived* pd=new Derived();
  pd->myMethod();
}

5. Consider a component which is supposed to generate an appropriate sequence of integral numbers, and to report the 10th, 20th, ..., and the 90th percentiles of the corresponding distribution. The component should support the generation of integers in all of the following ways:

The component also should support the calculation of p-th percentile of a given distrubution in all of the following ways:

The component should be designed in order to allow inclusion of further ways to generate integers and calculating percentiles without changing the component itself.

Design a solution to the problem in concordance with the Strategy design pattern, and demonstrate its functionality by a suitable test driver. Discuss the properties of your design and compare it to an alternative solution which would be obtained according to the Template method design pattern.

6. Consider a class which is supposed to model a database connection by the following interface.

class MyDBase{
public:
  struct Param{
    std::string table;
    std::string column;
    std::string key;
  };
  virtual int query(const Param& p)=0;
};
The method query returns zero when successful and otherwise the negative error code.

The objective of the exercise is to design decorators which are able to extend any given MyDBase object with the following additional functionality.

Both decorators have to be designed in a removable fashion, that is, they have to provide a way to turn the object into original state.

Test the developed decorators in combination with a mock implementation of the MyDBase interface, and demonstrate the possibility to remove decorators. Discuss the properties of the developed design.