Pattern Form

One of the key goals of patterns is to capture the solutions to these reoccurring problems (and the constraints or context in which they can be used) in a manner which is easily accessible to others.

When we capture this information, we attempt to understand the underlying reasons about why this solution works. At this point, we can often generalize and gain a deeper understanding of the different aspects at work. In doing this “harvesting” of information, we often uncover related patterns that may also provide equal or greater value. In addition, by looking explicitly at the forces and other elements of the problem, we gain an amazing insight into the nature of software development.

Perhaps most pragmatically, and unlike traditional documentation approaches, patterns can be used to document systems at a higher level of abstraction, allowing dynamic, as well as static, behavior to be captured. The value of this form of documentation for knowledge transfer cannot be overstated.

A pattern can be written in one of several forms. While the exact form does not matter, keeping to some form takes one more thing off the pattern writer's mind and allows the writer to address the true problems. Regardless of the form used, every form should contain, in some manner, the following:

Name/Aliases: A pattern must be uniquely named so it can be internalized in the reader's mind. A name must be memorable and must evolve the system. A pattern, though, may have several names by which it is known. This sometimes creates confusion.

Problem: The form should include the specific problem that the pattern addresses. If we consider that the problem portion is a somewhat abstract statement of what we are trying to accomplish, then the solution is the mechanism we can use to accomplish our goal. Because a problem may have many solutions, how do we determine the best or the “correct” solution? Well, first we must add another piece to this equation—forces.

Forces: Forces are any considerations that must be taken into account. These are the things that make the problem hard and make the obvious solutions invalid. We can think of forces as considerations that the pattern must reconcile. There are an infinite number of forces present, so we must prioritize these forces into a context. Forces include environmental issues, language issues, organization issues, and platform issues. For example, a Telecom UNIX development effort using Java in a highly skilled development team may bring to bear different forces than a financial mainframe development effort using COBOL. When a pattern is selected, it resolves many of the forces but leaves with both strong and weak forces that remain to be resolved by other patterns.

A good pattern choice should resolve the forces still to be addressed. The pattern carves and builds on other patterns in the system to make them stronger. Much like the jigsaw puzzle in Figure 2.2, as more pieces are added, the puzzle as a whole becomes more stable. In addition, if the wrong piece is forced into the puzzle, it becomes more likely to spring up and break. Think of the puzzle as the system and the pieces as the patterns, and you can start to see system dynamics in new ways.

Figure 2.2. Forces apply pressure and reinforce the whole like jigsaw puzzle pieces

However, recognizing the forces at work can be extremely complex. One place where similar forces exist is in chess (see Figure 2.3). If we consider chess from one strategic view, it really is about reinforcing certain forces to cancel out other forces. The goal, of course, is to supply sufficient force to the opposing king and to areas around the king to guarantee that your opponent is overwhelmed.

Figure 2.3. Forces in chess Source: Visio Template by Dennis K. Fitzgerald, CIS 72627, 1442 template found on Web/Visio Group.

When patterns are combined appropriately, they should form a complex system that is in harmony. Each pattern should reinforce the others, making the system stronger with each addition. In this manner a system may continue to evolve without hitting the maintenance burden or breakdown effect that often results as the system continues to be enhanced. What we are hoping to do is to create a reinforcing framework (see Figure 2.4) where each added piece makes all of the other parts stronger.

Figure 2.4. A reinforcing framework

A pattern does not exist in a vacuum. In fact, a pattern depends not only on the specific pattern itself but also on every other pattern that is part of the total architecture. Each pattern composes itself of other pieces and creates dependents. In the same way that a small number of words leads to the expression of many complex ideas, a small number of patterns can yield many complex systems. The proper application of patterns should create a system that is in order and complete but still extensible. In all cases, a system of harmony should exist.

As you get more comfortable with patterns, you can start to look at any problem you are trying to solve as a diagram of forces and understand that anything you put in will have an impact. Hopefully, that impact will be to harmonize the forces you want to resolve.

