Delegate Design Pattern

raj
5 min readMay 24, 2019

--

The Delegate is a light weight pattern that other design patterns use — to mainly compose behaviors at run time. The Delegate pattern is used to separate out the concerns by having two objects participate in servicing a request — the object that receives the request and its delegate that does the actual work. This might be analogous to inheritance in Object Oriented Programming, where in you pass off work to the superclass. The Delegate pattern is structurally different to inheritance and brings with it, its own strengths and weakness.

For example, below is a class that represents some text data.

class TextData {
private:
std::string _data;
public:
// Methods ...
}

This is a simple class that hold all its data in memory. Now, imagine we want to spell check the text against a dictionary. This brings a new object to the design — a spell checker. A spell checker can be represented as:

class SpellChecker {
private:
std::set<std::string> _dict;

public:
SpellChecker();
virtual ~SpellChecker();

virtual std::vector<std::string> spell_check(const std::string &data);
};

The spell checker maintains a set of valid words that it checks against, when validating a document. In our demo app, we need to get these two entities to work together.

When you think about it, the roles and responsibility of both the objects are different. The document is different from that of a spell checker and and the structure is where in one object, kind of, ‘extends’ the functionality of the other.

In our example, the document and the spell checker can naturally be modeled as a ‘containment’ relationship. The TextData contains/has-a SpellChecker. This type of relationship is common in Object Oriented Programming as is known as Object Composition.

The TextData class would now look like:

class TextData {
private:
std::string _data;
std::unique_ptr<SpellChecker> _spell_checker;

public:
TextData(const std::string &s,
std::unique_ptr<SpellChecker> sc);
TextData(const TextData &other);
TextData& operator=(const TextData &other);
~TextData();

std::string get_data();

void set_spell_checker(std::unique_ptr<SpellChecker> sc);

void spell_check();
};

The TextData uses a SpellChecker object to perform the spell check. The request will message a TextData object to perform a spell check on its data and the TextData object will 'delegate' the request to the SpellCheck object that it maintains as part of its state. The SpellCheck object is the one that actually does the spell check and returns back the result to the TextData object. The TextData object is the owner and the SpellChecker object is its delegate.

Delegation makes it easy to compose behaviour at run-time and lets us change the delegate based on the task at hand. For example, imagine TextData contains data from an internet chatroom and you need to spell check on it. And you feel that the SpellChecker is limited on a check on modern internet lingo. However, you have an AdvancedSpellChecker that can spell check them. And, the AdvancedSpellChecker class is as follows:

class AdvancedSpellChecker : public SpellChecker {
std::vector<std::string> spell_check(const std::string &data);
};

Since, AdvancedSpellChecker derives from SpellChecker and conforms to the same interface, you can change the spell checker to point to the right spell checker before calling the spell_check() method on it. This is shown below as:

// Construct and use text_data
// Change spell checker for the data in hand
text_data.set_spell_checker(
std::unique_ptr<SpellChecker>(new AdvancedSpellChecker()));
text_data.spell_check();

The Delegate pattern usually involves object composition to structure itself. We use composition to construct more complex functionality brick by brick so as to separate out the concerns of the participating objects. For example, let us say someone needs to take care of saving the TextData to disk and, you need to separate out the responsibilities. You can introduce a StorageManager as follows:

class StorageManager {
void save_as_text(const std::string &loc,
const TextData &text_data);
void save_as_pdf(const std::string &loc,
const TextData &text_data);
};

And then use it in your TextData like:

class TextData {
private:
std::string _data;
std::unique_ptr<SpellChecker> _spell_checker;
std::unique_ptr<StorageManager> _storage_manager;
...
}

And the way to delegate the work would be like:

text_data.save_as_pdf(loc);
// Where this call is delegated as,
// _storage_manager->save_as_pfd(loc, this->get_data());

And this way, you go about building large software applications — out of small well defined objects, by exposing only their public interfaces to get work done. This also helps encapsulate each other and makes them flexible to individual changes without adding any dependencies on the participating objects. This style of re-use is a ‘black-box’ reuse, because you do not need to know the internals of the objects present. You just need to know about the delegate’s public interfaces to use them. And, it is usually advised to favor object composition — because this keeps the objects focused on its own functionality.

Also, sometimes in your application, the place where the request has originated might not be the best option to handle the request. You might need to pass it off to an object, best suited to handle the request. These situations are tailor made for the Delegate pattern. You can delegate the work to a specialized object best suited for the job. For example, an UI application might catch a button click event, but such event might be outside its boundary use-case wise. So instead, it passes on to a delegate which work on such events.

Several design patterns makes use of the Delegate pattern. It is usually present when we need different execution strategies, depending on some factors. And, it involves delegating the specific request to the delegate object to carry out the request. We shall look at all these pattern in later blog posts.

The main disadvantage with the Delegate pattern is that it makes the software more dynamic and hard to reason about its run-time workings. It is easier to hunt down a bug when the structure is static and you have the compiler assisting you. Usually, the solution for this is to have well defined delegates that does one thing really well. So, when the boundaries are clearly defined, you can easily debug/reason about.

That’s it. Hope this post has explained well, the Delegate pattern and when they need to be used and also what to look out for.

You can view my projects on github.

You can reach me through twitter.

--

--