Design Patterns in Real Life

Today, Patrick Mintram presents us with a guest post about design patterns in real life applications. Patrick has been in and around engineering for about 9 years, the last 3 years of which he has been as a software engineer. He loves to learn new things, as a result, he is always keen for feedback on his work or to learn a different way of getting a problem solved. You can find him online on Twitter and GitHub.

All beginners, whether self-taught or not, come across a problem like the following:

Design some software to help a coffee shop’s ordering system.

where the guidance for the *tutorial/lecture note leads you down a path of using a design pattern such as a factory, or a decorator. For me though, this has little to do with my day to day work or indeed any real-world situation (as a beginner who would ask me to design a whole coffee shops’ ordering system?). I thought I’d write this to help give some real-world context for and examples of why Design Patterns can be useful.

(*) See the Head First Design Patterns book by Freeman and Freeman Chapter 3

I want to take no credit for this design, it was put in place by the staff engineer on the project.
I also want to keep many of the technical details away from this post to avoid infringing on any IP rules. Fortunately, a high-level description and pseudocode should do the trick!

In my day to day job, I was working on a C++ codebase which controlled some hardware including relays, power supplies, and some measurement instruments. Like many businesses’ typical setup this test equipment (TE) can be used to test lots of different products. There are a few variations upon a theme where some pieces of TE contain different power supplies or different measurement instruments but for the most part, it’s the same.

example setup

The first task the staff engineer faced was to find a way to support software development with lots of different units under test (UUTs). It’s possible to identify some things already from the basic description of the environment:

  1. The hardware (relays, power supplies, measurement instruments) is unlikely to change frequently on a particular piece of TE.
  2. The TE is going to be used for testing – so it will be producing results, error messages, user prompts etc.
  3. There are a few different pieces of TE which do similar things.
  4. The UUT is going to change a lot, and with it will be a whole new set of tests required.

I will be focussing on the Decorator and Composite patterns in this post – they are both recursive patterns where the implementations for the base type have both an is a and has a relationship

Decorator

painting banner

The interesting thing about measurement devices and power supplies (in fact just about anything) connected via a serial connection is that sometimes they fail to talk as you expect. Sometimes there is a timeout, sometimes there are framing errors, sometimes the device sends a really long list of error codes, sometimes it just starts working if you try a second time after clearing any buffers.

Here is the perfect time to introduce a real world usage of the decorator pattern! Take this simplistic class for example:

class power_supply {
public:
  virtual double get_voltage(void) {
    return std::strtod(get_from_device("voltage"));
  }

  virtual void set_voltage(const double v) {
    send_to_device(std::to_string(v));
  }

// ...

protected:
// These functions handle the comms for the device
// they throw an exception on failure
  virtual void send_to_device(const std::string msg) {
    // ...
  }

  virtual std::string get_from_device(const std::string msg) {
    // ...
  }
};

Using this class what are some possible solutions if there is a failure and the device manual says to try again? This could be done any number of ways

Option 1: Create a class which inherits from power_supply and overrides the send and get methods.

class retry_power_supply : public power_supply {
public:
  void send_to_device(const std::string msg) override {
    // Try 1
    // Try 2
  }
};

Option 2: When using the device in a test add in some extra steps which try again.

try {
  psu.set_voltage(10.5);
}
catch(...) {
  psu.set_voltage(10.5);
}

Option 3: Decorate the class by providing a pure virtual base class, a decorator base class, and a retry decorator.

class power_supply_base {
public:
  virtual double get_voltage(void) = 0;
  virtual void set_voltage(const double v) = 0;
protected:
  virtual std::string get_from_device(const std::string msg) = 0;
  virtual void send_to_device(const std::string msg) = 0;
};

class power_supply: public power_supply_base {
  // Same as above
};

class power_supply_decorator : public power_supply_base {
public:
  virtual double get_voltage(void) { return wrapee_.get_voltage(); }
  virtual void set_voltage(condt double v) { wrapee_.set_voltage(v); }
  /// Etc

protected:
  power_supply_base& wrapee_;
};

class retry_supply : public power_supply_base {
public:
  virtual std::string get_from_device(const std::string msg) override {
    try {
      return wrapee_.get_from_device(msg);
    }
    catch(...) {
      return wrapee_.get_from_device(msg);
    }
    throw this_is_really_broken;
  }
};

At first glance, methods 1 and 2 are the best. And for this case I’d agree it’s good to KISS. What about if one power supply likes to have its error buffer cleared if there is a failure? Implementing this means either adding another class which overrides the method in a specific way, thereby adding more complexity into the test itself, or creating another decorator specialism:

class clear_on_failure_supply : public power_supply_base {
public:
  virtual std::string get_from_device(const std::string msg) override {
    try {
      return wrapee_.get_from_device(msg);
    }
    catch(...) {
      wrapee_.send_to_device("Clear your buffer!");
      return wrapee_.get_from_device(msg);
    }
  }
};

And what about if there is a power supply that needs some combination of retrying and clearing error buffers? This is where the decorator really comes into its own because it’s possible to do this by writing no further classes! Without the decorator, we’d have to do yet another specialism of the power_supply class or further complicate the test steps themselves.

The decorator means any combinations of the above are possible:

power_supply nsupply;
retry_supply rsupply(nsupply);
clean_on_failure_supply cfsupply(rsupply);
clean_and_retry_supply crsupply(cfsupply);