Unfortunately in software, developers often approach a problem without really thinking about the forces at work or understanding the real problems at play. This results in a novice developer jumping into coding without really knowing how, or even if, a coding solution is appropriate. Taking a step back to understand what is really at work and then choosing the appropriate tools can save many thousands of lines of code and much wasted effort.

Often the real problems and forces are the unknowns, but we still have to develop our systems in spite of this. This is where the use of patterns is all the more essential. The unknowns become strong forces to which we must apply a pattern in order to resolve and reduce the risk of getting it wrong. By understanding the areas that are unknown, we can apply a pattern to provide for the variance when we finally understand the problem—this becomes the real challenge of design. If all the requirements were known up front and were unlikely to change, then design would be a trivial exercise. Luckily, few of us live in a world that is boring; instead we are faced with the constant challenge of change.

Context: Context includes any and all constraints on the solution usually formed by the application of other patterns to the system. Context serves to help prioritize strong and weak forces. Each pattern applied to a system further constrains subsequent patterns.

Solutions: The solution to the problem is within this context.

Motivation/Examples/Known Uses: It is important to have examples that the reader can understand and internalize. The key is for readers to internalize the pattern so they don't actively have to look things up to recall them. I recall Kent Beck clearly explaining at the first Pattern Language of Programming Conference I went to that the best (and only) way to learn is through stories. That is why examples or motivation is such a clearly essential part of the pattern. People will easily remember the pattern if they can associate a story with it.

Force Resolution: Force resolution includes the forces that are resolved by this pattern and those that are not. Any special handling should also be noted.

Resulting Context/Consequences: This form element includes what is left that still needs to be resolved after applying the solution.

Design Rationale: Design rationale is why this pattern works.

In the pattern snippets that I use from the Design Patterns book [Gam, 95], I add the relevant sections from their form. These include the following:

Varies: Varies indicates for what aspect of the system these patterns provide a hinge point. All GOF patterns support some level of variation.

Structure: This is a UML-based diagram of an example software structure diagram. These drawings, while useful, often fail to address adequately the dynamic nature of the pattern.

So let's consider two examples of the base form.

First, let's look at a common pattern used to describe memory reclamation. It was written to support the documentation of an approach we were using to develop a system at a company I was working for.

Pattern Name: Recycle Bin

Problem

How do you reduce the overhead of creation and destruction of resources?

Context

A number of resources are requested and released while the system is running.

Forces

  • Resource allocation, if left to the OS, may be expensive.

  • Resource allocation, if left to the OS, may be shared with others.

  • Resource allocation, if left to the OS, may cause fragmentation of the resource pool.

  • Dynamic allocation of resources may be unsafe or unavailable on some systems.

  • Small resource requests may have high overhead.

Solution

You can store freed resources in a local bin so that subsequent requests for these resources can reuse the ones in the bin. A client requests resources through the bin. The bin will reuse an existing bin, if available, or create a new one, if necessary. The recycle bin may request more resources than are actually needed to optimize performance. When the use of the resource is complete, it should be returned to the bin. A bridge (Gam, 96) may be introduced to separate the client's view of the resource from the actual resources so that the concept of the recycling bin can be completely hidden from the client. A recycle bin may create a fixed amount of resources up front or may request them on demand.

Resulting Context

We now have a system where resource allocation and deallocation may be controlled to whatever level the situation calls for.

Rationale

Resource requests normally go through the operating system. Kernel requests for resources are among the most expensive calls that can be made. Specifically, memory requests in a UNIX system can cause havoc on real-time system performance. In addition, users often misunderstand the overhead related to managing dynamic memory allocation. For example, a malloc of 2 bytes may carry an overhead that is at least 2 pointers in memory. The overhead cost is then more than the actual storage cost. Additionally, many operating systems use a minimum-size block so even more is wasted. By utilizing a recycle bin and managing the resources ourselves, no OS calls are required by the client, thus increasing our portability. All requests are essentially pointer assignments and calculations. Any additional overhead is completely controlled by the recycle bin, which can then take advantage of low-level optimizations without sacrificing portability of the clients.

