You have now explored five patterns in depth and looked at capsule summaries of quite a few more.
This module discusses some general issues that arise in using patterns to develop software.
In this module, you will learn:
- How to choose the right pattern for the job
- How to modify documented patterns to fit your problem
- How to combine design patterns
- The pitfalls and limitations of patterns
Patterns, specifically in the realm of software design, are powerful tools that encapsulate solutions to recurring problems within a particular context. However, the application of patterns isn't without its issues. These issues can manifest themselves in various ways and at different points in the software development lifecycle. Here are some general issues that may arise:
- Misuse and Overuse of Patterns: One of the most common issues is the misuse or overuse of patterns. This usually occurs when developers treat patterns as the ultimate solution to every problem, leading to their overuse. This could result in an overly complex design, which is hard to understand, maintain, and modify.
- Context Ignorance: Each pattern is designed for a specific context. Applying a pattern outside its appropriate context can lead to a counterproductive design.
- Impediment to Creativity: Strict adherence to design patterns may stifle creativity and inhibit the development of novel, more efficient solutions to a problem.
- Inefficiency due to Pattern Overhead: Every design pattern comes with its own overhead. While the overhead might be negligible in larger systems, in smaller systems it could lead to inefficiency.
- Lack of Flexibility: A pattern-oriented approach might sometimes result in rigid, tightly-coupled systems that are resistant to change, thereby limiting flexibility.
Software design is the process by which an agent creates a specification of a software artifact, intended to accomplish goals, using a set of primitive components and subject to constraints. Software design may refer to either
- "all the activities involved in conceptualizing, framing, implementing, commissioning, and ultimately modifying complex systems" or
- "the activity following requirements specification and before programming, as in the style of a software engineering process."
Software design usually involves problem solving and planning a software solution.
This includes both low-level component and algorithm design as well as high-level, architecture design.
Cost, customer satisfaction, productivity, and development time are some objectives of software development. Patterns contribute indirectly to many of these goals:
- Productivity: By providing domain expertise, patterns shorten the time interval for many important design structures.
Discovery includes the activities of a designer to find out how the current system works as a basis for maintenance changes.
More importantly, patterns avoid rework that comes from inexpert design decisions.
- Development Interval. Many software patterns are a form of design-level reuse. Patterns can reduce the amount of time required to build solution structures because they allow designers to use design chunks that are larger than functions or objects. Patterns also provide road maps to the structure of existing systems, making it easier for the inexpert designer to understand and navigate existing software. This can reduce discovery costs. Our studies suggest that as much as half of software development effort can be attributed to discovery.
- Cost. Cost reduction follows in a straightforward way from development interval reduction.
- Customer Satisfaction. Customer satisfaction is largely a result of the other factors.
The is a software design pattern that provides a way to manage the lifetime of shared objects. It works by keeping track of the number of references to an object, and deleting the object when the count reaches zero. This pattern is often used in languages like C++, where manual memory management is required.
The CBI works by adding a reference count to the object being shared, called the body. Access to the body is only allowed through a handle object. When a handle is created, it increments the reference count of the body. When a handle is destroyed, it decrements the reference count. When the reference count reaches zero, the body is deleted.
The CBI has a number of advantages over other memory management techniques, such as garbage collection. It is more efficient, because it does not require the runtime to scan for and delete unreachable objects. It is also more flexible, because it allows the programmer to control exactly when objects are deleted.
However, the CBI also has some disadvantages. It is intrusive, because it requires the programmer to modify the code for the body class. It is also prone to errors, if the programmer does not correctly increment and decrement the reference count.
Here is a simple example of how to use the CBI in C++:
class Body {
public:
Body() {}
~Body() {}
int getReferenceCount() const { return referenceCount_; }
void incrementReferenceCount() { referenceCount_++; }
void decrementReferenceCount() { referenceCount_--; }
private:
int referenceCount_ = 0;
};
class Handle {
public:
Handle(Body* body) : body_(body) { body_->incrementReferenceCount(); }
~Handle() { body_->decrementReferenceCount(); }
Body* getBody() const { return body_; }
private:
Body* body_;
};
int main() {
Body* body = new Body();
Handle handle(body);
// Use the body object here.
// Destroy the handle.
handle.reset();
// The body object will be deleted automatically when the reference count reaches zero.
return 0;
}
The Counted Body Idiom is a powerful tool for managing the lifetime of shared objects. However, it is important to be aware of its limitations and use it carefully.