Multi-paradigm design and generic programming

I want to step away from coding, talking about .NET altogether, and talk about abstraction, domain engineering, generic programming, and design paradigms for a bit. In 1999, I read a book by Jim Coplien, “Multi-Paradigm Design for C++”, which forever changed the way I looked at design paradigms. Lately, while reading Andrei Alexandrescu’s “Modern C++ Design: Generic Programming and Design Patterns Applied”, combined with doing a lot of “Modern C++”, I had several awakenings those shook up things for me even more.

            For years now, it seems to me, we have had many design “gurus” repeating a mantra “Object Oriented Design good, all other techniques  bad”, to the extent that for many developers and designers, that is the only paradigm that they use and even know. Abstraction is one of the key tools for managing the ever-increasing complexity of modern software. As Coplien says, “The common answer to what is abstraction usually has something to do with objects, thereby reflecting the large body of literature and tools that have emerged over the past decade to support object-oriented techniques.” Even more so, frameworks, languages and abstractions like .NET’s BCL and most of Java is only OO based (yes, I had been thinking about this for 2 weeks now, before Microsoft’s vapor announcement of future generic programming features in .NET). Java is touted, rightly or wrongly, as completely OO such that everything must be in a class. NET’s BCL is mostly only OO. Both of these, for the most part, wrap your abstraction and your way of thinking in object-oriented ways. Yes, there are exceptions (no pun intended!), but for the most part, most of the world thinks in objects. Well, that’s goodness, right? After all, it models the real world and builds abstractions much better, right? Well, yes and no. There’s no denying the vast benefits of OO but the key message in these books, and particularly Coplien’s book, is that Object Oriented Design is just one subset of the solution domain and not always appropriate for the problem at hand.

Furthermore, as Alexandrescu states “Software engineering exhibits a rich multiplicity: You can do the same thing in many correct ways, and there are infinite nuances between right and wrong. Each path opens up a new world. The design of a software system is a choice of solutions out of a combinational solution space.” This typically confuses apprentice developers. “The most important difference between an expert software architect and a beginner is knowledge of what works and what don’t.” How do you choose? He contends that neither multiple inheritance nor generic programming in the form of templates will create a flexible design paradigm to handle the combinational explosion; that combining the two in what are known as Policies and Policy Classes will.

            Coplien’s thesis, I think is that to solve a problem, you must analyze two domains: the problem domain and the solution domain. The problem domain is approximated by the functional specification of a product, but analysis of the problem domain can be generalized so it encompasses the limits of the business market and company market goals. The solution domain is the set of tools available: object-oriented design, traditional functional decomposition, generic programming (C++ templates), frameworks, and design patterns.

            Many people incorrectly assume C++ is only an Object Oriented language and a bad one at that. They look at not being a “pure” OO language like Java or C#. But it was never designed to be only an OO language and that has important ramifications for things like Multi-Paradigm design, as I have re-learned. C++, was designed to, and supports multi-paradigms well.  C++ has templates, overloaded functions, inheritance, and virtual functions – a rich collection of functions to express a broad spectrum of patterns of commonality and variability. As Coplien says, “All other languages debates aside, one advantage of C++ is that it directly expresses the intent of the analysis on a way that Smalltalk can only do by convention.” Then there is the complexity argument. In many regards, the complexity is mostly there because most C++ programmers (because of companies like Microsoft) use C++ as a better C, and to this point have not made sufficient use of the abstractions made available by the Standard library, which turn unreadable masses of page long pointer manipulations into 4 lines of elegant, abstracted readable code. It is not my intention to defend C++ here, nor deny the vast programmer benefits that come from the use of managed code like .NET, C# and Java. Nor do I deny the benefits of providing tools for the masses. But I do wonder about narrowing the abstraction paradigm to only OO and limiting the solution domain. In the hands of an experienced designer, there is a whole lot more power and flexibility afforded when designing with more than one design paradigm.

            Coplien talks about the two domain spaces being characterized as N-dimensional spaces, where each dimension is a “variability” or “parameter” in the problem space or in the tool set modeled by the solution space. Often, identification of a commonality in a domain points to a dimension of variability, or vise versa. Domains may be decomposed into sub-domains. Once, you identify a set of dimensions of variability in the problem space, and then the final step is to transform or map the dimensions of the problem space to the dimensions of the solution space.

            In a C++ example, there is the technique of function overloading that adds a dimension to the solution space. That dimension represents variability of algorithm, but not of function name or semantics. C++ templates add a different dimension to the solution space, predominantly uncorrelated with the preceding one. Along the dimension added by templates, there is variability in data type, but not in algorithm or behavior. Now, going over to the problem domain, suppose we find several ways in which we must sort data, although there are several ways to sort data. That requirement, which could be considered a sub domain of the overall problem space, lives in a domain space in which the principal dimension is variability in data type (but commonality in algorithm). Mapping dimensions from two domain spaces, we see that the principal dimension of the sorting sub domain maps readily onto the dimension of variability that C++ templates provide. Fancy way to say solve the problem with a function such as template sort<T>(). There is a whole lot more to this, but I think you get the idea.

            Now, back over to Alexandrescu. Let’s use his example of assembling a multi-threaded, reference counted smart pointer abstraction or class. Using Multiple Inheritance, one might inherit some BaseSmartPointer class and two classes: MultiThreaded and RefCounted. Any experienced class designer knows that such a naïve design does not work. Why? Mechanics are such that the there is no control in putting these things together. “The language applies simple superposition in combining the base classes and establishes a set of simple rules for accessing their members.” The classes don’t have enough type information. On the other hand, templates will not let specialize the structure of classes, only functions, and does not scale. Combining the two into policies does provide a safe, efficient and highly customizable design element. A policy “defines a class interface or a class template interface. “

            For instance, one could define a Creator policy to creates objects of type T. Then two possible implementations (policy classes) might be:

template <class T>

struct OpNewCreator

{

     static T* Create()

     {

          return new T;

     }

}

 

template <class T>

struct MallocCreator

{

     static T* Create()

     {

          void buf = std::malloc(sizeof(T));

          if (!buf) return 0;

          return new(buf) T;

     }

}

 

What does that let us do? How about being able to write host classes like so:

 

template <class CreationPolicy>

class WidgetManager: public CreationPolicy

{

     ....

}

typedef WidgetManager<OpNewCreator<Widget> > MyWidgetManager;

 

Here is the power: Whenever an object of type MyWidgetManager needs to create a Widget, it invokes Create() for its OpNewCreator<Widget> policy sub object. However, it is the user of WidgetManager who chose the creation policy. “Effectively, through its design, WidgetManager allows its users to configure a specific aspect of WidgetManager’s functionality. This is the gist of policy-based design.”

            It gets better. When you combine policies, you can implement all sorts of behavior and enforce certain behaviors. Back to the Smart Pointer.

 

 

template

<

     class T,

     template <class> class CheckingPolicy,

     template <class> class ThreadingModel,

>

class SmartPtr : public CheckingPolicy<T>. public ThreadingModel<SmartPtr>

 

Then you can combine a behavior like ensuring that pointers are not null and such, with a threading model.

 

What can you do with this? A lot of the things John Lam is talking about with AOP. Code generation, enforcing constraints and company standards, supplying default logging and error handling and much, much more.

 

There is much more to this area and I am just learning myself but I hope that I have shown that how domain engineering can be used as a basis for abstraction techniques that go far beyond the subset that is object-oriented analysis and design.