Sunday, June 17, 2012

Bindings and Coupling

Short post today since I just got back from a nice beach vacation. The next Practical Patterns installment should be up shortly, but first, quick detour to explain a couple of concepts that arise frequently when trying to describe patterns: binding and coupling.

When we describe the design and behavior of an object-oriented system, we are mostly interested in how the various objects interact with each other. Patterns, as an example, formalize a set of well-known and well-understood interactions between various objects in a system. In general, there are two different kinds of object-object interactions that we need to understand: the design-time, or architectural, interactions, and the run-time, or implementation, interactions.

The related concepts of binding and coupling describe various aspects of these interactions at run-time and design-time, respectively. Understanding them is a large part of understanding the benefit we get from patterns, so lets take a look.

Binding


Binding, specifically name binding, is one of the key operations performed during the translation of source code into something executable. It is the process that associates the name of a method at the point where it is used (the call site) with a specific block of named code implemented somewhere else (the call target). This process involves a number of smaller steps, including overload resolution, type verification, scope checking, and in general, everything required to uniquely identify one particular method block with a particular local name.

Since almost any programming language will have some form of name binding, the process needs to adapt to the particular details of each implementation. As such, there are a number of variations on name binding that are used in different situations. C# supports a number of different forms of binding, depending on your needs.

Perhaps the most well-known variation on the binding process is that of early binding vs. late binding. The difference between the two depends on what part of the application's life cycle is responsible for doing the name binding. For the most part, compiled languages use early binding by default, which means that the compiler and linker are responsible for performing all name binding operations up front, at build time. This is called early binding because the binding is done at the earliest possible moment - as soon as the compiler or linker has the information it needs to do the binding - and the information is encoded into the binary. The alternative is late binding, in which the binding operation is deferred until the latest possible moment - the actual execution of the method call at run time. Interpreted languages (such as Visual Basic or JavaScript) support this form of binding, because there is no compilation involved. In C#, you can perform late binding in several ways. First, you can expose your C# objects to late-bound languages using COM, via it's IDispatch interface. This interface exposes a single method, which is used behind the scenes by early-bound languages, which then looks up the actual desired method name at run-time and executes it. Late-bound languages that support COM appear to be calling the named methods directly, but internally the COM system converts these calls to run time dispatch calls. You can also call late-bound methods using reflection, which allows you to scan metadata at run time and locate the method calls, or by using dynamic objects, which does essentially the same thing automatically.

Another common distinction in the binding process is static binding vs. dynamic binding. Dynamic binding and late binding are often used interchangeably, but the two represent very different things. In a static bound method call, the compiler has enough information to directly encode the call target's location at the call site, which then never changes. This is how C# performs method calls for most object methods. The alternative, dynamic binding, defers the final decision about which method actually runs until later. The difference between this and late binding is that a late-bound method call could refer to a name that doesn't even exist, or whose method signature is completely wrong; but if the method exists, there is usually only one possible method that can be. In dynamic binding, the compiler is able to guarantee that a method call is legal at compile time, but there may be several alternatives that do not get selected from until later. A common case of this type of binding is dynamic dispatch, used in most implementations of object inheritance for virtual methods. In this case, the name binding associates a call site with an entry in the target object's virtual method table, performing an indirect call. The compiler can guarantee, because of the rules of inheritance, that some method will exists at the target location. However, derived objects can replace the base class method with an overriden one, updating the virtual method table and changing the actual run time call to a different method.

There is a third type of binding distinction, which I will refer to here as tightly bound vs. loosely bound calls. This is not standard terminology, and in fact I don't know if there is a generally accepted name for this distinction, but we'll use these terms anyway for reasons that will become clear later. The vast majority of bindings in a compiled language are tightly bound, meaning that the call site has a lot of information about the type of the call target. By contrast, a loosely-bound call has only very limited amount of type information available about its call target. In C#, loosely-bound method calls are implemented via delegates and events: the call site has almost no knowledge of the things that are bound to the other end of a delegate, other than the fact that there is a legal block of code there. (It may not even be a class method - the delegate could be anonymous.)

Name binding, all of these variations, are implementation details that depend almost entirely on the language, tools, and run time environment. In each case, the binding process determines how two object instances in a given system interact with each other. The equivalent characteristic of a system at design time is call coupling.

Coupling


Coupling, like binding, describes the ways in which two elements in an object oriented system interact. Unlike binding, however, coupling describes this relationship at the class definition level. Instead of a simple either-or process, the degree if coupling between two classes exists along a spectrum that ranges from loosely coupled to tightly coupled.

In a tightly coupled system, two classes require an in-depth knowledge of each other's behavior in order to properly interact. This is typical of simplistic or "quick and dirty" development, where calling objects are fully aware of the types and behaviors of the objects being called. Typical indications of a tightly coupled systems include shared global data, specific order-of-operation requirements, or the callee making assumptions about the type of caller. For example, it is a common technique in a Windows Forms application for methods to typecast their sender parameter to a specific type of control, say a TextBox. By doing so, the code has tightly bound the caller and callee of that method together: the Form instance knows that only a TextBox instance will ever call that method. Similar, two classes which maintain a parent-child relationship with each other are bound tightly to each other.

On the opposite end of the spectrum is loose binding, in which each objects knows only the base minimum it needs to know about the other objects in the system. In C#, the loosest coupling is achieved by use of events. Unlike loosely bound objects, however, loose coupling via events requires designing the classes with this exact intention in mind. The object which exposes the event must supply all relevant information in the event's argument: the caller must not rely on any type information about the event source to perform the required operations.

Somewhere in the middle of these two options, though definitely on the loosely-coupled side, is the use of interfaces. This technique provides a good middle ground between the two extremes: the interaction can be as complex as needed, encapsulating several related behaviors together, while still hiding the details of an objects implementation from its consumers. Similarly, the concept of object inheritance provides an intermediate, but still tightly coupled, option, where a caller knows all of the information about the base class of a call target, but may not be aware of the actual implementation being used at runtime.

Tightly coupled designs are common in smaller and simpler applications, because they are generally easier to design and build. They are often more efficient and performant, since there is almost no overhead involved in making method calls. However, there are a significant number of problems that eventually fall out of such designs that prevents them from scaling well.

Tight coupling, for starters, makes it difficult to reuse code effectively. Too many shared assumptions reduces the number of situations where a given piece of code can fit. It also increases maintenance costs, as the code becomes very fragile and sensitive to changes in other areas that may break those assumptions. Lastly, it tends to produce code that is difficult to unit test, as it is impossible to swap out any part of the code for a
test version without fundamentally changing the behavior.

Design Patterns: Loosely Coupled Everything


One of the main goals of most design patterns is to enforce loose coupling between all objects in a system. Patterns rely heavily on things like interfaces and events to define the interaction between objects in a system to keep the coupling as loose as possible. Many patterns, like the AbstractFactory pattern, exist for the sole purpose of allowing two other areas of the system to interact while knowing as little as possible about each other, even down to creating instances of objects without knowing their actual type.

When we take a look at the more complex patterns, starting with the common Observer pattern that's up next, keep this concept in mind. Often times a pattern seems to be more complex than strictly necessary, but much of that is to help encourage or enforce a loose coupling between objects.

0 comments: