• No results found

SOFTWARE DESIGN PATTERNS

Software Design

4.2 SOFTWARE DESIGN PATTERNS

One possible way of designing software is to attempt to match the problem to be solved with a preexisting software system that solves the same type of problem. This approach reduces at least a portion of the software design to pattern matching. The effectiveness depends upon a set of previously developed software modules and some way of recognizing different patterns.

Following are some types of patterns that seem to repeatedly occur in software develop- ment. Our patterns are relatively high level, as opposed to the patterns described in what is commonly called the “Gang of Four Book” after its four authors, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Gamma et al., 1995). These high-level patterns are 1. A menu-driven system, where the user must pass through several steps in a hierarchy

in order to perform his or her work. The menus may be of the pull-down type such as is common on personal computers or may be entirely text based.

2. An event-driven system, where the user must select steps in order to perform his or her work. The steps need not be taken in a hierarchical order. This pattern is most commonly with control of concurrent processes where actions may be repeated indefinitely, in contrast to pattern 3. It is also typical of a user interface that is guided by selection of options from both menus and combinations of keystrokes, such as in menus that may pop-up in response to user requests. In any case, there is less of a hierarchy than in the previous pattern.

3. A system in which the actions taken depend on one of a small number of “states” and a small set of optional actions that can be taken for each state. The optional action taken depends on both the state and the value of an input “token.” In this pattern, the tokens are usually presented as a stream. Once a token is processed, it is removed from the input stream.

4. A system in which a sequence of input tokens (usually in text format) is processed, one token at a time. This pattern differs from the previous pattern in that the decision about which action to take may depend on more information than is available from just the pair consisting of the state and the input token. In this pattern, the tokens may still remain in the input stream after being processed.

5. A system in which a large amount of information is searched for one or more specific pieces of information. The searches may occur once or many times.

6. A system that can be used in a variety of applications but needs adjustments to work properly in new settings.

7. A system in which everything is primarily guided by an algorithm, rather than depending primarily on data.

8. A system that is distributed, with many relatively independent computational actions taking place. Some of the computational actions may communicate with other com- putational actions.

How many of these patterns are familiar to you? The first two describe human–computer interfaces, with the first one being a rigid, menu-driven system, and the other being con- trolled by actions such as moving a mouse or other pointer and pressing a button. Much of these two patterns is still relevant in an age of tablets and smartphones. Note that pattern 1 seems procedural in nature, whereas pattern 2 fits well with objects communicating by passing messages in an object-oriented system.

Software pattern 3 describes what is often known as a “finite state machine” or “deter- ministic finite automaton.” This pattern is especially useful in controlling some processes in a manufacturing plant such as was shown in Figure 3.9. It is probably easiest to imagine it organized as a procedurally oriented design, at least at top level.

Pattern 4 occurs often in utility software. It is the basis for lexical analyzers and the parsing actions of compilers. Many software systems have some sort of parser to process input commands.

Obviously, software pattern 5 refers to a database searching problem.

Pattern 6 is quite general but suggests that there is a general-purpose system that must be specially configured for each application setting. The installation of a printer for a per- sonal computer (or a network of computers) follows this model.

Software patterns 7 and 8 are also very general in nature. However, pattern 7 appears to suggest that the solution will be procedural in nature, whereas pattern 8 might be better suited to a solution using object-oriented methods. Pattern 8 suggests cloud computing and the Software as a Service (SaaS) model.

These software patterns are not absolute rules. Neither are they intended as a complete classification of software. Nonetheless, they can guide us in the design and implementation portions of the software development process.

We note that these high-level patterns can be further broken into three groups: cre- ational patterns, structural patterns, and behavioral patterns. These groups can be broken further into twenty-three patterns as follows.

Creational patterns

1. Abstract factory—This pattern provides an interface for creating families of related objects without specifying their classes.

2. Builder—This pattern separates the construction of a complex object from its representation.

3. Factory method—This pattern defines an interface for creating a single object and allows subclasses to decide which class to instantiate.