crsupply.set_voltage(10.5);

Using the magic of OOP that single call to the crsupply object above is doing all of the retrying and clearing buffers this fussy power supply needs! Isn’t that so much nicer to read than something like:

try {
  nsupply.set_voltage(10.5);
}
catch(...) {
  try {
    nsupply.set_voltage(10.5);
  }
  catch(...) {
    n_supply.clear_buffer();
  }
  // ...
}

Summary

The decorator is used to add or extend existing functionality of classes, this example shows how using decorators for communicating with devices can help our code be open to extension but closed to modification – after all, not once did I go and rework any existing classes. Nor did I repeat myself anywhere!

Composite

When you are testing equipment a set of test steps might be similar to this:

  1. Turn on the Power
  2. Do x
  3. .Do y
  4. Measure pin 99
  5. Do x
  6. Do y
  7. For every pin on the UUT do
    1. Do x
    2. Do y
    3. Measure pin
    4. Do x
    5. Do y
  8. Do x
  9. Do y
  10. Measure pin 99
  11. Do x
  12. Do y
  13. Turn off Power

Within this there is a bunch of repetition – hooray. This is where the recursive nature of the composite pattern can lend us a hand! Consider the following:

class test_t {
public:
  virtual void test (void) = 0;
  virtual ~test_t(void) {}
};

class loop_test_t: public test_t {
public:
  loop_test_t(void) {
    populate_loop();
  }

  void test(void) {
    while(!m_loop.empty()) {
      auto t = m_loop.front();
      t->test();
      delete t;
      m_loop.pop_front();
    }
  }

protected:
  virtual void populate_loop(void) = 0;
  std::deque<test_t*>; m_loop;
};

These two classes provide a neat foundation for facilitating the tests by providing a common interface to them – .test(), in addition to providing a good chuckle when you pronounce test_t as teste.

The first step is to identify where that repetition happens, and any common functionality. Here the common functionality is in the “do x”, “do y”, “measure pin”, “do x”, “do y” steps of the instructions.

This can be used in practice as follows:

class pin_test_t: public test_t {
public:
  pin_test_t(const int pin): m_pin(pin){}

  void test(void) {
    x();
    y();
    std::cout << "Testing Pin" << m_pin << std::endl;
    x();
    y();
  }

private:
  const int m_pin;
};

class example_test_t: public loop_test_t {
public:
  example_test_t(void){}

protected:
  void populate_loop(void) {
    m_loop.push_back(new pin_test_t(1));
    m_loop.push_back(new pin_test_t(2));
    // ...
  }
};

The populate_loop method matches to our test steps pin testing loop, there are still the first and last parts of the test to implement though. This can use another loop_test_t:

class example_test_t: public loop_test_t {
public:
  example_test_t(void){}
protected:
  void populate_loop(void) {
    m_loop.push_back(new pin_test_t(99));
    m_loop.push_back(new example_loop());
    m_loop.push_back(new pin_test_t(99));
  }
};

The key thing about this pattern here is that you can very quickly make changes to the sequence of test steps. For example what if you want to do a log between the tests? In a procedural design, this might result in having to make changes in every single specialism of test_t. However, you can just change loop_test_t::test to suit when using a composite pattern.

I found this a lot to get my head around so here is a Gist with a compilable version of the above to play with.

In this contrived example, there are other (arguably) simpler ways of creating the set of test steps. In reality, tests normally follow the following structure:

  1. Turn on the power supplies
  2. Set the UUT into a certain state
  3. Do a bunch of measuring of the UUT
  4. If there is a failure stop further testing
  5. Set the UUT into a different state
  6. Do a bunch of measuring of the UUT
  7. Turn off the power supplies

How would you do this using the Composite pattern and the Gist provided? I would override loop_test_t::test like so, and the rest will fall into place!

class quit_on_failure_test : loop_test_t {
  void test(void) override {
    while(!m_loop.empty() &amp;&amp; no_failures) {
      auto t = m_loop.front()
      t->test();
      delete t;
      m_loop.pop_front();
    }
  }
};

Summary

I find this pattern fascinating, because it adds complexity to the design, as a result, most people think “I’ll just do a set of test steps and when a loop comes up use a for loop” and I’m inclined to agree with them in a lot of simple cases. I will refer back to the paradigm of keeping code open to extension but closed to modification and encourage everyone (including myself) to think about how this application of the composite design pattern can help with that:

  • Changing the order of tests doesn’t require anyone to even touch the code which implements the test steps. (Particularly useful if it has been approved by an organization’s Quality department!)
  • If another test which comes up with a slightly different start/end, we can re-use the existing example_test_t class as is!

Have a play with the pattern in the Gist and discover how quickly you can make changes to the test, and create new ones without even touching the pin_test or example_loop classes.

Previous Post
Next Post
Facebooktwittergoogle_plusredditlinkedinFacebooktwittergoogle_plusredditlinkedinby feather

5 Comments


  1. I just realized I had implemented something very much like a decorator in a university project a few years ago, without realizing it was a well-known design pattern.

    Reply

    1. That’s the actual nature of design patterns: they often emerge naturally. The often-seen notion that you have to learn them all and then forcefully apply them in your code no matter what is ill-advised and harmful.

      Reply

  2. Great post, thank you!

    At first I struggled a bit with the Decorator example: I think you meant retry_supply to inherit from the decorator, not from the base.

    Reply

Leave a Reply