Designing Software  «Prev  Next»
Lesson 2 Choosing a design pattern
Objective Find the design pattern that fits your need.

Choosing a Design Pattern during Software Development

Design patterns provide a generalized reusable solution to commonly occurring problems in software design. However, determining the correct design pattern to apply to a particular problem can be challenging. The selection process requires a deep understanding of the problem domain, a comprehensive knowledge of available patterns, and an ability to match a pattern's strengths to the problem's requirements. Below is a step-by-step process to guide this selection:
  1. Analyze the Requirement: Understanding the requirement is the first and most important step. You should thoroughly analyze the problem at hand and understand what is expected from the solution. Try to pinpoint the core concerns, whether they relate to object creation, class structure, or object behavior. This phase may involve dialogues with stakeholders, detailed study of requirement documents, and brainstorming sessions among the development team.
  2. Identify the Problem Type: After grasping the requirement, categorize the core problem you are facing. Is it related to object creation, structural organization, or the behavior of objects in your system? For example, if objects need to be created on the fly with a particular behavior or state, then the problem can be classified under "Creational" category.
  3. Explore Design Patterns: Once the problem category is clear, explore various design patterns that fall under that category. Each design pattern is meant to address a specific type of problem. For instance, if you're facing a creational problem, look into patterns such as Singleton, Factory Method, Abstract Factory, Builder, Prototype, etc. For structural issues, you might consider patterns like Adapter, Bridge, Composite, Decorator, etc. Behavioral concerns could be addressed by patterns like Chain of Responsibility, Command, Iterator, Strategy, etc.
  4. Evaluate the Patterns: After identifying potential design patterns, critically evaluate each of them. Compare the strengths and weaknesses of the patterns in relation to your specific problem. Consider factors such as the complexity of the pattern, its implications for future changes, its overhead, and how well it integrates with the existing system. In some cases, you might want to use more than one pattern or even a combination of patterns to solve a complex problem.
  5. Consider Practical Constraints: Be aware of any practical constraints like project deadlines, team expertise, performance requirements, and so on. Some patterns might require more time to implement correctly, or they might be more complex and require a higher level of programming expertise. Some patterns may also introduce a degree of performance overhead that might not be acceptable in performance-critical applications.
  6. Prototype and Review: Once you have made a choice, consider creating a small prototype using the chosen design pattern. This can provide a reality check on whether the pattern is a good fit for the problem. Have the prototype reviewed by peers or subject matter experts to gain additional insight and identify potential issues before full-scale implementation.

Remember, the goal of using a design pattern is to simplify and improve the design process, making your software easier to understand, maintain, and extend. If a design pattern introduces more complexity without significant benefits, it may not be the right choice. Always be prepared to reassess your choice of design pattern as your understanding of the requirements evolves and as the system itself changes over time.

Choosing a Design Pattern

Software patterns encourage reuse and modification of both
  1. objects and
  2. classes.

Architectural patterns primarily encourage reuse of blueprints, that is classes, but not of the actual buildings, parks, and towns built from those blueprints. It is much easier to tear down and replace part of a software package as needed than it is to extend a house.

The State Pattern seems like a great idea. It lets me change an object state dynamically. However, not all states have the same methods. Administrators have more methods than User. Moderator does not have any of the methods from Admin or User. You can only use General to log in. I think that this will then limit the ability to use State Pattern.
Do you have any suggestions for some other pattern?

Object Hierarchies and Design Patterns

Object-oriented programming encompasses more than the simple bundling of data and the associated methods within an object. It extends into the realm of categorizing objects and scrutinizing the dynamics among varying object classes. At the heart of expressing these interclass relationships is the concept of derivation, a mechanism through which new classes are crafted based on pre-existing ones. The power of derivation lies in the principle of inheritance, whereby new, or 'derived', classes come to possess the attributes of their 'parent' classes. This foundational mechanism not only allows for the inherited attributes to be refined or supplanted but also permits the introduction of new attributes within the derived class.
A distinctive feature highlighted in this text is the presentation of nearly all data structures within a singular, coherent class hierarchy. This hierarchy essentially functions as a structured classification of data structures, where various implementations of a particular abstract data structure stem from a common abstract base class. Classes at the foundational level of this hierarchy encapsulate the shared traits of their more specialized progeny. Beyond the realm of hierarchical class structures, adept object-oriented designers must also give due consideration to the interactions between classes that do not share a direct lineage. Through seasoned experience, designers discern recurring patterns in object interactions. Mastery of these patterns significantly enhances the flexibility and reusability of object-oriented designs. As a testament to their significance, these prevalent design patterns have not only been named but also compiled into catalogs, which are increasingly being recognized and referenced within the programming community.
The following object-oriented design patterns are used throughout this text:
  1. Containers
  2. Enumerators
  3. Visitors
  4. Cursors
  5. Adapters

