Object Seam

Introduction

Object seams are probably the most common seam type. To start with an example, consider the following code where the class GameFourWins has a hard coded dependency to Die:

// Die.h
struct Die {
  int roll() const ;
};
// Die.cpp
int Die::roll() const {
  return rand() % 6 + 1;
}
// GameFourWins.h
struct GameFourWins {
  void play(std::ostream& os);
private:
  Die die;
};
// GameFourWins.cpp
void GameFourWins::play(std::ostream& os = std::cout) {
  if (die.roll() == 4) {
    os << "You won!" << std::endl;
  } else {
    os << "You lost!" << std::endl;
  }
}

According to Feathers definition, the call to play is not a seam because it is missing an enabling point. We cannot alter the behaviour of the member function play without changing its function body because the used member variable die is based on the concrete class Die. Furthermore, we cannot subclass GameFourWins and override play because play is monomorphic (not virtual).

This fixed dependency also makes GameFourWins hard to test in isolation because Die uses C's standard library pseudo-random number generator function rand. Although rand is a deterministic function since calls to it will return the same sequence of numbers for any given seed, it is hard and cumbersome to setup a specific seed for our purposes. The classic way to alter the behaviour of GameFourWins is to inject the dependency from outside. The injected class inherits from a base class, thus enabling subtype polymorphism. To achieve that, Mockator provides a refactoring called Extract Interface and creates the following code:

struct IDie {
  virtual ~IDie() {}
  virtual int roll() const =0;
};
struct Die : IDie {
  int roll() const {
    return rand() % 6 + 1;
  }
};
struct GameFourWins {
  GameFourWins(IDie& die) : die(die) {}
  void play(std::ostream& os=std::cout) {
    // as before
  }
private:
  IDie& die;
};

This way we can now inject a different kind of Die depending on the context we need. This is a seam because we now have an enabling point: The instance of Die that is passed to the constructor of GameFourWins.

Screencast