Object-Oriented Design Patterns Explained

35 carte

This note provides a comprehensive overview of design patterns in object-oriented software design. It covers the definition, classification, and detailed descriptions of various patterns, including creational, structural, and behavioral types. The note also explores their advantages, disadvantages, and provides examples of their implementation in Java.

35 carte

Ripassa
La ripetizione spaziata ti mostra ogni carta al momento ottimale per memorizzare a lungo termine, con revisioni sempre più distanziate.
Domanda
How many design patterns are described in the GoF book?
Risposta
The GoF book describes 23 design patterns.
Domanda
What is the primary characteristic of class patterns?
Risposta
Class patterns use inheritance to define algorithms and control structures.
Domanda
What is an advantage of the Singleton pattern concerning global variables?
Risposta
The Singleton pattern provides controlled access to its single instance, offering an alternative to global variables.
Domanda
What is the primary purpose of structural patterns?
Risposta
To combine objects and classes into larger structures and define new functionalities.
Domanda
What is the primary benefit of Design Patterns?
Risposta
Design patterns offer reusable, standard solutions to common software design problems, facilitating communication.
Domanda
What is the goal of the Factory Method pattern?
Risposta
Defines an interface for creating an object, but lets subclasses decide which class to instantiate. Delegates instantiation to subclasses.
Domanda
What is the alias for the Strategy pattern?
Risposta
The Strategy pattern is also known as the Policy pattern.
Domanda
Name one structural design pattern.
Risposta
One structural design pattern is the Adapter pattern.
Domanda
Name one creational design pattern.
Risposta
One creational design pattern is the Abstract Factory pattern.
Domanda
What is the main goal of creational patterns?
Risposta
Isolating instantiation logic to decouple code from the specific types of objects being created.
Domanda
What is the alias for the Observer pattern?
Risposta
The Observer pattern's aliases are Dependents and Publish-Subscribe.
Domanda
What does a good pattern name aim to do?
Risposta
A good pattern name aims to describe the pattern's principle and facilitate communication.
Domanda
What is the alias for the Abstract Factory pattern?
Risposta
The Abstract Factory pattern is also known as the Virtual Constructor.
Domanda
What categories does the Singleton pattern fall under?
Risposta
The Singleton pattern falls under the Structural and Object categories.
Domanda
What is the 'Singleton' pattern's primary goal?
Risposta
Ensures a class has only one instance and provides a global point of access to it.
Domanda
What is the core intention of the Strategy pattern?
Risposta
The Strategy pattern allows an algorithm's variations to be selected at runtime, encapsulating each one into a separate class.
Domanda
What categories does the Adapter pattern belong to?
Risposta
The Adapter pattern belongs to the Structural and Object categories.
Domanda
What are the two main classifications of design patterns based on their domains?
Risposta
The two main classifications are creational and structural patterns, focusing on object creation and structure, respectively.
Domanda
What is the alias for the Factory Method pattern?
Risposta
The alias for the Factory Method pattern and its description appear in the context as Constructeur Virtuel.
Domanda
What is the purpose of the Adapter pattern?
Risposta
Allows classes with incompatible interfaces to collaborate. It acts as a bridge between two otherwise incompatible interfaces.
Domanda
What are the three main classifications of design patterns based on their roles?
Risposta
Design patterns are classified by role into **creational**, **structural**, and **behavioral**, detailing how objects and classes are communicated.
Domanda
What is a benefit of the Observer pattern regarding coupling?
Risposta
The Observer pattern promotes low coupling between the Subject and its Observers, as the Subject doesn't need to know the concrete classes of its Observers.
Domanda
When should the Factory Method be used?
Risposta
Use when a class cannot anticipate the type of object it needs to create, or when subclasses should specify the objects they create.
Domanda
Name one behavioral design pattern.
Risposta
One behavioral design pattern is the Strategy pattern. It defines a family of algorithms, encapsulates each one, and makes them interchangeable.
Domanda
What is the main purpose of the Abstract Factory pattern?
Risposta
Provides an abstract interface for creating families of related or dependent objects without specifying their concrete classes.
Domanda
When should the Adapter pattern be used?
Risposta
Use the Adapter pattern to convert a class\'s interface to match client expectations when direct compatibility is impossible.
Domanda
What type of diagram is typically used to represent the 'Structure' of a design pattern?
Risposta
A UML diagram is typically used to represent the structure of a design pattern.
Domanda
What is a 'Motivation' in the context of design patterns?
Risposta
A design pattern's Motivation is an illustrative scenario showing how the pattern solves a specific design problem.
Domanda
Who are considered the authors of the book 'Design Patterns: Elements of Reusable Object-Oriented Software'?
Risposta
The authors are Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.
Domanda
What is the core intent of the Observer pattern?
Risposta
Defines a one-to-many dependency so that when one object changes state, all its dependents are notified and updated.
Domanda
What is the 'Intention' section of a design pattern description?
Risposta
The 'Intention' section describes what the design pattern does, its purpose, and the design problem it addresses.
Domanda
What do behavioral patterns primarily focus on?
Risposta
Behavioral patterns primarily focus on algorithms and the assignment of responsibilities between objects.
Domanda
What generally distinguishes object patterns from class patterns?
Risposta
Object patterns describe how objects collaborate, while class patterns use inheritance to compose classes.
Domanda
How do clients access the singleton instance?
Risposta
Clients access the singleton instance via its class method `getInstance()`.
Domanda
What does the 'Consequences' section describe?
Risposta
The consequences section details the positive and negative effects, as well as compromises, resulting from implementing a design pattern.

Design Patterns: Solutions for Reusable Object-Oriented Software Design

Design Patterns are standardized solutions to recurring design problems in software development. They provide a common vocabulary and structured approach to building flexible, modular, extensible, and reusable software while operating under limited resources.

The field of design patterns was significantly popularized by the book "Design Patterns: Elements of Reusable Object-Oriented Software" by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, commonly known as the Gang of Four (GoF). This seminal work, published in 1995, described 23 design patterns, offering generic solutions to common programming and design challenges.

Introduction to Design Patterns

Software design is often a complex and iterative process, benefiting greatly from accumulated experience. Recurring design problems suggest the need for structured, proven solutions.

Concept Origin

The idea of "patterns" originated with architect Christopher Alexander, who developed a theory of architectural design based on recurring motifs. Alexander's work, particularly "A Pattern Language" (1977), defines a pattern as:

"Each motif describes a problem that one encounters incessantly in our environment; then it describes the core of the solution to this problem in such a way that the solution can be used millions of times, without ever implementing it in the same way."

This implies that a pattern:

  • Describes a recurring problem.

  • Outlines the core of a solution.

  • Can lead to multiple distinct implementations.

Challenges in Software Design

Developing robust software involves balancing conflicting objectives:

  • Encapsulation vs. Access: Protecting data while allowing necessary interaction.

  • Object Granularity: Choosing the appropriate size and scope for objects.

  • Reduced Coupling: Minimizing dependencies between objects.

  • Simple API: Providing an easy-to-use public interface.

  • Performance Optimization: Ensuring efficient execution.

These challenges often require compromises to achieve reusability, extensibility, adaptability, and performance.

Classification of Design Patterns

Design patterns are classified based on their roles (what they do) and their domains (how they apply to classes or objects).

Roles: Creational, Structural, or Behavioral

  • Creational Patterns: Focus on object creation mechanisms. They abstract the instantiation process, making the system independent of how its objects are created, composed, and represented. These patterns isolate the instantiation instructions, making the rest of the code independent of the type of objects created.

  • Structural Patterns: Deal with the composition of classes and objects to form larger structures. They describe how to assemble objects to realize new functionality. They establish associations and compositions between objects and classes.

  • Behavioral Patterns: Focus on communication and collaboration between objects. They identify common communication patterns and encapsulate them. They interconnect objects and classes to make them communicate and collaborate.

Domains: Classes or Objects

The domain specifies whether a pattern primarily applies to classes or objects:

  • Class Patterns:

    • Define relationships between classes using inheritance.

    • Relationships are defined at compile time (static).

    • For creational patterns, they delegate parts of object construction to subclasses.

    • For structural patterns, they use inheritance to compose classes.

    • For behavioral patterns, they use inheritance to define algorithms and control structures.

  • Object Patterns:

    • Describe relationships between objects, mainly using composition.

    • Relationships are established at runtime (dynamic).

    • For creational patterns, they delegate parts of object construction to other objects.

    • For structural patterns, they describe ways to assemble objects.

    • For behavioral patterns, they describe how a group of objects collaborates to achieve a task that a single object cannot accomplish alone.

GoF Pattern Classification Table

The GoF (Gang of Four) categorized their 23 patterns within this matrix:

Créateurs (Creational)

Structurels (Structural)

Comportementaux (Behavioral)

Classes

Fabrication (Factory Method)

Adaptateur (Adapter)

Interprète (Interpreter)
Patron de Méthode (Template Method)

Objets

Fabrique Abstraite (Abstract Factory)
Monteur (Builder)
Prototype (Prototype)
Singleton (Singleton)

Adaptateur (Adapter)
Composite (Composite)
Décorateur (Decorator)
Façade (Facade)
Procuration (Proxy)
Poids-Mouche (Flyweight)
Pont (Bridge)

Chaîne de Responsabilité (Chain of Responsibility)
Commande (Command)
Itérateur (Iterator)
Médiateur (Mediator)
Mémento (Memento)
Visiteur (Visitor)
État (State)
Observateur (Observer)
Stratégie (Strategy)

Description Format for Design Patterns

Graphical notations like UML are often insufficient to fully describe design patterns because they primarily show class and object relationships but fail to capture the underlying motivations, alternatives, and compromises. The GoF proposed a unique, comprehensive format for describing design patterns, which typically includes the following elements:

  1. Name and Classification: A concise name that describes the pattern's essence, along with its classification (creational, structural, behavioral; class or object). A good name is crucial for communication.

  2. Intentions (Intent): What the pattern does, its purpose, the design problem it addresses, and its reason for being.

  3. Alias: Other names by which the pattern is known.

  4. Motivation: An illustrative scenario demonstrating the pattern's use and how it solves a particular problem.

  5. Indications d'utilisation (Applicability):

    • When should the pattern be used?

    • What situations justify its application?

    • How to recognize these situations?

  6. Structure: A graphical representation, typically using UML diagrams, showing the classes and objects involved in the pattern and their relationships.

  7. Constituents (Participants): A list of the classes and objects participating in the pattern.

  8. Collaborations: How the participating classes and objects interact to carry out their responsibilities.

  9. Conséquences (Consequences): The results and trade-offs of using the pattern, including both positive and negative impacts.

  10. Implémentation (Implementation): Practical tips, tricks, pitfalls to avoid, and common solutions for specific programming languages (e.g., C++, Smalltalk, Java).

  11. Exemples de code (Code Examples): Code fragments illustrating possible implementations of the pattern.

  12. Utilisations remarquables (Known Uses): Examples of where the pattern has been applied in existing systems.

  13. Modèles apparentés (Related Patterns): Other design patterns that are related to the one described, including how they differ or can be used together.

Catalogue of Design Patterns

This section details several commonly used design patterns from the GoF catalogue.

Singleton Pattern

Category: Creational, Object

Intention: Guarantees that a class has only one instance and provides a global point of access to that instance.

Motivation: Some classes, like a system's logger, configuration manager, or a single database connection pool, should inherently have only one instance. Managing multiple instances of such classes could lead to inconsistencies or resource exhaustion. The Singleton pattern ensures this constraint while offering a global access point.

Structure: A Singleton class typically has a private constructor to prevent direct instantiation, a private static instance of itself, and a public static method to provide access to that single instance. Clients access the unique instance through its class method.

Example (Java):

public class Singleton {
    private static Singleton monInstance;
private Singleton() {
    // Private constructor to prevent direct instantiation
    // ... initialization code ...
}

// Synchronized to ensure thread-safety for lazy initialization
public static synchronized Singleton getInstance() {
    if (monInstance == null) {
        monInstance = new Singleton();
    }
    return monInstance;
}

}

Consequences:

  • Advantages:

    • Provides controlled access to its unique instance.

    • Offers an alternative to global variables, encapsulating the instance.

    • Allows for a variable number of instances; the pattern can be modified to permit multiple instances if needed, offering flexibility in strategy.

  • Disadvantages/Considerations:

    • Can make testing more difficult due to global state.

    • Violation of the Single Responsibility Principle if it manages its own creation and controls functionality.

    • Potential for misuse as a global access point for frequently used objects, leading to tightly coupled code.

    • Thread-safety must be carefully considered during implementation, especially in multi-threaded environments (e.g., using or Double-Checked Locking).

Known Uses: Loggers, configuration objects, print spoolers, window managers.

Adapter Pattern

Category: Structural, Object or Class

Alias: Wrapper

Intention: Converts the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.

Motivation: Imagine you have an existing component (the "adaptee") that provides valuable functionality, but its interface doesn't match the interface that your client code expects to interact with (the "target interface"). Rather than modifying the existing client or adaptee (which might be impossible, e.g., if it's a third-party library), the Adapter pattern allows you to create an intermediate object that translates calls from the target interface to the adaptee's interface.

Example Scenario: Consider two email sending services: and .

has a method like:
has a method like:
If your application is designed to use the interface but you need to integrate , an Adapter can bridge this gap.

Applicability: The Adapter pattern should be used when:

  • You want to use an existing class, but its interface does not coincide with the one you need.

  • You want to create a reusable class that collaborates with unrelated, and possibly unknown, classes (i.e., whose interfaces might not be compatible).

  • (For object adapters) You need to use several existing subclasses, but adapting each of them by subclassing is not feasible. An object adapter can adapt the interface of its parent class.

Observer Pattern

Category: Behavioral, Object

Alias: Dependents, Publish-Subscribe

Intention: Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Motivation: Often, there's a need for objects to be aware of changes in other objects without being tightly coupled. For example, in a GUI application, multiple widgets (like charts, tables) might need to reflect changes in a data model. The Observer pattern allows the data model (subject) to notify subscribing widgets (observers) of changes without knowing their concrete types, promoting loose coupling.

Existing Implementations: The Observer pattern has been widely implemented in various frameworks:

  • Java's (now deprecated since Java 9, with recognized bugs since 2000).

  • Java Beans and AWT/Swing event models.

  • Bindings in modern frameworks.

Structure: The core components are a Subject and an Observer.

  • Subject (Sujet): The object whose state is being observed. It maintains a list of its dependents (observers) and provides methods to attach (), detach (), and notify () them of state changes.

  • Observer (Observateur): Defines an updating interface for objects that should be notified of changes in a subject. It typically has a method.

  • Concrete Subject: Implements the Subject interface and stores the state of interest to ConcreteObserver objects. It sends a notification to its observers when its state changes.

  • Concrete Observer: Implements the Observer updating interface to keep its state consistent with the subject's.

Example (Java Code Excerpts):

Subject class:

public abstract class Sujet {
    private List<Observateur> observateurs = new ArrayList<>();
public void attache(Observateur obs){
    observateurs.add(obs);
    obs.setSujet(this); // Assuming observer needs reference to its subject
}

public void detache(Observateur obs){
    observateurs.remove(obs);
    obs.setSujet(null); // Clear subject reference
}

public void notifie(){
    for(Observateur obs : observateurs){
        obs.miseAJour();
    }
}

}

Observer interface (or abstract class):

public interface Observateur {
    void miseAJour();
    void setSujet(Sujet sujet); // To allow observers to know their subject
}

Concrete Subject Example (`Compteur`):

public class Compteur extends Sujet implements Runnable {
    private int valeur; // Should be 'value'
public int getValue(){
    return valeur; // Should be 'value'
}

private void incremente(){
    valeur++; // Should be 'value'
    notifie();
}

public void run(){
    // ... (thread logic to increment value and notify) ...
    Random random = new Random();
    while(true){
        try {
            Thread.sleep(random.nextInt(2)*1000);
        } catch(InterruptedException e) { System.exit(0); }
        incremente();
    }
}

}

Main Application (`Main`) demonstrating usage:

public class Main {
    public static void main(String[] args) {
        Compteur compteur = new Compteur();
        Thread thread = new Thread(compteur);
    ObservateurCompteur compteur1 = new ObservateurCompteur("compteur 1");
    ObservateurCompteur compteur2 = new ObservateurCompteur("compteur 2");
    ObservateurCompteur compteur3 = new ObservateurCompteur("compteur 3");
    ObservateurCompteur compteur4 = new ObservateurCompteur("compteur 4");

    thread.start(); // Start the counter thread

    compteur.attache(compteur1);
    try { Thread.sleep(3000); } catch (InterruptedException e) { System.exit(0); }
    compteur.attache(compteur2);
    compteur.attache(compteur3);
    try { Thread.sleep(2000); } catch (InterruptedException e) { System.exit(0); }
    compteur.attache(compteur4);
}

}

Applicability: The Observer pattern is used when:

  • A concept has two representations, one depending on the other. Encapsulating these two representations in different objects allows them to evolve independently.

  • A change in one object requires changes in others, and you don't know how many other objects need changing.

  • An object needs to be able to notify other objects without making assumptions about their concrete types.

Consequences:

  • Advantages:

    • Loose Coupling: The subject and observers are loosely coupled. A subject only knows that it has a list of observers, not their concrete classes. This allows for independent evolution.

    • Support for Broadcast Communication: No need to specify recipients; notifications are broadcast to all subscribed objects.

  • Disadvantages/Considerations:

    • Unexpected Updates: Since observers are not directly aware of each other, updates can become heavy or complex if not carefully managed.

    • Order of Notification: The order in which observers are notified is not guaranteed and can sometimes be critical.

    • Memory Leaks: If detached observers are not properly garbage collected, they can lead to memory leaks, especially when the subject holds strong references to them.

Factory Method Pattern

Category: Creational, Class

Alias: Virtual Constructor

Intention: Defines an interface for creating an object, but lets subclasses decide which class to instantiate. The Factory Method pattern allows a class to delegate instantiation to subclasses.

Motivation: Consider creating an application that handles different types of documents (e.g., text documents, image documents, spreadsheet documents). If the application needs to create new document objects, and the specific type of document to create depends on the subclass of the application (e.g., a text editor creates text documents), a direct instantiation using would couple the application to concrete document classes. The Factory Method decouples the creator from the concrete products.

Structure: The pattern involves:

  • Product: Defines the interface of objects the factory method creates.

  • ConcreteProduct: Implements the Product interface.

  • Creator: Declares the factory method, which returns an object of type Product. It may also define a default implementation of the factory method that returns a default ConcreteProduct object. It can call the factory method to create a Product object.

  • ConcreteCreator: Overrides the factory method to return an instance of a ConcreteProduct.

The Creator delegates definition of the factory to its subclasses so that it returns an instance of the appropriate Concrete Product.

Example Scenario: An application framework that allows users to create and open documents of various types. A generic application class might have an operation to create a new document. Subclasses could then override this operation to create specific document types (e.g., creating , creating ).

Applicability: The Factory Method pattern should be used when:

  • A class cannot anticipate the class of objects it must create.

  • A class wants its subclasses to specify the objects it creates.

Consequences:

  • Advantages:

    • Decoupling: The Factory Method decouples the client code (Creator) from concrete product classes. The code only concerns itself with the interface and can therefore work with any . This promotes flexibility and makes it easier to introduce new product types.

    • Extensibility: New product types can be added by creating new and subclasses without modifying existing code.

  • Disadvantages/Considerations:

    • Can lead to a proliferation of subclasses, as a new Creator subclass might be needed for every new Concrete Product.

Abstract Factory Pattern

Category: Creational, Object

Alias: Kit

Intention: Provides an interface for creating families of related or interdependent objects without specifying their concrete classes.

Motivation: Consider a graphical user interface (GUI) toolkit that supports different look-and-feel styles (e.g., Unix, Windows, macOS). An application built with this toolkit should be able to switch its entire look-and-feel by changing a single parameter, without modifying the code that creates individual GUI components (buttons, menus, windows). The Abstract Factory pattern allows the creation of consistent families of products (e.g., all Unix-style widgets, or all Windows-style widgets).

Structure: The pattern involves:

  • AbstractFactory: Declares an interface for operations that create abstract products.

  • ConcreteFactory: Implements the operations to create concrete product objects. There will be one concrete factory for each product family (e.g., , ).

  • AbstractProduct: Declares an interface for a type of product object.

  • ConcreteProduct: Implements the AbstractProduct interface.

  • Client: Uses interfaces declared by AbstractFactory and AbstractProduct classes.

A single instance of a is created at runtime. It is responsible for creating concrete products. To create different concrete products, different instances are required. An delegates the creation of instances to its subclass.

Example Scenario: A cross-platform application that needs to create GUI widgets (buttons, text fields) conforming to the operating system's native look and feel. The application shouldn't be hard-coded to create Windows buttons or Unix buttons directly. Instead, it uses an Abstract Factory interface () that has methods like . Concrete factories like and would implement this interface, each returning the appropriate concrete button type.

Applicability: The Abstract Factory pattern is recommended when:

  • A system must be independent of how its products are created, combined, and represented.

  • A system is configured with one of multiple families of products.

  • You want to enforce a consistency constraint on product usage, ensuring that objects from only one product family are used together.

Consequences:

  • Advantages:

    • Isolates Concrete Classes: Clients manipulate classes through their abstract interfaces. The names of concrete product classes are isolated within the concrete factory's implementation and do not appear in the client code.

    • Facilitates Product Family Exchange: A concrete factory class appears in client code only where it is instantiated. It is therefore easy to change the concrete factory used by an application, allowing for easy switching of product families.

    • Promotes Consistency: If product objects from the same family are designed to work together, it is important that an application uses objects from only one family at a time. The Abstract Factory ensures this condition.

  • Disadvantages/Considerations:

    • Difficulty in Handling New Product Types: It is difficult to make Abstract Factories create new types of products. This requires modifying the factory's interface, which implies modifying the Abstract Factory class and all its subclasses.

Strategy Pattern

Category: Behavioral, Object

Alias: Policy

Intention: Defines a family of algorithms, encapsulates each one, and makes them interchangeable. The Strategy pattern allows algorithms to vary independently from clients that use them.

Motivation: Imagine an object that performs an operation with various possible algorithms (e.g., sorting algorithms, tax calculation methods, payment processing strategies). Instead of embedding all these algorithms within the object (leading to large, inflexible conditional statements), the Strategy pattern extracts them into separate, interchangeable objects. This allows the client object to choose an algorithm at runtime without changing its own structure.

Structure: The pattern involves:

  • Strategy: Declares an interface common to all supported algorithms. Context uses this interface to call the algorithm defined by a ConcreteStrategy.

  • ConcreteStrategy: Implements the Strategy interface, providing a specific algorithm.

  • Context: Maintains a reference to a ConcreteStrategy object. It can be configured with a ConcreteStrategy object and optionally defines an interface that lets the Strategy access its data.

Example Scenario:An "Agent" class needs to greet people, but the greeting method (e.g., "Hello," "Bonjour," "Hola") can change. The Strategy pattern would define a interface with a method. Concrete strategies like and would implement this. The class holds a reference to a object and delegates the greeting action to it.

Example (Java Code Excerpts):

// Strategy Interface
public interface Strategie {
    void bonjour();
}

// Concrete Strategy 1
public class StrategieAnglais implements Strategie {
public void bonjour() {
System.out.println("Hello!");
}
}

// Concrete Strategy 2
public class StrategieFrancais implements Strategie {
public void bonjour() {
System.out.println("Bonjour!");
}
}

// Context Class
public class Agent {
private Strategie strategie;

public Agent (Strategie strategie) {
    this.strategie = strategie; // Corrected assignment
}

// Accessor and Mutator (if needed to change strategy at runtime)
public void setStrategie(Strategie strategie) {
    this.strategie = strategie;
}

public void saluer() {
    strategie.bonjour();
}

}

// Client usage
public class Client {
public static void main(String[] args) {
Agent agentEnglish = new Agent(new StrategieAnglais());
agentEnglish.saluer(); // Outputs "Hello!"

    Agent agentFrench = new Agent(new StrategieFrancais());
    agentFrench.saluer(); // Outputs "Bonjour!"

    // Change strategy at runtime
    agentFrench.setStrategie(new StrategieAnglais());
    agentFrench.saluer(); // Outputs "Hello!"
}

}

Consequences:

  • Advantages:

    • Algorithm Flexibility: Allows exchanging algorithms at runtime.

    • Eliminates Conditional Statements: Avoids complex conditional logic ( or statements) for selecting algorithms.

    • Open/Closed Principle: New algorithms can be added without modifying the context class.

  • Disadvantages/Considerations:

    • Increased Number of Objects: Introduces new classes for each strategy, potentially increasing complexity.

    • Client Must Know Strategies: The client must know the different strategies to choose the appropriate one (unless delegated to another object).

Facade Pattern

Category: Structural, Object

Intention: Provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

Motivation: Large software systems often consist of many complex subsystems. Directly interacting with the numerous classes and objects within a subsystem can be overwhelming and error-prone. The Facade pattern offers a simplified, higher-level entry point to the subsystem, hiding its complexity from clients. This reduces coupling between clients and the subsystem, making the subsystem easier to use and evolve independently.

Structure: The pattern involves:

  • Facade: Knows which subsystem classes are responsible for a request. It delegates client requests to appropriate subsystem objects.

  • Subsystem Classes: Implement the subsystem functionality. They handle the work assigned by the Facade object and have no knowledge of the facade.

Clients communicate with the subsystem by sending requests to the facade, which then relays these requests to the appropriate objects within the subsystem. Clients using the facade do not need to directly access the objects of its subsystem.

Example Scenario: Configuring a complex graphical editor. Instead of clients having to interact directly with (trace management), (canvas management), (text management), and (UI management) classes, a can provide simplified methods like or that orchestrate calls to the underlying subsystem components.

Example (Java Code Excerpts):

public class FacadeEditeur {
    GestionTracé tracéMgr;
    GestionCanvas canvasMgr;
    GestionTexte texteMgr;
    GestionIHM ihmMgr;
public FacadeEditeur() {
    // Initialize subsystem components via singletons or factories
    GestionGraphique graphicMgr = GestionGraphique.getGestionGraphique();
    tracéMgr = graphicMgr.getTracéManager();
    canvasMgr = graphicMgr.getCanvasManager();
    texteMgr = graphicMgr.getTexteManager();
    ihmMgr = GestionIHM.getIHMManager();
}

public void changerPolice(String nom, int taille, String style) {
    Police police = new Police(nom, taille, style);
    texteMgr.setPolice(police); // Delegates to subsystem
}

public void setCouleur(Color couleur) {
    tracéMgr.setCouleur(couleur);   // Delegates to multiple subsystem components
    canvasMgr.setCouleur(couleur);
    texteMgr.setCouleur(couleur);
}

}

Applicability: The Facade pattern is used when:

  • You want to provide a simple interface to a complex subsystem.

  • You want to decouple a subsystem from its clients and other subsystems. This promotes independence and portability of the subsystem.

  • You want to layer your subsystem. A facade can define an entry point to each level of the subsystem.

Consequences:

  • Advantages:

    • Simplifies Usage: Hides the subsystem’s components from the client, making it easier to use.

    • Reduced Coupling: Promotes loose coupling between the subsystem and its clients. This allows the subsystem to evolve without affecting its clients.

    • Flexibility: Does not prevent clients from using the subsystem's classes directly if necessary, offering a choice between comprehensiveness and ease of use.

  • Disadvantages/Considerations:

    • Can become a "God Object" if too much responsibility is pushed into the facade.

    • The facade might introduce an additional layer that sometimes needs to be bypassed for advanced usage.

Composite Pattern

Category: Structural, Object

Intention: Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

Motivation: Many systems deal with hierarchical data where individual items and groups of items are often treated similarly. For example, a file system contains individual files and directories (which contain other files and directories). A graphical drawing application might have individual shapes (lines, circles) and groups of shapes. The Composite pattern allows you to represent these hierarchies and treat both individual objects and compositions of objects in the same manner, simplifying client code.

Applicability: The Composite pattern should be used when:

  • You want to represent part-whole hierarchies.

  • You want clients to ignore the difference between individual objects and compositions of objects. The client can then treat all objects in the composite structure uniformly.

Consequences:

  • Advantages:

    • Uniformity: Simplifies clients by treating composites and individual components uniformly. Clients don't have to distinguish between them, simplifying coding logic.

    • Extensibility: Makes it easy to add new types of components (leaves or composites) without changing existing client code.

    • Flexibility: Provides flexibility in representing complex object structures.

  • Disadvantages/Considerations:

    • Over-generalization: Can make your design overly general, making it harder to restrict the types of components a composite can contain.

    • Runtime Checks: Operations that only make sense for specific component types might require runtime checks.

Proxy Pattern

Category: Structural, Object

Alias: Surrogate

Intention: Provides a surrogate or placeholder for another object to control access to it.

Motivation: Sometimes, direct access to an object is either not feasible, undesirable, or requires additional logic. For instance, an object might be very expensive to create, located remotely, or demand access control. A proxy acts as an intermediary, presenting the same interface as the real object but adding a layer of control (or "indirection") before delegating the request to the real object when appropriate.

Types of Proxy:

  • Remote Proxy (Procuration à distance): Represents an object located in a different address space. It handles the encoding of requests and results, facilitating communication over a network.

  • Virtual Proxy (Procuration virtuelle): Creates expensive objects on demand (e.g., an image proxy loads the full image only when it needs to be displayed, not upon creation). This defers instantiation until it's actually needed.

  • Protection Proxy (Procuration de protection): Controls access to the original object, perhaps by checking user permissions before allowing an operation.

  • Smart Reference (Références intelligentes): Replaces a raw pointer or reference. It can perform additional actions such as reference counting, lazy loading, or locking when the object is accessed.

Consequences:

  • Advantages:

    • Controlled Access: Provides a level of indirection that can control access to the real subject.

    • Reduced Overhead: Can defer expensive operations (e.g., in Virtual Proxy).

    • Remote Object Handling: Simplifies interaction with remote objects.

  • Disadvantages/Considerations:

    • Increased Complexity: Adds a layer of indirection, which can complicate the system.

    • Performance Overhead: Each request going through the proxy might incur a small performance overhead.

Iterator Pattern

Category: Behavioral, Object

Alias: Cursor

Intention: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

Motivation: Collections of objects (lists, trees, hash maps) store their elements in different ways. Clients often need to traverse these collections to process their elements. Hard-coding traversal logic for each collection type in the client code is fragile and violates the Single Responsibility Principle. The Iterator pattern abstracts the traversal process, allowing clients to iterate over different types of collections using a common, uniform interface.

Existing Implementations: Modern programming languages extensively use the Iterator pattern:

  • Java's interface (and its predecessor ).

  • C++ Standard Template Library (STL) iterators.

Structure: The pattern involves:

  • Iterator: Defines an interface for accessing and traversing elements.

  • ConcreteIterator: Implements the Iterator interface and keeps track of the current position in the traversal of the aggregate.

  • Aggregate: Defines an interface for creating an Iterator object.

  • ConcreteAggregate: Implements the Iterator creation interface to return an instance of the appropriate ConcreteIterator.

A keeps track of the current object within the aggregate and can determine the next object in the traversal.

Example (Java Code Excerpts for a custom array iterator):

// Generic Iterator Interface (conceptual, modern Java has java.util.Iterator)
public interface Iterateur<E> {
    E premier(); // Get first element
    void suivant(); // Move to next
    boolean estTermine(); // Check if traversal is complete
    E elementCourant(); // Get current element
}

// Concrete Iterator for a hypothetical Tableau (Array) class
public class IterateurTableau<E> implements Iterateur<E> {
private Tableau<E> monTableau;
private int indice = 0;

public IterateurTableau(Tableau&lt;E&gt; tab) {
    this.monTableau = tab;
}

public E premier() {
    indice = 0;
    return monTableau.getElement(indice);
}

public void suivant() {
    indice = indice + 1;
}

public boolean estTermine() {
    return indice == monTableau.size();
}

public E elementCourant() {
    if (estTermine()) {
        throw new NoSuchElementException(); // Handle out of bounds
    }
    return monTableau.getElement(indice);
}

}

Consequences:

  • Advantages:

    • Multiple Traversal Methods: Different iterators can enable various traversal methods for the same aggregate object (e.g., infix, postfix, prefix traversal for a tree).

    • Concurrent Traversal: Multiple iterators can traverse the same aggregate simultaneously without interfering with each other.

    • Uniform Interface: Iterators provide a uniform interface for traversing diverse aggregate structures, regardless of their internal representation.

    • Decoupling: Decouples the algorithm for traversal from the data structure, improving reusability of both.

  • Disadvantages/Considerations:

    • Overhead: Can add a slight overhead due to the additional object.

    • External Iterators: External iterators (where the client controls the traversal) can be less encapsulated than internal iterators (where the aggregate controls it).

Command Pattern

Category: Behavioral, Object

Alias: Action, Transaction

Intention: Encapsulate a request as an object, thereby allowing parameterization of clients with different requests, queuing and logging of requests, and support for undoable operations.

Motivation: In many applications, you need to issue requests to objects without knowing anything about the operation being requested or the receiver of the request. For example, in a menu system, various menu items might trigger different actions (copy, paste, open). Instead of directly binding menu items to concrete actions, the Command pattern abstracts each action into a command object. This decouples the invoker (menu item) from the receiver (document, application) and the action itself, enabling features like undo/redo, macros, and queuing.

Structure: The pattern involves:

  • Command: Declares an interface for executing an operation.

  • ConcreteCommand: Implements the Command interface by binding a receiver to an action. It calls the operation(s) on its receiver to perform the action.

  • Client: Creates a ConcreteCommand object and sets its receiver.

  • Invoker: Asks the command to carry out the request. It does not know the ConcreteCommand class or the Receiver.

  • Receiver: Knows how to perform the operations associated with carrying out a request. Any class can serve as a Receiver.

Example Components:

  • Concrete Command: (Paste Command), (Copy Command)

  • Client: Application

  • Invoker: (Menu Item)

  • Receiver: document, application

Consequences:

  • Advantages:

    • Decoupling: Decouples the object that invokes the operation from the object that knows how to perform it.

    • Extensibility: Makes it easy to add new commands without changing existing classes.

    • Support for Undo/Redo: Commands can store their state to reverse operations.

    • Queueing and Logging: Commands can be stored in a queue for asynchronous execution or logged for persistent storage.

    • Macro Support: Composite commands can be built from simpler commands.

  • Disadvantages/Considerations:

    • Increased Number of Classes: Each command generally requires its own class, potentially leading to a larger codebase.

Visitor Pattern

Category: Behavioral, Object

Intention: Represents an operation to be performed on the elements of an object structure. The Visitor pattern allows you to define a new operation on instances of a class without changing the class on which the operation is applied.

Motivation: Imagine an object structure (e.g., a tree of different node types: and ). You frequently need to perform new operations on these elements. If you add these operations directly to the element classes, the classes become cluttered, and adding new operations requires modifying all element classes, violating the Open/Closed Principle. The Visitor pattern allows you to define new operations (visitors) in a separate object, keeping the element classes stable.

Structure: The pattern typically involves:

  • Visitor: Declares a operation for each class of in the object structure. The operation's name identifies the class of the element it visits.

  • ConcreteVisitor: Implements each operation declared by . Each operation implements a fragment of the algorithm for the corresponding class of object.

  • Element: Defines an operation that takes a visitor as an argument.

  • ConcreteElement: Implements the operation, calling the appropriate operation on the visitor.

Example (Java Code Excerpts):

// Abstract Element
public abstract class Element {
    abstract void accept(Visitor v);
}

// Concrete Element A
public class ElementA extends Element {
void accept(Visitor v) {
v.visit(this); // Double dispatch: calls visit(ElementA) on the visitor
}
}

// Concrete Element B
public class ElementB extends Element {
void accept(Visitor v) {
v.visit(this); // Double dispatch: calls visit(ElementB) on the visitor
}
}

// Abstract Visitor
public abstract class Visitor {
abstract void visit(ElementA a);
abstract void visit(ElementB b);
}

// Concrete Visitor for "Goodbye" operation
public class VisitorGoodbye extends Visitor {
void visit(ElementB b) {
System.out.println("Goodbye b");
}
void visit(ElementA a) {
System.out.println("Goodbye a");
}
}

// Concrete Visitor for "Hello" operation
public class VisitorHello extends Visitor {
void visit(ElementB b) {
System.out.println("Hello b");
}
void visit(ElementA a) {
System.out.println("Hello a");
}
}

// Client usage
public class Client {
public static void main(String[] args) {
Random r = new Random();
ArrayList<Element> liste = new ArrayList<>();

    // Populate list randomly with ElementA and ElementB
    for (int i = 0; i &lt; 10; i++) {
        if (r.nextBoolean()) {
            liste.add(new ElementA());
        } else {
            liste.add(new ElementB());
        }
    }

    // Apply a random operation (VisitorGoodbye or VisitorHello) to elements
    for (Element e : liste) {
        if (r.nextBoolean()) {
            e.accept(new VisitorGoodbye());
        } else {
            e.accept(new VisitorHello());
        }
    }
}

}

Consequences:

  • Advantages:

    • Adding New Operations Simplified: Adding new operations is simplified by the Visitor pattern. You just need to add a new concrete visitor class, implementing the new operation's logic for each element type.

    • Operation Centralization: Operations on an object (or class) are centralized within its visitor, making it easy to see all operations for a specific element type in one place.

    • Decoupling: Decouples algorithms from the object structure.

  • Disadvantages/Considerations:

    • Adding New Concrete Element Types is Cumbersome: It is tedious to add new concrete element classes. This requires adding a new method, taking the new concrete object into account, in each concrete visitor class.

    • Breaks Encapsulation: Visitors often need to access the internal state of elements, potentially breaking their encapsulation.

Learning and Application

To effectively use design patterns, engineers need to:

  • Identify Quality Software Design Challenges: Recognize the issues that design patterns aim to solve (e.g., lack of reusability, tight coupling).

  • Know the Tools: Understand how to use patterns to achieve modular and extensible designs.

  • Recognize Common Patterns: Be familiar with the most frequently used design patterns.

  • Select Appropriate Patterns: Identify which patterns are best suited to address a particular problem.

  • Adapt Patterns: Integrate suitable design patterns into existing designs.

Pedagogical Methods and Prerequisites

Effective learning typically involves:

  • Interactive Courses: Engaging discussions and explanations of pattern concepts.

  • Virtual OS TPs (Practical Work): Hands-on experience implementing patterns in a controlled environment.

  • Case Studies: Analyzing real-world scenarios where patterns are applied to solve design problems.

Prerequisites for successful engagement with design patterns include the ability to:

  • Develop information systems using a language like Java, adhering to best practices.

  • Propose object-oriented designs for information systems.

  • Utilize UML diagrams to model systems and formalize their behavior.

  • Work autonomously.

Conclusion

Design patterns are not ready-made code; they are documented solutions, templates that can be adapted to specific problems. They provide a common language for designers, making communication about complex design structures more efficient and less ambiguous. Understanding and applying design patterns is a crucial skill for any software engineer aiming to build robust, maintainable, and scalable object-oriented systems.

References

  • Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1995). Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. (The original "Gang of Four" book)

  • Alexander, C., Ishikawa, S., & Silverstein, M. (1977). A Pattern Language: Towns, Buildings, Construction. Oxford University Press.

  • Jacobson, M., & Jacobson, K. (2002). Patterns of Home: The Ten Essentials of Enduring Design. Taunton Press.

Inizia un quiz

Testa le tue conoscenze con domande interattive