Protected Variations - UML

Problem

How to design objects, subsystems, and systems so that the variations or instability in these elements does not have an undesirable impact on other elements?

Solution

Identify points of predicted variation or instability; assign responsibilities to create a stable interface around them.

Note: The term "interface" is used in the broadest sense of an access view; it does not literally only mean something like a Java interface.

Example

For example, the prior external tax calculator problem and its solution with Polymorphism illustrate Protected Variations. The point of instability or variation is the different interfaces or APIs of external tax calculators. The POS system needs to be able to integrate with many existing tax calculator systems, and also with future third - party calculators not yet in existence.

By adding a level of indirection, an interface, and using polymorphism with various TaxCalculator Adapter implementations, protection within the system from variations in external APIs is achieved. Internal objects collaborate with a stable interface; the various adapter implementations hide the variations to the external systems.

Discussion

This is a very important, fundamental principle of software design! Almost every software or architectural design trick in book - data encapsulation, polymorphism, data - driven designs, interfaces, virtual machines, configuration files, operating systems, and much more - is a specialization of Protected Variations.

Protected Variations (PV) was first published as a named pattern by Cockburn in [VCK96], although this very fundamental design principle has been around for decades under various terms, such as the term information hiding [Parnas72].

Mechanisms Motivated by Protected Variations

PV is a root principle motivating most of the mechanisms and patterns in programming and design to provide flexibility and protection from variations - variations in data, behavior, hardware, software components, operating systems, and more.

At one level, the maturation of a developer or architect can be seen in their growing knowledge of ever - wider mechanisms to achieve PV, to pick the appropriate PV battles worth fighting, and their ability to choose a suitable PV solution. In the early stages, one learns about data encapsulation, interfaces, and polymorphism - all core mechanisms to achieve PV. Later, one learns techniques such as rule - based languages, rule interpreters, reflective and metadata designs, virtual machines, and so forth - all of which can be applied to protect against some variation.

For example : Core Protected Variations Mechanisms

Data encapsulation, interfaces, polymorphism, indirection, and standards are motivated by PV. Note that components such as virtual machines and operating systems are complex examples of indirection to achieve PV.

Data - Driven Designs

Data - driven designs cover a broad family of techniques including reading codes. values, class file paths, class names, and so forth, from an external source in order to change the behavior of, or "parameterize" a system in some way at runtime. Other variants include style sheets, metadata for object - relational mapping, property files, reading in window layouts, and much more. The system is protected from the impact of data, metadata, or declarative variations by externalizing the variant, reading it in, and reasoning with it.

Service Lookup

Service lookup includes techniques such as using naming services (for example, Java's JNDI) or traders to obtain a service (for example, Java's Jini, or UDDI for Web services). Clients are protected from variations in the location of services, using the stable interface of the lookup service. It is a special case of data - driven design.

Interpreter - Driven Designs

Interpreter - driven designs include rule interpreters that execute rules read from an external source, script or language interpreters that read and run programs, virtual machines, neural network engines that execute nets, constraint logic engines that read and reason with constraint sets, and so forth. This approach allows changing or parameterizing the behavior of a system via external logic expressions. The system is protected from the impact of logic variations by externalizing the logic, reading it in, and using an interpreter.

Reflective or Meta - Level Designs

An example of this approach is using the java.beans.Introspector to obtain a Beanlnfo object, asking for the getter Method object for bean property X, and calling Method.invoke. The system is protected from the impact of logic or external code variations by reflective algorithms that use introspection and meta - language services. It may be considered a special case of data - driven designs.

Uniform Access

Some languages, such as Ada, Eiffel, and C#, support a syntactic construct so that both a method and field access are expressed the same way. For example, aCircle.radius may invoke a radiusQ:float method or directly refer to a public field, depending on the definition of the class. We can change from public fields to access methods, without changing the client code.

Standard Languages

Official language standards such as SQL provide protection against a proliferation of varying languages.

The Liskov Substitution Principle (LSP)

LSP [Liskov88] formalizes the principle of protection against variations in different implementations of an interface, or subclass extensions of a superclass. To quote:

What is wanted here is something like the following substitution property: If for each object ol of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when ol is substituted for o2 then S is a subtype of T [Liskov88]. Informally, software (methods, classes, ...) that refers to a type T (some interface or abstract superclass) should work properly or as expected with any substituted implementation or subclass of T - call it S. For example:

public void addTaxes (iTaxCalculatorAdapter calculator.Sale sale) {
List taxLineltems = calculator.getTaxes(sale);
// ... }

For this method addTaxes, no matter what implementation of ITaxCalculator Adapter is passed in as an actual parameter, the method should continue to work "as expected." LSP is a simple idea, intuitive to most object developers, that formalizes this intuition.

Structure - Hiding Designs

In the first edition of this book, an important, classic object design principle called Don't Talk to Strangers or the Law of Demeter [Lieberherr88] was expressed as one of the nine GRASP patterns. Briefly, it means to avoid creating designs that traverse long object structure paths and send messages (or talk) to distant, indirect (stranger) objects. Such designs are fragile with respect to changes in the object structures - a common point of instability. But in the second edition the more general PV replaced Don't Talk to Strangers, because the latter is a special case of the former. That is, a mechanism to achieve protection from structure changes is to apply the Don't Talk to Strangers rules.

Don't Talk to Strangers places constraints on what objects you should send messages to within a method. It states that within a method, messages should only be sent to the following objects:

  1. The this object (or self).
  2. A parameter of the method.
  3. An attribute of this.
  4. An element of a collection which is an attribute of this.
  5. An obiect created within the method.

The intent is to avoid coupling a client to knowledge of indirect objects and the object connections between objects.

Direct objects are a client's "familiars," indirect objects are "strangers." A client should talk to familiars, and avoid talking to strangers.

Here is an example that (mildly) violates Don't Talk to Strangers. The comments explain the violation.

class Register
private Sale sale;
public void slightlyFragileMethod() {
// sale.getPayment() sends a message to a "familiar" (passes #3)
Protected Variations
// but in sale.getPayment().getTenderedAmount()
// the getTenderedAmount{) message is to a "stranger" Payment
Money amount = sale.getPayment().getTenderedAmount();

This code traverses structural connections from a familiar object (the Sale) to a stranger object (the Payment), and then sends it a message. It is very slightly fragile, as it depends on the fact that Sale objects are connected to Payment objects. Realistically, this is unlikely to be a problem.

But, consider this next fragment, which traverses farther along the structural path:

public void moreFragileMethod()
AccountHolder holder = sale.getPayment().getAccount().getAccountHolder();
// ... }
Or more generally:
public void doX()
{
F someF = foo.getAO .getBf) .getCO .getD() .getE() .getF();
}

The example is contrived, but you see the pattern: Traversing farther along a path of object connections in order to send a message to a distant, indirect object - talking to a distant stranger. The design is coupled to a particular structure of how objects are connected. The farther along a path the program traverses, the more fragile it is. Why? Because the object structure (the connections) may change. This is especially true in young applications or early iterations.

Karl Lieberherr and his colleagues have done research into good object design principles, under the umbrella of the Demeter project. This Law of Demeter (Don't Talk to Strangers) was identified because of the frequency with which they saw change and instability in object structure, and thus frequent breakage in code that was coupled to knowledge of object connections.

Yet, as will be examined in the following "Speculative PV and Picking your Battles" section, it is not always necessary to protect against this; it depends on the instability of the object structure. In standard libraries (such as the Java libraries) the structural connections between classes of objects are relatively stable. In mature systems, the structure is more stable. In new systems in early iteration, it isn't stable.

In general, the farther along a path one traverses, the more fragile it is, and thus it is more useful to conform to Don't Talk to Strangers.

Strictly obeying this law - protection against structural variations - requires adding new public operations to the "familiars" of an object; these operations provide the ultimately desired » information, and hide how it was obtained. For example, to support Don't Talk to Strangers for the previous two cases:

// case 1
Money amount = sale.getTenderedAmountOfPayment ();
// case 2
AccountHolder holder = sale.getAccountHolderOfPayment ();

ContraindicationsCaution: Speculative PV and Picking Your Battles

First, two points of change are worth defining:

  1. variation point :- Variations in the existing, current system or requirements, such as the multiple tax calculator interfaces that must be supported.
  2. evolution point :- Speculative points of variation that may arise in the future, but which are not present in the existing requirements.

PV is applied to both variation and evolution points.

A caution: Sometimes the cost of speculative "future - proofing" at evolution points outweighs the cost incurred by a simple, more "brittle" design that is reworked as necessary in response to the true change pressures. That is, the cost of engineering protection at evolution points can be higher than reworking a simple design.

For example, I recall a pager message - handling system where the architect added a scripting language and interpreter to support flexibility and protected variation at an evolution point. However, during rework in an incremental release, the complex (and inefficient) scripting was removed - it simply wasn't needed. And when I started 00 programming (in the early 1980s) I suffered the disease of "generalize itis" in which I tended to spend many hours creating superclasses of the classes I really needed to write. I would make everything very general and flexible (and protected against variations), for that future situation when it would really pay offwhich never came. I was a poor judge of when it was worth the effort.

The point is not to advocate rework and brittle designs. If the need for flexibility and protection from change is realistic, then applying PV is motivated. But if it is for speculative future - proofing or speculative "reuse" with very uncertain probabilities, then restraint and critical thinking is called for.

Novice developers tend toward brittle designs, intermediate developers tend toward overly fancy and flexible, generalized ones (in ways that never get used). Expert designers choose with insight; perhaps a simple and brittle design whose cost of change is balanced against its likelihood.

Benefits

Also Known As; Similar To

  1. Extensions required for new variations are easy to add.
  2. New implementations can be introduced without affecting clients.
  3. Coupling is lowered.
  4. The impact or cost of changes can be lowered.
  5. Most design principles and patterns are mechanisms for protected variation. including polymorphism, interfaces, indirection, data encapsulation, most of the GoF design patterns, and so on.
  6. In [Pree95] variation and evolution points are called "hot spots."

PV is essentially the same as the information hiding and open - closed principles, which are older terms. As an "official" pattern in the pattern community, it was named "Protected Variations" in 1996 by Cockburn in [VCK96].

Information Hiding

David Parnas's famous paper On the Criteria To Be Used in Decomposing Systems Into Modules [Parnas72] is an example of classics often cited but seldom read. In it, Parnas introduces the concept of information hiding. Perhaps because the term sounds like the idea of data encapsulation, it has been misinterpreted as data encapsulation, and some books erroneously define the concepts as synonyms. Rather, Parnas intended information hiding to mean hide information about the design from other modules, at the points of difficulty or likely change. To quote his discussion of information hiding as a guiding design principle:

We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others.

That is, Parnas's information hiding is the same principle expressed in PV, and not simply data encapsulation - which is but one of many techniques to hide information about the design. However, the term has been so widely reinterpreted as a synonym for data encapsulation that it is no longer possible to use it in its original sense without misunderstanding.

Open - Closed Principle

The Open - Closed Principle (OCP), described by Bertrand Meyer in [Meyer88], is essentially equivalent to the PV pattern and to information hiding. A definition of OCP is:

Modules should be both open (for extension; adaptable) and closed (the module is closed to modification in ways that affect clients).

OCP and PV are essentially two expressions of the same principle, with different emphasis: protection at variation and evolution points. In OCP, "module" includes all discrete software elements, including methods, classes, subsystems, applications, and so forth.

In the context of OCP, the phrase "closed with respect to X" means that clients are not affected if X changes. For example, "the class is closed with respect to instance field definitions" through the mechanism of data encapsulation with private fields and public accessing methods. At the same time, they are open to modifying the definitions of the private data, because outside clients are not directly coupled to the private data.

As another example, "the tax calculator adapters are closed with respect to their public interface" through implementing the stable ITaxCalculatorAdapter interface. However, the adapters are open to extension by being privately modified in response to changes in the APIs of the external tax calculators, in ways that do not break their clients.


All rights reserved © 2018 Wisdom IT Services India Pvt. Ltd DMCA.com Protection Status

UML Topics