Searching...
English
EnglishEnglish
EspañolSpanish
简体中文Chinese
FrançaisFrench
DeutschGerman
日本語Japanese
PortuguêsPortuguese
ItalianoItalian
한국어Korean
РусскийRussian
NederlandsDutch
العربيةArabic
PolskiPolish
हिन्दीHindi
Tiếng ViệtVietnamese
SvenskaSwedish
ΕλληνικάGreek
TürkçeTurkish
ไทยThai
ČeštinaCzech
RomânăRomanian
MagyarHungarian
УкраїнськаUkrainian
Bahasa IndonesiaIndonesian
DanskDanish
SuomiFinnish
БългарскиBulgarian
עבריתHebrew
NorskNorwegian
HrvatskiCroatian
CatalàCatalan
SlovenčinaSlovak
LietuviųLithuanian
SlovenščinaSlovenian
СрпскиSerbian
EestiEstonian
LatviešuLatvian
فارسیPersian
മലയാളംMalayalam
தமிழ்Tamil
اردوUrdu
Dive Into Design Patterns

Dive Into Design Patterns

by Alexander Shvets 2018 406 pages
4.66
764 ratings
Listen
2 minutes
Try Full Access for 3 Days
Unlock listening & more!
Continue

Key Takeaways

1. Mastering OOP Pillars for Robust Software

Object-oriented programming is a paradigm based on the concept of wrapping pieces of data, and behavior related to that data, into special bundles called objects, which are constructed from a set of “blueprints”, defined by a programmer, called classes.

Foundational concepts. Object-Oriented Programming (OOP) forms the bedrock of modern software design, organizing code around 'objects' created from 'classes'. These objects encapsulate both data (state) and the functions that operate on that data (behavior), promoting modularity and reusability. Understanding these basics is crucial before diving into design patterns.

Four pillars. OOP is built upon four fundamental pillars that differentiate it from other programming paradigms:

  • Abstraction: Modeling real-world objects in a specific context, focusing on relevant details and omitting the rest.
  • Encapsulation: Hiding an object's internal state and behaviors, exposing only a limited public interface for interaction.
  • Inheritance: Building new classes upon existing ones, allowing subclasses to inherit state and behavior from a superclass, promoting code reuse.
  • Polymorphism: The ability of a program to detect an object's real class and call its specific implementation, even when its exact type is unknown in the current context.

Interconnected principles. These pillars are not isolated but work in concert to create flexible, maintainable, and scalable software. For instance, encapsulation often relies on abstraction to define clear interfaces, while polymorphism leverages inheritance to enable dynamic behavior. A solid grasp of these concepts is essential for effectively applying design patterns.

2. Design Patterns: A Toolkit for Recurring Problems

Design patterns are typical solutions to commonly occurring problems in software design.

Pre-made blueprints. Design patterns are not ready-to-use code snippets but rather generalized, customizable blueprints for solving recurring design challenges. They offer a high-level description of a solution, allowing developers to adapt it to the specific context of their program. This contrasts with algorithms, which provide a clear, step-by-step set of actions.

Common language. Learning design patterns provides a shared vocabulary for developers, enabling more efficient communication within teams. Instead of lengthy explanations, referring to a pattern like "Singleton" or "Factory Method" instantly conveys a complex design idea, streamlining discussions and reducing misunderstandings. This shared understanding fosters better collaboration and code quality.

Beyond solutions. While patterns offer proven solutions, their true value extends to teaching effective object-oriented design principles. By studying how patterns address specific problems, developers learn to apply core OOP concepts like encapsulation, polymorphism, and composition more effectively, even when not directly implementing a known pattern. This deepens design intuition and improves overall software architecture.

3. Encapsulate Change to Build Resilient Systems

Identify the aspects of your application that vary and separate them from what stays the same.

Minimize impact. The core objective of "Encapsulate What Varies" is to minimize the ripple effect of changes within a software system. By isolating parts of the program that are prone to modification into independent modules, the rest of the codebase is shielded from adverse effects. This compartmentalization makes the system more resilient and easier to maintain.

