ACCU Home page ACCU Conference Page
Search Contact us ACCU at Flickr ACCU at GitHib ACCU at Google+ ACCU at Facebook ACCU at Linked-in ACCU at Twitter Skip Navigation

pinLetters: Encapsulate Context

Overload Journal #65 - Feb 2005 + Letters to the Editor   Author: Alan Griffiths

Encapsulate Context

Mr. Griffiths,

When I read the title and abstract for the pattern, I thought it might be really useful. Instead, this Pattern is simply a global wrapped in a shroud. I would like to say, at the outset, that I do believe there is a place for global variables (std::cout being an excellent example). I also like the concept of contextually global variables - variables that are global within a given context. To make my argument I offer the following:

  1. There are two ways a variable becomes global within a program: 1) it is intentionally declared global, or 2) it is passed to every function as a parameter.

  2. The ENCAPSULATE CONTEXT pattern suggests that there is a single variable that should be passed to all functions within a given context, effectively making the variable global (via A.).

  3. The ENCAPSULATE CONTEXT pattern as written, does not build a context into the variable, but rather provides a global that can be provided within context. There is nothing to define or constrain the context under which variables within the structure should be accessed.

In short, the functionality provided by the ENCAPSULATE CONTEXT pattern as written can be easily achieved by declaring some global variables in a namespace that is shared by the functions that need access to these variables. This "solution" is far cheaper, more effective, and clearly delineates the level of coupling the functions share, whereas the ENCAPSULATE CONTEXT solution as written only serves to obfuscate the level of coupling, not mitigate it.

Were I a sufficiently adept programmer, I would propose an alternative to the ENCAPSULATE CONTEXT solution as written. Sadly, my skills do not lie in this area and it would take me ages to accomplish this task. At best I can offer some design suggestions.

It strikes me that the container which "collects data together" needs to do more than simply provide a pointer that can be passed around on the parameter line. It should have a built in hierarchy of contexts that allow functions to pass not simply the container, but also information about the context under which the data should be accessed. By providing the context (albeit dynamically) it is possible to limit the scope of the variables that are accessible as the context tree is traversed.

Taking the straw man stock exchange trading system that Allan Kelly used, I would see the calls to the functions something like this:

ProcessMarketTrade(msg,
               context->constrain(data_store
                                  || log));
MarketStore::Sell(msg,
                  context->constrain(log));

the constrain function from within context should look something like:

MarketContext * MarketContext::constrain(
                         ContextLevel level);

In this case, the parameters to context.constrain() define the highest point in the hierarchies of data stored within the context to which the function should have access and the call returns a pointer to a variable which has been so constrained (I can envision several objections to this particular implementation, but I hope that the idea is clear). The hierarchy within the context container might look something like:

config_data
  parms
app_data
  data_store
run_time_data
  log
    error_log
    transaction_log

Within the hierarchy there might be variables associated with that point in the hierarchy ... in run_time_data/log/error_log there might be a lock that is used to lock the file while a set of error messages is being written, the file pointer, etc. It is worth noting that you shouldn't be able to pass a context above your current context. Thus, if you had received the container with the context level of log you would be able to pass on log, error_log, or transaction_log, but not parms or run_time_data. Maybe all of this could be done with templates somehow.

This would encapsulate the context and refine how generally global variables might be accessed. A function with access to "log" might not have access to "parms" and this would help with decoupling. It strikes me that this is still a long-winded and complicated way to achieve the same thing as globals within a namespace. Given your concerns, however, that it might be good to be able to recover from the state in which you find the application rather than simply rewriting it so that such coupling is either not needed or explicit, this may be a reasonable approach. Again, I wish I had the necessary skills to adequately program such a structure, but my "back of the envelope" efforts have not come to fruition and I'm wondering if I have grasped the whole of the problem or just a piece.

As a final note, I would like to say that the article was clear, well written, and was on topic for Overload. I do feel that the solution given represents a "bad programming practice" but I readily concede to those more learned in this area then myself. I also think that the solution given is a very complicated way to ignore namespaces for no particular gain and some loss.

Sincerely,

William Fishburne

Allan's Reply

Dear Editor,

I'm rather surprised by the amount of attention my little pattern, ENCAPSULATE CONTEXT, in Overload 63 has generated. However, I regard this as a good thing.

I'm a little disappointed that I was not given the opportunity to respond to Phil Bass's points in the same issue of Overload as his letter appeared. Phil's technical points are all valid, the problem is: how do we balance all these forces? That is the problem that ENCAPSULATE CONTEXT addresses.

Phil is also concerned about the resulting coupling. I too am concerned about this and would draw his attention to the Solution and Consequences sections. These deal with some of the problems which can arise when this pattern is used - or misused. This paper does not claim to be a solution to every programming problem. Like any other pattern this one may be useful sometimes and not others. If a system can be partitioned to avoid this problem then great, if not, then this pattern has a use. Unlike many other patterns this one knows its limitations and highlights these to the reader.

Readers must decide whether this pattern stands or falls. However I am more concerned about a non-technical point Phil makes in his letter. He states "mention of Kevlin Henney, Frank Buschmann and EuroPLoP gives the article an unwarranted air of authority." Let me say that these people are mentioned not to aggrandise the pattern or myself but to acknowledge their assistance in creating the pattern. I am most grateful to these people - and the others mentioned - for all their help. It is in the culture of the patterns community that such assistance is acknowledged.

In no way did I hide anything from these people, there was no attempt to pull the wool over their eyes and slip a dodgy pattern past them. The final choice of words may have been mine but this pattern wouldn't be what it is without the review and comments of others.

These names were not mentioned to create a false authority for the pattern. If the pattern has authority it comes not from mention of these names but form the fact that others have reviewed it on multiple occasions. (In fact, I find it hard to think of any other piece of writing in Overload that has been reviewed as much as this - and that was before the Overload editorial board got to see it.)

Moving onto the comments from William Fishburne. First, let me encourage William to write up his thoughts. I'm sure his skills are up to the job.

Reading William's comments I take two main points. Firstly his suggestion to use a namespace as part of the solution. What he describes sounds like the MONOSTATE pattern - otherwise known as THE BORG. This pattern has its uses but - as the alternative name suggests - it too has problems.

The second solution is to constrain the parameters passed to a function. This may well be a solution in some contexts, but in the context taken by ENCAPSULATE CONTEXT it is not. In ENCAPSULATE CONTEXT we wish the caller to remain ignorant of what is being passed down. This is a deliberate selective ignorance. While this approach introduces compile time coupling the coupling is less than would be introduced by either global variables or an extra parameter in the function signature.

We could remove this coupling were we to use dynamic members within the context, this is the approach taken by Patow and Lyaret in PARAMETER BLOCK pattern (another pattern presented at EuroPLoP 2003.)

Both Phil and William describe my example as a straw man. To some degree this is true, like any example it is an abstraction which ignores some elements, however, I can assure them it is not a straw man example.

The example given comes from a very real system. Two explanations are therefore possible:

Option #1:

A better design was possible that would have avoided this design situation. Undoubtedly other designs where possible, would other designs have avoided this problem? I don't know. I do know that this solution troubled me as it emerged; this was the trigger for deeper investigation and ultimately writing the pattern.

More importantly, like any design this evolved, knowing what one knows at the end of a design one might not always chose the same design again. However, I am convinced that given the constraints at the time (knowledge of domain, time to market, programming interfaces, experience, etc.) this was the best design possible.

Option #2:

Simply that I am a poor designer. I will let readers decide this for themselves but I will note that others have come to similar conclusions in similar circumstances.

Finally, I welcome this debate, it's good to hear other views and it demonstrates that software development is not black and white, different opinions exists and always will. I look forward to hearing more comments and opinions.

Allan Kelly

Alan's reply to Allan

Allan is right: if a "letter to the editor" had expressed the views that Phil propounded then I would have sought a response from the author. For that I have to apologise, both to Allan and to the readership. Sorry.

Phil's comments caused discussion within the editorial team - not so much because of the views expressed, but how to deal with them. Given Phil's position as one of Overload's advisors his comments needed different treatment to those of most correspondents. Especially as the main point was regarding editorial policy: should material that an advisor has concerns about be published without warning?

My experience with the solution Allan proposes is that a number of problems experienced on the project that I employed it on were resolved without unexpected effects. Hence, I didn't feel it appropriate to give any warning beyond those Allan himself provides.

I trust that Overload readers are sufficiently sophisticated to make up their own minds about both the validity of Allan's pattern paper and also about Phil's concerns regarding publishing it.

Alan Griffiths (editor)

Developing a Pattern

Dear Editor,

I was a little surprised and more than a little worried by the comments made by Phil Bass (included in the editorial of Overload 64) on the ENCAPSULATE CONTEXT pattern by Allan Kelly (published in Overload 63). They were quite strongly against Allan's article, which is a personal preference anyone is allowed to express, but for reasons that I felt were ungrounded, and which undermined the value of those comments as a review.