Known Uses

This pattern has been used successfully for memory management in many systems that support the allocation of memory in large chunks. A similar approach is used in Sun's Hotspot Java environment through the use of nurseries.

Related Patterns

Bridge [Gam, 95]

Sketch



Author(s)

Brandon Goldfedder

Date(s)

9/25/95


Patterns don't have to be about writing software at all. Consider the following somewhat tongue-in-cheek pattern.

Pattern Name: Scream Test

Problem

How do you determine if an aspect of the system is in use?

Context

A system has a feature that is advantageous to phase out. This pattern also applies to networks in which it is unknown if active users are using a program. In all cases, a functional equivalence of the program should exist.

Forces

  • It is often difficult to tell if users are using a program or a feature or if its importance to the users after an alternative is available.

  • If you ask users, you will often get resistance to change.

  • Louder users may be more important than quieter ones; then again, they may not… .

  • The careless application of this pattern may reflect poorly on the implementer. This fear may keep it from being properly applied.

  • A help desk or other avenue for users to complain must exist.

Solution

Temporarily disable the feature (move the files, remove menu options, and so on), and place it into a hidden holding area. After a predetermined time, if no “significant” complaints are presented, the feature may be removed.

Resulting Context

Either a feature (program or option) is removed if unused, or its importance is better understood.

Rationale

If there are options available, users will often use the “better” alternative than do without. This encourages the users' exposure to the alternatives. If there are no options available, this pattern will cause this problem to surface as well.

Known Uses

Help desks and system shops everywhere.

Author(s)

Brandon Goldfedder


Patterns: The Language of Design

In developing software systems, we are starting to see the possibility of an evolution in the way in which we communicate. Program languages and our way of describing them began with machine code, a series of binary values (1s and 0s) to represent electronic switch placements. Each unique switch alignment could represent a command or a value represented by the machine. Common combinations of legal arrangements gained mnemonic meanings. For example, 0101 0001 might mean load the accumulator with the value 1. Assembly-level languages evolved to allow this to be specified as LDA 0x1. This more efficient mechanism allowed for increased productivity in communicating with the machine. Higher-level languages, such as FORTRAN and COBOL, continued to evolve, allowing domain-specific concepts[1] to be expressed by providing a syntax and semantics for expressing these higher concepts.

[1] FORTRAN's domain is primarily for scientific and engineering approaches. COBOL's domain is primarily for business.