Strategic isolation. Consider an e-commerce application where tax calculation logic frequently changes due to new regulations or regional variations. Instead of embedding this logic directly within a getOrderTotal method, it should be extracted into a separate method or even a dedicated class. This way, tax-related updates are confined to a single, isolated unit, preventing unintended side effects across the application.

Class-level encapsulation. As methods accumulate more responsibilities and helper components, their containing classes can become bloated and lose focus on their primary role. Extracting these additional behaviors into new, dedicated classes clarifies responsibilities and simplifies the overall design. For example, moving tax calculation from an Order class to a TaxCalculator class ensures the Order class remains focused on order management.

4. Depend on Abstractions, Not Concretions, for Flexibility

Program to an interface, not an implementation. Depend on abstractions, not on concrete classes.

Enhancing flexibility. This principle advocates for designing interactions between classes using interfaces or abstract classes rather than concrete implementations. When one object needs to collaborate with another, defining a contract (interface) for the required methods allows for greater flexibility. The dependent class then interacts with this interface, not a specific class.

Decoupling components. By programming to an interface, you decouple your code from specific implementations. This means you can easily swap out one implementation for another without altering the client code, as long as the new implementation adheres to the same interface. This is particularly beneficial for extensibility, allowing new functionalities to be introduced without breaking existing code.

Example in action. Imagine a Company class that needs to hire various Employee types (e.g., Programmer, Designer). Initially, the Company might be tightly coupled to concrete Programmer and Designer classes. By introducing an Employee interface and making all employee types implement it, the Company can now interact with any Employee object polymorphically. This allows for the introduction of new employee types (e.g., Tester) without modifying the core Company logic, demonstrating the Factory Method pattern in action.

5. Choose Composition Over Inheritance for Adaptability

Favor Composition Over Inheritance.

Avoiding inheritance pitfalls. While inheritance offers a straightforward way to reuse code, it comes with significant drawbacks that can lead to rigid and fragile designs. Subclasses are tightly coupled to their superclasses, making changes in the parent potentially break children. Furthermore, inheritance struggles with extending classes in multiple, orthogonal dimensions, leading to a combinatorial explosion of subclasses.

"Has-a" over "is-a". Composition offers a more flexible alternative, representing a "has a" relationship rather than an "is a" relationship. Instead of inheriting behavior, an object acquires functionality by holding a reference to another object and delegating tasks to it. This allows behaviors to be swapped at runtime and avoids the deep, static coupling inherent in inheritance.

Multi-dimensional extension. Consider a car manufacturer's catalog app where cars and trucks can be electric or gas, and have manual or autopilot controls. Using inheritance would create a massive hierarchy (e.g., ElectricTruckWithAutopilot). With composition, you can extract engine types and navigation types into separate hierarchies. A Car object then "has an" Engine object and a NavigationSystem object, allowing for dynamic combinations and easier extension without creating an explosion of subclasses.

6. SOLID Principles: Guidelines for Maintainable Code

SOLID is a mnemonic for five design principles intended to make software designs more understandable, flexible and maintainable.

A guiding framework. The SOLID principles, introduced by Robert C. Martin, provide a set of guidelines for writing robust, flexible, and maintainable object-oriented code. While not dogmatic rules, they serve as a valuable compass for navigating complex design decisions, helping developers create systems that are easier to understand, extend, and debug.

The five principles:

  • Single Responsibility Principle (SRP): A class should have only one reason to change, focusing on a single part of the software's functionality.
  • Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification, allowing new features without altering existing, tested code.
  • Liskov Substitution Principle (LSP): Objects of a superclass should be replaceable with objects of its subclasses without breaking the application, ensuring behavioral compatibility.
  • Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use, advocating for small, specific interfaces.
  • Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions, inverting traditional dependency flow.

Pragmatic application. Applying SOLID principles mindlessly can sometimes over-complicate a simple design. The key is to be pragmatic, using them where they genuinely add value by addressing complexity, promoting extensibility, or improving maintainability. Striving for these principles fosters better design habits, even if not every principle is strictly applied in every scenario.

7. Creational Patterns Streamline Object Instantiation

Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code.

Flexible object generation. Creational design patterns are concerned with the process of object creation, offering mechanisms that enhance flexibility and promote the reuse of existing code. Instead of directly instantiating objects using the new operator, these patterns abstract the creation process, allowing the system to decide which objects to create and how.

Decoupling creation logic. A primary benefit of creational patterns is decoupling the client code from the concrete classes it instantiates. This means that changes to the object creation process (e.g., introducing new product types or changing how objects are configured) can be made without modifying the client code that uses these objects. This adheres to the Open/Closed Principle.

Key creational patterns include:

  • Factory Method: Defines an interface for creating an object, but lets subclasses decide which class to instantiate.
  • Abstract Factory: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
  • Builder: Separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
  • Prototype: Specifies the kind of objects to create using a prototypical instance, and creates new objects by copying this prototype.
  • Singleton: Ensures a class has only one instance and provides a global point of access to it.

8. Structural Patterns Organize Objects into Flexible Structures

Structural patterns explain how to assemble objects and classes into larger structures, while keeping the structures flexible and efficient.

Building robust architectures. Structural design patterns focus on how classes and objects are composed to form larger, more complex structures. Their goal is to simplify the design by identifying relationships between entities, ensuring that these structures remain flexible, efficient, and easy to understand as the system evolves.

Adapting and enhancing. These patterns often involve creating "wrappers" or "adapters" around existing components to make them compatible with new interfaces or to add new functionalities without altering their core code. This promotes code reuse and helps integrate disparate systems seamlessly. They address challenges like incompatible interfaces, complex subsystem interactions, or the need to add responsibilities dynamically.

Common structural patterns:

  • Adapter: Allows objects with incompatible interfaces to collaborate.
  • Bridge: Decouples an abstraction from its implementation so that the two can vary independently.
  • Composite: Composes objects into tree structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly.
  • Decorator: Attaches additional responsibilities to an object dynamically, providing a flexible alternative to subclassing for extending functionality.
  • Facade: Provides a simplified interface to a complex subsystem.
  • Flyweight: Reduces the number of objects created to improve performance and memory usage by sharing common state.
  • Proxy: Provides a surrogate or placeholder for another object to control access to it.

9. Behavioral Patterns Orchestrate Object Communication

Behavioral patterns are concerned with algorithms and the assignment of responsibilities between objects.

Streamlining interactions. Behavioral design patterns focus on the communication and interaction between objects, as well as the distribution of responsibilities. They help define how objects collaborate to achieve a common goal, making the system more flexible and easier to maintain by managing complex control flows and dependencies.

Decoupling and flexibility. These patterns often aim to decouple senders and receivers of requests, allowing them to vary independently. They provide mechanisms for objects to communicate without being tightly bound to each other's concrete classes, enabling dynamic changes in behavior and easier extension of the system. This promotes a more organized and less "spaghetti-like" codebase.

Key behavioral patterns include:

  • Chain of Responsibility: Passes requests along a chain of handlers, allowing multiple objects to handle a request without explicit sender-receiver coupling.
  • Command: Encapsulates a request as an object, thereby allowing for parameterization of clients with different requests, queuing or logging of requests, and support for undoable operations.
  • Iterator: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
  • Mediator: Defines an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly.
  • Memento: Captures and externalizes an object's internal state without violating encapsulation, so that the object can be restored to this state later.
  • Observer: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
  • State: Allows an object to alter its behavior when its internal state changes, making it appear as if the object changed its class.
  • Strategy: Defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing the algorithm to vary independently from clients that use it.
  • Template Method: Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses, allowing subclasses to redefine certain steps of an algorithm without changing the algorithm's structure.
  • Visitor: Represents an operation to be performed on the elements of an object structure, allowing new operations to be defined without changing the classes of the elements on which it operates.

10. Factory Method: Decoupling Product Creation from Client Code

Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Abstracting instantiation. The Factory Method pattern addresses the problem of tightly coupled object creation by replacing direct constructor calls (new) with calls to a special "factory method." This method, often declared in a superclass, returns objects (referred to as "products") that conform to a common interface. Subclasses can then override this factory method to produce different types of products.

Enhancing extensibility. This pattern is particularly useful when the exact types of objects your code needs to work with are unknown beforehand or may change in the future. By decoupling the product creation code from the client code, you can introduce new product types simply by creating new creator subclasses and overriding their factory methods, without altering existing client logic. This adheres to the Open/Closed Principle.

Cross-platform example. Consider a cross-platform UI application that needs to render buttons differently on Windows and Web. Instead of conditional logic throughout the UI code, a Dialog superclass can declare an abstract createButton() factory method. WindowsDialog and WebDialog subclasses then implement this method to return WindowsButton and HTMLButton objects, respectively. The client code interacts with these buttons via a generic Button interface, remaining oblivious to the underlying platform-specific implementations.

11. Singleton: Guaranteeing a Unique Global Instance

Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.

Controlled uniqueness. The Singleton pattern solves two problems simultaneously: ensuring a class has only one instance and providing a global access point to that instance. This is crucial for resources that should be unique across an application, such as a database connection, a configuration manager, or a logging service, preventing multiple, potentially conflicting, instances.

Private constructor, static method. To achieve this, the Singleton pattern makes the class's default constructor private, preventing direct instantiation with the new operator. Instead, it provides a public static creation method (e.g., getInstance()) that acts as a controlled constructor. This method creates the object only on its first call and stores it in a static field, returning this cached instance on all subsequent calls.

Global access, protected instance. Similar to a global variable, the Singleton offers access from anywhere in the program. However, unlike a global variable, it protects the instance from being overwritten by other code, ensuring its integrity. While powerful, the Singleton can mask bad design by encouraging tight coupling between components and can complicate unit testing due to its global nature and private constructor.

Last updated:

Want to read the full book?
Listen2 mins
Now playing
Dive Into Design Patterns
0:00
-0:00
Now playing
Dive Into Design Patterns
0:00
-0:00
1x
Voice
Speed
Dan
Andrew
Michelle
Lauren
1.0×
+
200 words per minute
Queue
Home
Swipe
Library
Get App
Create a free account to unlock:
Recommendations: Personalized for you
Requests: Request new book summaries
Bookmarks: Save your favorite books
History: Revisit books later
Ratings: Rate books & see your ratings
600,000+ readers
Try Full Access for 3 Days
Listen, bookmark, and more
Compare Features Free Pro
📖 Read Summaries
Read unlimited summaries. Free users get 3 per month
🎧 Listen to Summaries
Listen to unlimited summaries in 40 languages
❤️ Unlimited Bookmarks
Free users are limited to 4
📜 Unlimited History
Free users are limited to 4
📥 Unlimited Downloads
Free users are limited to 1
Risk-Free Timeline
Today: Get Instant Access
Listen to full summaries of 26,000+ books. That's 12,000+ hours of audio!
Day 2: Trial Reminder
We'll send you a notification that your trial is ending soon.
Day 3: Your subscription begins
You'll be charged on Mar 16,
cancel anytime before.
Consume 2.8× More Books
2.8× more books Listening Reading
Our users love us
600,000+ readers
Trustpilot Rating
TrustPilot
4.6 Excellent
This site is a total game-changer. I've been flying through book summaries like never before. Highly, highly recommend.
— Dave G
Worth my money and time, and really well made. I've never seen this quality of summaries on other websites. Very helpful!
— Em
Highly recommended!! Fantastic service. Perfect for those that want a little more than a teaser but not all the intricate details of a full audio book.
— Greg M
Save 62%
Yearly
$119.88 $44.99/year/yr
$3.75/mo
Monthly
$9.99/mo
Start a 3-Day Free Trial
3 days free, then $44.99/year. Cancel anytime.
Scanner
Find a barcode to scan

We have a special gift for you
Open
38% OFF
DISCOUNT FOR YOU
$79.99
$49.99/year
only $4.16 per month
Continue
2 taps to start, super easy to cancel
Settings
General
Widget
Loading...
We have a special gift for you
Open
38% OFF
DISCOUNT FOR YOU
$79.99
$49.99/year
only $4.16 per month
Continue
2 taps to start, super easy to cancel