The first concern I had was with Phil's question over the validity of the pattern. Given Phil's experience and the way that he has approached design in his articles, this is surprising. The solution outlined in the pattern is both valid and good for the problem it addresses, and a standard tool in the toolkit of experienced OO developers. For the record, and to assuage Phil's concerns, it can be found in a number of well-designed systems; it can also be found missing in several systems that are not so well designed, where instead single, fixed points of contact (globals, SINGLETONS, MONOSTATES, etc) are employed or long, unstable and tedious argument lists are passed around.

Unifying sets of distinct items, such as all or part of an argument list, that are bound by common use or an invariant is one of the diverse range of techniques OO developers use to identify object types. Phil considers such a use to be trivial, which appears to go against much of the accumulated wisdom on identifying stable elements in a design. Such normalisation is a common and accepted practice, whether carried out explicitly or intuitively.

The second concern I had is concerned with a fundamental part of the pattern concept. A pattern is not an unconditional piece of design advice, a blanket recommendation that covers all designs. Its applicability is very much dependent on context and the acceptability of trade-offs involved in applying it. A robustly written pattern will make clear that it is not a panacea, publicising its benefits, liabilities and alternatives quite openly. This is a point that Mark Radford made clearly and well in his editorial of Overload 63. Hence the reason for my surprise: Phil's comments suggest that he views the pattern as unconditional design advice that has neither a discussion of consequences nor a discussion of techniques involved in applying it, such as partitioning the context. I don't believe that this is the way that Phil actually views patterns, but that is the message that comes across in this instance.

Allan's pattern is quite clear in its caution, making explicit the many design decisions and alternative paths that would lead to or away from encapsulating the execution context of an object. Phil does not seem to have picked up on some of the other points made in the paper, such as partitioning the execution context. He proceeds to misapply the pattern in his discussion, and then claim that it is the pattern that is broken and not his reading of it. It is important to recognise that the pattern does not unconditionally propose that there be only one context type or context object (or, to pick up a previous point a conditional application, that context objects are needed in all system designs). To claim that the system's coupling will rise if Encapsulated Context is applied both misses and makes the point: employ the pattern to reduce the coupling rather than increase it; if that doesn't work, do it differently or do something else. If Phil does not wish to apply the pattern, I have no problem with that; if he has been unable to evaluate it on its own terms and is cautioning others that it should not be used at all, I question that.

The first two concerns are technical in character, and are the stuff of lively technical debate, whatever views we hold. However, the third concern I had was more ad hominem in nature. Phil's claim that "the mention of Kevlin Henney, Frank Buschmann and EuroPLoP gives the article an unwarranted air of authority" is an inappropriate claim that potentially insults all those involved - Allan, Frank, me and the EuroPLoP workshop participants who offered Allan feedback on his paper.

In his prologue and his acknowledgements section Allan offers an insight into the history and evolution of the paper. In recognising that it has evolved, and the journey taken so far, he mentions those who have contributed in some way to the paper. That is part of telling the story of the paper, but it is also why "acknowledgements" sections are so called. Our names were not picked out of thin air to aggrandise Allan's work: we offered Allan feedback in one form or another. It is generally considered polite to acknowledge such contributions. Such acknowledgements are also part of the cultural expectation for pattern papers.

It is perhaps worth understanding a little more about the process that surrounds the reviewing of many pattern papers. A shepherd is someone who offers comments structured as iterative feedback with dialogue, with the goal of giving the author a different insight into their paper and the means to improve it. This is the role that I took on voluntarily when the pattern originally came up in discussion in 2002. At this point Allan was working on the paper without a distinct publication goal in mind. Allan decided to submit it to EuroPLoP 2003, and at this point Frank Buschmann took on the role of the shepherd. Papers are not accepted for the PLoP family of conferences without some amount of shepherding and acceptance by the shepherd. The shepherding is intended to improve the paper to a point where it can be opened to further structured feedback at the conference in the form of a workshop, which is where Allan received further suggestions for improvement. Publication in the EuroPLoP proceedings is not a mark of perfection, but is a visible outcome of the reviewing process, and to reference it is a statement of fact rather than an appeal to authority.

Given the depth and breadth of the review process, it seems only good manners to include a list of acknowledgements. And, given the common characterisation of a pattern paper as a work that is always in progress, it is possible that Allan will take Phil's comments on board and address them in some way. In such a case, it is also likely that Phil's name will appear in the list of acknowledgements, because in one way or another he will have contributed to improving the paper.

Kevlin Henney

Phil's Response

Dear Editor,

The only comment I really wish to make is that I apologise unreservedly to Allan, Kevlin and anyone else who feels insulted by my remarks. When I said that "the mention of Kevlin Henney, Frank Buschmann and EuroPLoP gives the article an unwarranted air of authority" I chose my words badly. I still believe Allan's article over-states the value of this "pattern", but I never intended to question anyone's integrity.

The real technical issue, here, lies with Allan's initial premise that "A system contains data, which must be generally available to divergent parts of the system". That is a description of a problem. ENCAPSULATE CONTEXT is a sticking plaster that can be applied if you wish, but it doesn't begin to tackle the problem itself. What we should be doing is analysing the system's design with a view to removing (as far as possible) the need for such data.

Phil Bass

C++ Lookup Mysteries

Dear Editor,

Sven Rosvall's "C++ Lookup Mysteries" in Overload 63 couldn't have been better timed as it provided a solution to a problem I had been struggling with - a test harness that failed to compile after a new feature was added to the main product because of C++'s non-intuitive name lookup rules.

The problematic code, trimmed to the minimum to illustrate the problem, was:

template<class C>
void DebugPrint(const string& description,
                const C& container) {
  cout << description << "\n";
  // some stuff
  copy(container.begin(), container.end(),
       ostream_iterator<typename C::iterator
                    ::value_type>(cout, " "));
  cout << endl;
  // some other stuff
}

This works fine for standard containers containing either built-in types or user defined types that define an output operator in the same namespace as the type is defined. The code that broke the test harness was a standard container of std::pair. The obvious solution, defining operator<<(std::pair) in the test harness namespace, didn't work because the compiler cannot "see" this definition. The problem is that operator<< is already defined (for the built-in types) in namespace std and masks my definition, as Sven explains "Firstly, the nearest enclosing namespace is searched for 'entities' with the same name. Note that as soon as a name is found the search stops" (my italics). C++ name lookup says that the only place that the compiler will look for operator<<(std::pair) is in std.

Ah, so all I have to do is define it in std:

namespace std {
  template<class T1, class T2>
  std::ostream& operator<<(std::ostream& os,
                  const std::pair<T1,T2>& p) {
    return os << "(" << p.first
              << "," << p.second << ")";
  }
}

except that adding declarations or definitions to namespace std is undefined behaviour according to the standard (Clause 17.4.3.1).

Of course the name lookup will find operator<<(pair) in the test harness namespace for a pair also in that namespace. The standard fully defines std::pair (Clause 20.2.2) so I can copy the source code to define my own pair (in my workspace) and expect identical behaviour. Although legal, this has a number of problems:

  • It breaks the rule of least surprise. A future maintainer may wonder why there are two identical pairs in different namespaces.

  • It requires the main product source code to use this new pair, and thus adds a dependency on the test harness code.

  • Although the two pairs are binary compatible, they are not interchangeable in source code without considerable scaffolding, and even then not fully.

To force the name lookup to find my operator<<(std::pair) without duplicating std::pair, I took Sven's wrapper class, PrintSpannerNameAndGap, and made it into a template class and output function:

template<class T>
class osformatter {
public:
  osformatter(const T& t) : t_(t) {}

  void print(std::ostream& os) const {os << t_;}
private:
  const T & t_;
};

template<class T>
std::ostream& operator<<(std::ostream& os,
                    const osformatter<T>& f) {
  f.print(os);
  return os;
}

and changed the line in the debug function to use it:

  copy(container.begin(), container.end(),
    ostream_iterator<osformatter<typename
       C::iterator::value_type> >(cout, " "));

This now works for all built-in types and any user defined types that define an operator<< in the same namespace.

Now it is possible to write a specialisation of osformatter for any type that does not support the output operator or for which we want some special formatting, for example, fixed precision doubles:

class osformatter<double> {
public:
  osformatter(const double& d) : d_(d) {}
  void print(std::ostream& os) const {
    int p=os.precision();
    os.precision(4);
    os << d_;
    os.precision(p);
  }
private:
  const double& d_;
};

I can now apply the same specialisation to std::pair, which, being a template itself, needs a template declaration for the types contained within it:

template<class T1, class T2>
class osformatter<std::pair<T1,T2> > {
public:
  osformatter(const std::pair<T1,T2>& p)
                                     : p_(p) {}
  void print(std::ostream& os) const {
    os << "(" << p_.first
       << "," << p_.second << ")";
  }
private:
  const std::pair<T1,T2>& p_;
};

This now compiles because the std::pair output code is contained in osformatter, and thus explicitly called, and so is no longer dependent on the name lookup rules.

This not only solved my name lookup problem but provided a nice way of changing the default output format of built-in types when using copy.

Regards,

Mark Easterbrook

Overload Journal #65 - Feb 2005 + Letters to the Editor