4. Prototype—This pattern specifies the kinds of objects to create using a prototypi- cal instance and creates new objects by copying this prototype. In theory, this can aid in the development of projects that use a rapid prototyping life cycle.

5. Singleton—This pattern makes sure that a class has only a single instance. Structural patterns

6. Adapter—This pattern converts the interface of a class into another interface that the clients of the class expect. This function is called bridgeware or glueware in other contexts.

7. Bridge—This pattern ensures that an abstraction is separate from details of its implementation.

8. Composite—This pattern allows objects to be grouped into tree structures where both individual objects and compositions of objects can be accessed.

9. Decorator—This pattern attaches additional responsibilities to an object dynami- cally while keeping the same interface.

10. Façade—This pattern provides a unified interface to a set of interfaces in a subsystem.

11. Flyweight—This rare pattern uses a form of sharing to treat large numbers of similar objects efficiently.

12. Proxy—This pattern provides a placeholder for another object to control access to it.

Behavioral patterns

13. Chain of responsibility—This pattern allows giving more than one object a chance to handle a request by the request’s sender. This can be highly useful if there are alternative responders, including fallback operations.

14. Command—This rare pattern encapsulates a request as an object, thereby allow- ing clients to be given different requests.

15. Interpreter—This large-scale pattern allows designers to use a representation of a language grammar for parsing. This can be helpful for developing code metrics. 16. Iterator—This pattern provides a way to access the elements of an aggregate object

sequentially without exposing its underlying representation, which need not be an array.

17. Mediator—This pattern defines an object that encapsulates how a set of objects interact, preventing explicit references.

18. Memento—This pattern allows objects to capture and externalize an object’s internal state, while allowing the object to be restored. This can be very useful in creating rollback states in the event of an unforeseen system error.

19. Observer—This useful pattern defines a one-to-many dependency between objects where a state change in one object results in all dependents being notified and updated.

20. State—This pattern allows an object to alter its behavior when its internal state changes. This can be useful for rollbacks in the event of system errors.

21. Strategy—This pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it. This can be used to improve system fault tolerance when incorporated into an “N-version programming” or “multiversion” scheme where correctness is evaluated by majority vote on a set of independently implemented program versions.

22. Template method—This pattern, which is familiar to those who have taken a data structures course, defines the skeleton of an algorithm in an operation, deferring some steps to subclasses.

23. Visitor—This pattern is used to represent an operation to be performed on the elements of an object structure without changing the classes of the elements on which it operates.

I refer the reader to the original Gang of Four book (Gamma et al., 1995) for more detail on these twenty-three patterns. The reader can find many online examples of implementa- tions of these examples in a variety of places. Of course, a 1995 book as important as this one has spawned many research efforts. There are at least forty-eight patterns described in the literature, far too many to discuss here.

It is important to note that the original twenty-three design patterns were given in C++, a language that is not as object-oriented as Java, Objective C, Swift, Ruby, and the like. Hence, some of the aforementioned patterns that were intended to solve issues specific to C++ may not apply to other, later object-oriented programming languages.

We note explicitly that is not necessary to have the entire software system under consid- eration match one of the aforementioned patterns. It is perfectly reasonable to have differ- ent portions of the same software system match several different patterns. This will be the case when we discuss our large software engineering example later in this chapter.

Separation of a software system into several subsystems is common during software design. Indeed, some sort of stepwise refinement of designs is essential for an efficient soft- ware design process. Decomposition of system designs will be a recurring theme during this chapter. For now, we will ignore system decomposition and concentrate on software patterns.

Let us consider how these patterns can be used as part of a design process. Suppose that we recognize that a particular pattern is applicable to a software system that we are to design. Where do we go from there?

The first step is to see if we can make our work simpler. Specifically, we should search through any software available to see if, in fact, we do already have a full or partial solution to our problem. This situation is an example of software reuse, which is the most efficient way to produce software. If the preexisting software solves the problem exactly, then we are done. The needed modifications should be compared with the estimated cost of entirely new software to see if we should attempt to reuse a partial match. We will illustrate the application of software reuse to our continuing discussion of our major software engineer- ing example in Section 4.20 of this chapter.