This programming language evolution continued to evolve, allowing even more abstract concepts to be expressed. For example, structural programming languages (with the concept of modules) were replaced by languages that support object-based (such as Ada 83) and object-oriented (such as Smalltalk, C++, and Java) techniques. This language evolution was combined with common library reuse (such as those provided by the FORTRAN math library, C++ Standard Library, and Java's many common libraries). Unfortunately, for reasons to be explained shortly, language issues became a common area of disagreement and division among many IT professionals.

Recall, though, that the concepts we are describing, albeit at higher levels, are still what we want to communicate to the machine. No matter what we believe, we are describing an implementation that we want the machine to perform. Unfortunately, while we have greatly improved our ability to communicate to the machine, we have not improved our ability to communicate to other developers. Implementation languages are written for machines, not for other human beings.

What is needed is to describe high-level concepts in a way in which humans can communicate design efficiently. A system and a way in which to describe larger concepts could evolve by providing such a language. As a side effect, such a language would allow us to utilize our implementation languages, which our computer needs, much more efficiently.

To summarize, we need a language to allow us to communicate and discuss common recurring design concepts efficiently and to build upon these concepts. We are looking at a way to shift from the semantics that a machine requires to the content that we use to communicate among developers. Patterns provide the words of this new, common language (Figure 2.5). Don't think about patterns merely as the solution but rather as the words that can be combined by rules to form sentences (system designs). It is important as you continue to learn about patterns to keep this idea in mind.

Figure 2.5. Finding a common language


Consider the following scene, as illustrated in Figure 2.6: You sit in a design review meeting, and a majority of the meeting is taken up by several developers arguing about a specific function and whether a pointer or a reference should be used for passing parameters in C++ (if you don't use C++, insert your own programming language nuance here). This discussion continues to dominate the meeting and will probably arise again in future meetings.

Figure 2.6. A “design” discussion?


No doubt these types of discussions are useful, but is a design review meeting the proper place? I would not think so. Moreover, these types of conversations should be the exception rather than the rule in this type of review. That's not to say that these conversations don't have a place; they do. They should occur during a code review. It is essential to keep design reviews and code reviews separate. The real problem surfaces when these coding discussions displace the design talks that are necessary for a project's success.

If you consider the process of software development (discussed later in this chapter), it is clear that the actual implementation itself does not dominate the process. Why then is the specific programming language used? Well, it is partially because any programming language brings certain idioms (or language-specific patterns/techniques) to the table. Novice developers often do not understand the distinction between design and implementation, so these idioms are the only way they have to express design.

Why then do these things happen? To quote Maslov: “If the only tool you have is a hammer, you tend to treat everything as if it were a nail.” Most developers and architects began as coders and think in terms of the tool they are most familiar with, that is, the text editor for programming code. Design is primarily a communication activity. The problem is that developers are often extremely bad communicators; their interpersonal skills often play second fiddle to their coding capability. Often a developer who cannot communicate may serve to do far more harm than good on a project.

One observation that seems to recur whenever I come to a new project is that no matter how much experience the individual developers have when they come to the project, it often seems that they are starting from scratch. The discussions on technique, languages, notation, CM tools, and so on seem to occupy far more time than is justified at this point. The initial phase of the project resembles two modems trying to find a common protocol. This is actually part of any normal communication process where each party tries to identify a common language and frame of reference.

When developers have previously worked on a project together the situation changes to that shown in Figure 2.7.

Figure 2.7. An established team at work


This transition is very helpful because we now have a way to discuss a higher-level concept in terms that the developers can relate to. The problems with this concept are

  1. the developers may not remember things the same way;

  2. this does nothing to help developers who were not part of the same project; and

  3. there is no way to determine if it is necessarily the right thing to apply (see the earlier discussion about problems/solutions without a context).

SOAPBOX

Knowledge about existing systems is a double-edged sword. It can greatly benefit a project having people who have “been there; done that,” but often the domain or environment of the previous projects doesn't apply anymore. Unfortunately, these experienced developers may be blinded to this. One project I worked with at a telecommunication shop attempted to build a new switch scheduling system in the same manner as its previous switches. What the developers failed to consider was that the older systems had a custom-built operating system to guarantee real-time behavior. The new switch was based on a commercial UNIX system with a commercial database where context switching was unpredictable. Needless to say, this project was less than a 100 percent success (in spite of faster hardware, new advances, and so on). If the context had been considered more carefully, the results might have been better.


Once the team has started to understand and use patterns, the conversation can change to something like that in Figure 2.8.

Figure 2.8. A pattern-literate team at work


At this point, the vocabulary of the developers can emerge to take advantage of this newfound way to look at the systems. As the developers become more acclimated to speaking and thinking in terms of higher-level concepts, their ability to communicate abstract concepts and build better systems improves.

I often begin any new project with a few standard things in place:

  1. A set of coding guidelines, valid tools, notational rules, and so on

  2. An introduction to patterns course (often given in one really long day) to ensure that everyone on the team understands patterns and that they have the same understanding of terms

  3. Follow-up on design reviews and additional training to ensure proper application

I have found that these standards tend to reduce drastically the time required to build highly functional development teams. This team-based approach to training has been demonstrated time and again to pay for itself many times over [Gol, 96].

I believe that the improvement in design communication skills is one of the paramount advantages to using patterns. The ability to discuss these high-level concepts and to analyze the trade-off in design alternatives is an invaluable one for building robust extensible systems.

Documentation

One of the most troublesome activities in system development is creating good, meaningful documentation. Part of this problem (aside from the pure complexity of the systems themselves) stems from the vocabulary we use to discuss system design; part of it is historical. Most documentation techniques arise from large-scale projects that evolved from governmental standards, such as the infamous 2167.[2] These forms of documentation can do a relatively good job describing the components of the system itself. However, they tend to lend themselves to structured design principles. They do very little to describe the state/data of components. In addition, they fail to describe the interactions between objects. In describing complex systems and frameworks, I have found no other effective manner in which to convey the system design so that others may quickly understand and extend them.

[2] 2167 is one of the earlier government standards of how to rigorously document a system. It was superceded by 2167A, Mil-Std-SDD, and other standards.

To summarize this concept with a quote from Taligent [Tal, 94]:

By describing the design of a framework in terms of patterns, you describe both the design and the rationale behind the design.

As a example, consider the following two options to describe how to find a number in a sorted list of values:[3]

[3] Algorithms are not patterns, but I find this a great analogy to describe their benefits in a simple way.

  1. Divide the list in half. Compare the value we are looking for to the middle element. If it is equal, then we have found it. If it is less than the middle value, set the new top position equal to the middle position (dividing it in half again). If it is more than the middle value, set the new bottom position equal to the middle position. Continue this division until the list cannot be divided any further. At this point if it isn't in the last two positions, it is not in the list.

  2. Use binary search.

Note that the second value is more precise and easier to understand, and it is valid to use in documentation, if the following assumptions are made:

  • Readers know what binary search is.

  • Readers can find an implementation in a reuse library, an algorithms textbook, or their own memory.

Extensible Software Development and Change Management

One of the major failings with many development methodologies is that they fail to consider the impact of requirement changes that will occur as the product evolves. For example, several notable OO methods work great in the classroom where requirements are known up front and can never change, but they fail miserably in the real world of changing user needs.

One of the key goals in development is Meyers's Open-Closed Principle [Mey, 88]. This principle states that a system remain open for extension but closed for use. Simply put, “If it ain't broke, don't fix it!”

To extend a system with new functionality, you will want to write the new functionality and modify exactly one part of the existing system, the part where you decide to use this new functionality. By moving in this direction, a single point of maintenance is achieved. I like to think about this simply as being able to plug in new implementations and to extend a system much in the way that additional electronic devices can be plugged into an extension cord.

I find this way of viewing a system to be so important that I focus on it in detail throughout this book. Patterns allow us to take advantage of this principle to create large, robust systems and frameworks.

Following is a pseudocode fragment that does not subscribe to the Open-Closed Principle:

LegoSystem::processColor()
{
switch(color)
   {
    case RED:
         redprocess();
         break;
    case GREEN:
         greenprocess();
         break;
    case YELLOW:
         yellowprocess();
         break;
    }
}

Adding a new color such as orange obviously causes the code that handles green, red, and yellow to be modified. This could be resolved by the use of a State pattern [Gam, 95] that reifies (or turns into an object) this concept of color.

State Pattern

Intent

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

Varies

The internal state of an object varies.

Structure

Figure 2.9 illustrates a State structure

Figure 2.9. State structure


Comments

Use a State pattern to handle an enumerated list anywhere you see two or more similar case structures in a system. While enumerated lists make excellent candidates for the State pattern, consider carefully if it makes sense in boolean cases.

One other common consideration is whether to have the state created within the object or to use a complementary pattern such as a flyweight pattern [Gam, 95]. (A flyweight is used to pool object state, allowing a very lightweight manner to handle many objects. At the other extreme is the singleton, which avoids multiple copies of an object being created by sharing a single instance [Gam, 95].)

The nominal case is where the context selects the state. However, the State pattern can also be used where the next state to utilize is decided by the existing case. I have used this mechanism to allow rule-processing trees of system state for call processing to be loaded dynamically.

A finite state machine or transition system is usually required for many telecommunications switching systems. We have found that simply applying a state pattern achieves all the benefits of the home-grown complex solutions without most of the development costs. Basically, we are allowing virtual methods and the resulting jump tables produced by the compiler to provide what used to require detailed hand coding. Figure 2.10 illustrates an improved extensible structure.

Figure 2.10. An improved extensible structure


The code now becomes something similar to the following:

LegoSystem::processColor
{
    color->process()
}
Red::process()
{
    redprocess();
}
Yellow::process()
{
    yellowprocess();
}
Green::process()
{
    greenprocess();
}

Notice that we really haven't added any additional implementation code but rather have provided a support framework that allows us to graft on the additional colors easily. The real advantage increases when we combine multiple areas with case statements, allowing us to group the behavior into a common class; we can now easily add and modify state-specific behavior.

When we look at a large system, the key is to identify those points where future extension is likely to occur. For these, we can use patterns to act as “hinge points” in which new functionality can be added. Once we understand the variability we need, we find that we can utilize patterns to provide it.

Most important, by allowing us to provide this separation of the parts of the system, we can better handle multiple developers, testing, and distribution of systems. This book will address these issues throughout.


Training

As with most developers, I began my career working on existing systems, maintaining and extending them. The normal process I discovered when dealing with huge systems was to find a piece of code that sort of did what you wanted, copy it, and then tweak it to get the final product. I would like to pretend that with our understanding of reuse and framework development techniques today that this approach has been completely eliminated from any software shops. The problem was that developers understood how to do certain things but not really why. When the operating system evolved, obsolete code continued to be used and written, with no one really understanding why. Patterns solve this problem in part by providing a solution along with the context so that the “why” comes across along with the “how.” The risk in applying a solution without understanding why it works is that it may be misapplied in inappropriate situations.

With the high amount of transition among IT professionals, the concern over knowledge transfer is paramount. Several efforts to capture the information assets are ongoing, including the creation of “Best Practices.” For example, several telecommunications companies fear that new developers will not learn new lessons as the generation that built the original infrastructure begins to retire. The use of patterns is essential to capture this essential knowledge.

Silver Bullets

Let's get this out now and be done with it: Patterns are not the mystical silver bullet that will resolve all of our issues in software development. Instead, it is better to think of patterns as an essential tool to add to our arsenal. They can resolve many of our communication issues, provide a well-proven set of knowledge, and allow us to jump start many development activities.

Most important to consider is that patterns are relatively new. I include a brief time line of events and publications that illustrate this fact.

A Selected History of Patterns
1964 Christopher Alexander publishes Notes on the Synthesis of Form [Ale, 64], which attempts to look at the process of architecture in a different light.
1977 Christopher Alexander publishes A Pattern Language [Ale, 77].
1987 Ward Cunningham and Kent Beck begin applying some of the architectural concepts to software development in SmallTalk.
1992 Jim Coplien publishes Advanced C++: Programming Style and Idioms [Cop, 92].
1992 Peter Coad publishes his work on analysis patterns in ACM [Coa, 92].
1993 Erich Gamma's Ph.D. thesis with additional work from John Vlissides, Ralph Johnson, and Richard Helm is presented at ECOOP 93. This forms the basis of Design Patterns. [Gam, 93].
1993 Kent Beck, Grady Booch, Jim Coplien, and others formed the Hillside Group to provide a forum for discussion of patterns.
1994 First PLoP (Pattern Language of Programming) conference is held.
1994 Design Patterns: Elements of Reusable Object-Oriented Software is published. It is still considered by some to be the best OO book of all time.
1996 Frank Buschmann and others publish Pattern-Oriented Software Architecture: A System of Patterns [Bus, 96].
1997 Martin Fowler publishes Analysis Patterns: Reusable Object Models.
1999 Martin Fowler and others publish Refactoring: Improving the Design of Existing Code [Fow, 99].
2000 Linda Rising's The Pattern Almanac [Ris, 00], cataloging published patterns, is made available.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.74.54