Patterns are not the panacea of program design. They do not replace traditional object-oriented analysis techniques like CRC cards or use-case modeling. To use an architectural analogy, analysis lets you determine that your house needs 200 amps of electricity. Patterns let you determine how the wiring will be installed. Patterns do help you think about the problems you may encounter while designing a system. Therefore, a generic, pattern-based solution isoften more robust than a solution designed by one individual to solve a specific problem. Given the number of design patterns in common use (as well as many more being invented and discovered almost daily) it can sometimes be hard to choose the pattern that suits your needs.

Choosing a Design Pattern

So far in this course, we have seen standard ways for objects of different types to interact: inheritance,
  1. one class containing fields of another classes type,
  2. one class throwing a member of an exception class.

We have one class whose "function" it is to traverse another class and provide some functionality (access to elements) that is not naturally handled within the original class. In other words, we have abstracted one particular functionality out of one class into another. Once we absorb the ideas of object-oriented programming more fully, it becomes really fun to think about objects as each having their own identity, filling a role in a larger system of components, and interacting with each other in particular ways.
Since the idea of an Iterator is fairly useful generally, it has been identified as one of many Design Patterns. Design patterns are an important way of identifying frequently occurring ways in which objects interact, and they help in designing well-structured larger software systems that can be extended easily later. As such, they are an important part of the field of Software Engineering.

Matching Problems with Patterns

This table describes some of the common programming patterns, and suggested design patterns:

Problem Solution Patterns
Your code depends on the names of classes. Changing the class of an object is burdensome because the name of the class is hard-coded in the client program through constructor invocations. Do not use constructors directly in your client classes. Provide an extra level of indirection to the code that invokes the constructor. Abstract Factory
Factory Method
Your code depends on platform idiosyncrasies. #ifdef and conditional compilation only takes you so far.
Even Java's vaunted platform independence has a few weak spots.
Isolate the platform-dependent parts of your program from the platform-independent parts. Abstract Factory
Your code depends on specific methods in specific classes. Separate the request itself from the object and/or method that handles the request. Chain of Responsibility
Your code depends too closely on exactly how an object is implemented. Changing the implementation of the class forces change on the client class. Encapsulation is not airtight. This is more often a problem in C++ with its pointers, pointer arithmetic, and relatively close access to the machine than it is in Java. Often the solution is to wrap an additional layer of interface around the implementation. Abstract Factory
Changing an algorithm requires too many changes in the classes that use it, especially changes that affect the class's interface as well as its implementation. The algorithm should be separated from the class, and moved into a class of its own. Builder
Template Method
Classes are excessively dependent on each other. It's difficult to change one class without changing most or all other classes. Separate classes with additional levels of indirection. Bridge
Chain of Responsibility
Subclassing is too difficult. Use object composition and delegation instead. Bridge
Chain of Responsibility
Composite, Strategy
A class can't be modified, either because you don't have its source code or because too many other classes depend on it. Use object composition to embed an instance of the class inside another class that provides a new interface. Delegate requests to the embedded object. Adaptor

The first thing you should decide is whether the problem is fundamentally creational, structural, or behavioral. Some problems, of course, have aspects of two or even three, and may require you to mix and match patterns.

Integration of Design Patterns

The most notable aspect of this site is the manner in which the coverage of design patterns is interwoven with the remainder of the material.
For example,
  1. Swing containers and components motivate the COMPOSITE pattern.
  2. Swing scroll bars motivate the DECORATOR pattern, and Swing borders are examined as a missed opportunity for that pattern.
  3. Java streams give a second example of the DECORATOR pattern. Seeing the pattern used in two superficially different ways greatly clarifies the pattern concept.
Without memorable examples, design patterns are just words. In order to visualize design patterns, this website uses examples from graphical user interface programming. Students will remember how a component is decorated by scroll bars, and how layout managers carry out different strategies. I will use a small and carefully selected subset of Swing is used for this purpose.

A Foundation for Further Study

This website discusses the design and implementation of computer programs from the object-oriented point of view. Although much of what we say remains valid for large projects, there are added complexities with large projects that we will not address here. Programming tasks originate from the desire to solve a particular problem. The task may be simple, such as writing a program that generates and formats a report, or complicated, such as writing a word processor. The end product is a working program. To this end, it is a common practice to break up the software development process into three phases:
  1. Analysis
  2. Design
  3. Implementation