阅读更多
1 Overview
Here are some of the commonly used design patterns in Java:
- Creational Patterns:
- Singleton Pattern: Ensures that only one instance of a class is created throughout the application.
- Factory Pattern: Provides an interface for creating objects, but allows subclasses to decide which class to instantiate.
- Abstract Factory Pattern: Provides an interface for creating families of related or dependent objects without specifying their concrete classes.
- Builder Pattern: Separates the construction of complex objects from their representation, allowing the same construction process to create different representations.
- Prototype Pattern: Creates objects by cloning existing ones, providing a way to create new instances without explicitly invoking a constructor.
- Structural Patterns:
- Adapter Pattern: Converts the interface of a class into another interface that clients expect, allowing classes to work together that couldn’t otherwise because of incompatible interfaces.
- Bridge Pattern: Decouples an abstraction from its implementation, allowing both to vary independently.
- Composite Pattern: Composes objects into tree structures, representing part-whole hierarchies. It lets clients treat individual objects and compositions uniformly.
- Decorator Pattern: Dynamically adds behaviors or responsibilities to objects without modifying their structure.
- Proxy Pattern: Provides a surrogate or placeholder for another object to control access to it.
- Behavioral Patterns:
- Observer Pattern: Defines a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically.
- Strategy Pattern: Defines a family of interchangeable algorithms, encapsulates each one, and makes them interchangeable within a context.
- Template Method Pattern: Defines the skeleton of an algorithm in a method, deferring some steps to subclasses.
- Iterator Pattern: Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
- State Pattern: Allows an object to alter its behavior when its internal state changes.
- Chain of Responsibility Pattern: Avoids coupling the sender of a request to its receiver by giving multiple objects a chance to handle the request.
- Command Pattern: Encapsulates a request as an object, thereby allowing users to parameterize clients with queues, requests, and operations.
- Interpreter Pattern: Defines a representation for a grammar or language and provides a way to interpret sentences in the language.
- Mediator Pattern: Defines an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly.
- Visitor Pattern: Defines a new operation to a class without changing the class itself.
2 Creational Patterns
2.1 Singleton Pattern
The Singleton Pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. This pattern is often used when there should be a single, shared instance of a class that needs to be accessed from various parts of the program.
Examples:
-
case-1:
1
2
3
4
5
6
7
8
9class Singleton{
private static Singleton instance=new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return instance;
}
} -
case-2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Singleton{
private static Singleton instance;
private Singleton(){}
public synchronized static Singleton getSingleton(){
if(instance==null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
} -
case-3
1
2
3
4
5
6
7
8
9
10
11
12class Singleton{
private static final class LazyInitialize{
private static Singleton instance=new Singleton();
}
private Singleton(){}
public static Singleton getSingleton(){
return LazyInitialize.instance;
}
}
2.2 Factory Pattern
The Factory Pattern is a creational design pattern that provides an interface or base class for creating objects, but delegates the responsibility of determining the concrete type of object to the subclasses. It allows for the creation of objects without specifying their exact class, promoting loose coupling and flexibility in the code.
Examples:
1 | // Product interface |
In this example, we have an interface Vehicle
that defines the common behavior of different types of vehicles. The Car
and Motorcycle
classes implement this interface and provide their own implementations of the drive()
method.
The VehicleFactory
class acts as the factory, providing a static method createVehicle()
that takes a type parameter. Based on the type provided, it creates and returns the corresponding concrete Vehicle
object. In this case, it creates either a Car
or a Motorcycle
object.
In the Main
class, we can use the VehicleFactory
to create instances of vehicles without directly instantiating concrete classes. This promotes flexibility because we can easily extend the factory to create new types of vehicles by adding new classes without modifying the client code.
By using the Factory Pattern, we decouple the client code from the concrete classes, allowing for easier maintenance, code extensibility, and testability.
2.3 Abstract Factory Pattern
The Abstract Factory Pattern is a creational design pattern that provides an interface or abstract class for creating families of related or dependent objects without specifying their concrete classes. It is an extension of the Factory Pattern, where multiple factories are grouped together under an abstract factory, allowing the creation of related objects as a family.
Examples:
1 | // Abstract product A |
In this example, we have two families of related products: Window
and Button
. Each family has multiple variations or implementations: WindowsWindow
and MacWindow
for the Window
family, and WindowsButton
and MacButton
for the Button
family.
The GUIFactory
interface represents the abstract factory, declaring the methods createWindow()
and createButton()
for creating the respective products. The WindowsFactory
and MacFactory
classes are the concrete factories that implement the GUIFactory
interface and provide the specific implementations of creating Windows or Mac products.
The client code uses the abstract factory (GUIFactory
) to create the products without directly instantiating the concrete classes. The client code is decoupled from the specific product implementations and can work with any family of products created by the chosen factory.
The Abstract Factory Pattern promotes the creation of families of related objects in a unified and flexible manner. It allows for easy interchangeability of product families by simply switching the concrete factory, without affecting the client code that uses the products.
2.4 Builder Pattern
The Builder Pattern is a creational design pattern that provides a way to construct complex objects step by step. It separates the construction of an object from its representation, allowing the same construction process to create different representations of the object.
Examples:
1 | // Product class |
In this example, we have a Car
class that represents a complex object to be constructed. The Car
class has private properties for the make, model, year, and number of doors. It also has a private constructor to enforce the use of the builder for object creation.
The inner Builder
class provides setter methods to set the different properties of the car. Each setter method returns the builder object itself (this
) to support method chaining.
The Builder
class also has a build()
method that constructs and returns the final Car
object. The build()
method creates a new Car
instance, sets the properties based on the values provided through the builder, and returns the constructed Car
object.
In the Main
class, we can use the builder to create a Car
object by chaining the setter methods and finally calling the build()
method. This allows us to construct a Car
object step by step, setting only the desired properties.
The Builder Pattern provides a more readable and flexible way to create objects, especially when dealing with complex objects with many properties. It allows for the creation of different representations of the object by providing different builders or using optional setter methods in the builder.
2.5 Prototype Pattern
The Prototype Pattern is a creational design pattern that allows creating new objects by cloning or copying existing objects, known as prototypes. It provides a way to create objects without relying on traditional constructors, thus avoiding the need to explicitly instantiate classes.
Examples:
1 | // Prototype interface |
In this example, we have an interface Prototype
that declares the clone()
method, responsible for creating a copy of the prototype object. The concrete prototypes ConcretePrototype1
and ConcretePrototype2
implement this interface and provide their own implementations of the clone()
method.
In the Main
class, we create instances of the concrete prototypes ConcretePrototype1
and ConcretePrototype2
. To create a copy of a prototype, we simply call the clone()
method on the prototype object, which returns a new instance of the same concrete prototype class.
By using the Prototype Pattern, we can create new objects by cloning existing objects, eliminating the need for complex instantiation logic. This pattern is particularly useful when creating objects that are costly to create or require initialization with a lot of data. It allows us to create new instances efficiently by copying the existing ones and then modifying them as needed.
It’s worth noting that in Java, the clone()
method is inherited from the java.lang.Object
class. By implementing the Cloneable
interface, you can use the built-in clone()
method to create a shallow copy of an object. However, you may need to override the clone()
method to perform a deep copy if the object contains mutable references.
3 Structural Patterns
3.1 Adapter Pattern
The Adapter Pattern is a structural design pattern that allows objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, enabling them to collaborate and interact seamlessly.
Examples:
1 | // Target interface |
In this example, we have a MediaPlayer
interface that defines the target interface for playing audio files. The AudioPlayer
class implements this interface and handles the playing of MP3 files directly.
The AdvancedMediaPlayer
class is the existing class with its own interface for playing advanced audio formats like VLC and MP4.
The MediaAdapter
class acts as an adapter, implementing the MediaPlayer
interface while internally using the AdvancedMediaPlayer
to handle the specific audio format.
The AudioPlayer
class acts as the client and receives requests to play audio files. When it receives a request for an incompatible format (e.g., VLC), it creates an instance of the MediaAdapter
specific to that format and delegates the play request to the adapter.
By using the Adapter Pattern, we can decouple the client (AudioPlayer
) from the specific format of the audio being played (AdvancedMediaPlayer
). The adapter allows the client to work with any audio format by adapting the requests to the appropriate methods of the AdvancedMediaPlayer
.
In the example, when we play an MP3 file, the AudioPlayer
handles it directly. When we play a VLC file, the AudioPlayer
creates a MediaAdapter
for VLC, which internally uses the AdvancedMediaPlayer
to play the file.
The Adapter Pattern enables interaction between incompatible interfaces, making it easier to reuse existing classes and integrate them into new systems without modifying their original code.
3.2 Bridge Pattern
The Bridge Pattern is a structural design pattern that decouples an abstraction from its implementation, allowing both to vary independently. It provides a way to separate the interface (abstraction) and the implementation into separate class hierarchies, which can evolve independently of each other.
Examples:
1 | // Abstraction interface |
In this example, we have an abstraction hierarchy represented by the Shape
interface. The Circle
and Rectangle
classes are concrete implementations of the Shape
interface.
The Color
interface represents the implementation hierarchy. The RedColor
and BlueColor
classes are concrete implementations of the Color
interface.
The Bridge Pattern connects the abstraction and implementation hierarchies. The Shape
interface has a reference to an instance of the Color
interface. This way, the Shape
can delegate the responsibility of applying the color to the specific Color
implementation.
In the Main
class, we create instances of concrete shapes (Circle
and Rectangle
) and associate them with specific colors (RedColor
and BlueColor
). We can call the draw()
method on the shapes, which in turn calls the appropriate draw()
method on the specific shape implementation. Similarly, the applyColor()
method is called on the associated color implementation.
By using the Bridge Pattern, we separate the abstraction of shapes from their specific implementations and the application of colors from the specific color implementations. This allows for greater flexibility and extensibility, as we can easily add new shapes and colors independently without modifying existing classes.
The Bridge Pattern helps in achieving loose coupling between abstractions and implementations, enabling changes in one without affecting the other.
3.3 Composite Pattern
The Composite Pattern is a structural design pattern that allows you to compose objects into tree structures and represent part-whole hierarchies. It lets clients treat individual objects and compositions of objects uniformly.
Examples:
1 | // Component interface |
In this example, we have a Component
interface that defines the common operations for both leaf objects (Leaf
) and composite objects (Composite
). The Leaf
class represents the individual objects, while the Composite
class represents the composite objects that can contain other components.
The Composite
class contains a collection of Component
objects and provides methods to add or remove components. The render()
method in the Composite
class recursively calls the render()
method on each of its child components, resulting in a tree-like structure.
In the Main
class, we create instances of leaf components (Leaf
) and composite components (Composite
). We add leaf components to composites and composites to other composites, forming a hierarchical structure.
When we call the render()
method on the top-level composite, it internally calls the render()
method on each component, resulting in the rendering of the entire component hierarchy.
The Composite Pattern allows clients to treat individual objects and compositions of objects uniformly. It simplifies the client’s code as it doesn’t need to distinguish between leaf objects and composite objects when operating on them. The pattern also enables the creation of complex structures by combining simple objects in a recursive manner.
The Composite Pattern is useful in scenarios where you need to represent part-whole hierarchies or tree-like structures, and you want to apply operations uniformly across individual objects and compositions.
3.4 Decorator Pattern
The Decorator Pattern is a structural design pattern that allows adding new behaviors or responsibilities to objects dynamically by wrapping them with decorator objects. It provides an alternative to subclassing for extending functionality.
Examples:
1 | // Component interface |
In this example, we have a Pizza
interface that defines the basic operations of a pizza, such as getDescription()
and getCost()
. The Margherita
class is a concrete implementation of the Pizza
interface.
The PizzaDecorator
class is an abstract class that acts as the base decorator. It implements the same interface (Pizza
) and holds a reference to a Pizza
object. It delegates the calls to the wrapped pizza object, effectively adding functionality or modifying behavior.
The CheeseDecorator
and MushroomDecorator
classes are concrete decorators that extend the PizzaDecorator
class. They add specific behaviors (extra cheese or mushrooms) to the decorated pizza by modifying the description and cost.
In the Main
class, we create a Margherita
pizza instance. We then wrap it with decorators (CheeseDecorator
and MushroomDecorator
) to add extra cheese and mushrooms to the pizza dynamically. The decorators modify the description and cost of the pizza, allowing for the dynamic addition of new features without modifying the original pizza class.
The Decorator Pattern provides a flexible and dynamic way to extend the functionality of objects without relying on subclassing. It promotes the principle of open-closed design, as new behaviors can be added by creating new decorator classes rather than modifying existing classes. It allows for the composition of objects with different behaviors at runtime.
3.5 Proxy Pattern
The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder object, controlling access to another object. It allows for the creation of a representative object that can control the access, perform additional operations, or act as a protective layer for the underlying object.
Examples:
1 | // Subject interface |
In this example, we have an Image
interface that represents the subject. The RealImage
class is the real subject that performs the actual loading and displaying of an image.
The ImageProxy
class acts as a proxy for the RealImage
. It implements the Image
interface and has a reference to a RealImage
object. When the display()
method is called on the proxy, it checks if the real image has been loaded. If not, it creates a RealImage
instance and then calls the display()
method on it.
In the Main
class, we use the proxy to control access to the real image objects. When we call the display()
method on the proxy, it dynamically creates the real image if necessary and delegates the display operation to it.
The Proxy Pattern is useful in various scenarios, such as lazy loading of resources, access control, caching, or adding additional functionality around an object. It allows for transparent access to the underlying object by providing a surrogate with the same interface.
In the example, the proxy delays the creation of the real image until it is actually needed, improving performance by loading images on demand. It acts as a protective layer, allowing the client to work with the proxy without directly interacting with the real image until necessary.
4 Behavioral Patterns
4.1 Observer Pattern
The Observer Pattern is a behavioral design pattern that establishes a one-to-many dependency between objects, so that when one object changes its state, all its dependents (observers) are automatically notified and updated.
Examples:
1 | import java.util.ArrayList; |
In this example, we have a Subject
interface that defines the methods to register, remove, and notify observers. The WeatherStation
class is the concrete subject that maintains the temperature and manages the list of observers.
The Observer
interface declares the update()
method that is called by the subject to notify the observers. The Display
and Log
classes are concrete observers that implement the Observer
interface and define their own behavior when the temperature changes.
In the Main
class, we create a WeatherStation
object and instances of the Display
and Log
classes as observers. We register the observers with the weather station. When the temperature changes, the weather station calls the update()
method on each registered observer, passing the new temperature as a parameter.
By using the Observer Pattern, we achieve loose coupling between the subject and its observers. The subject doesn’t need to know the specific details of its observers, allowing for flexibility in adding, removing, or modifying observers without affecting the subject or other observers.
In the example, the weather station notifies the display and log observers about temperature changes, and each observer can perform its specific actions based on the updated temperature.
The Observer Pattern is commonly used in scenarios where there is a one-to-many relationship between objects, and changes in one object should be propagated to multiple dependent objects. It promotes modularity, extensibility, and maintainability in the design of systems.
4.2 Strategy Pattern
The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm or strategy dynamically at runtime. It allows encapsulating interchangeable behaviors and provides a way to vary the behavior of an object by encapsulating it within separate classes.
Examples:
1 | // Strategy interface |
In this example, we have a PaymentStrategy
interface that defines the contract for different payment strategies. The CreditCardStrategy
and PayPalStrategy
classes are concrete implementations of the payment strategy interface.
The ShoppingCart
class acts as the context that utilizes the selected payment strategy. It has a reference to the payment strategy and uses it for the checkout process.
In the Main
class, we create a shopping cart with a specific payment strategy, such as a credit card. We add items to the cart and calculate the total amount. Then, we call the checkout()
method on the cart, which internally invokes the pay()
method of the selected payment strategy.
By using the Strategy Pattern, we decouple the payment strategy from the shopping cart. The shopping cart only needs to know the PaymentStrategy
interface and can switch between different strategies at runtime without modifying its code.
The Strategy Pattern provides flexibility in selecting and switching algorithms or behaviors dynamically. It allows for easy addition of new strategies and enhances code reuse, modularity, and maintainability.
In the example, we can easily add more payment strategies by implementing the PaymentStrategy
interface and using them with the shopping cart without impacting existing code.
4.3 Template Method Pattern
The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class, allowing subclasses to provide specific implementations of certain steps of the algorithm. It promotes code reuse and provides a way to define the overall structure of an algorithm while allowing subclasses to customize certain steps.
Examples:
1 | // Abstract class with template method |
In this example, we have an abstract class Pizza
that represents the template for making a pizza. It defines the overall structure of the pizza-making process using a template method called makePizza()
. The template method consists of several steps that are common to all pizzas.
The abstract class provides default implementations for some steps, such as preparing the dough, baking the pizza, and serving the pizza. It also defines abstract methods, such as addIngredients()
, which must be implemented by concrete subclasses.
The concrete subclasses, MargheritaPizza
and PepperoniPizza
, extend the Pizza
class and provide their specific implementations for adding ingredients. They can also override certain steps if needed, such as the decision to add cheese in the shouldAddCheese()
method.
In the Main
class, we create instances of the concrete pizza subclasses and call the makePizza()
method on them. The template method (makePizza()
) is responsible for executing the overall pizza-making process by invoking the defined steps and allowing subclasses to provide specific implementations where required.
The Template Method Pattern enables the creation of a common algorithm structure while allowing subclasses to customize certain steps. It promotes code reuse and provides a way to define the skeleton of an algorithm in a base class while delegating the implementation details to subclasses.
In the example, the Pizza
class defines the common structure for making a pizza, and the concrete subclasses provide their specific ingredient additions. The template method ensures that the overall pizza-making process follows the defined steps, while allowing subclasses to have flexibility in ingredient selection and other customizations.
4.4 Iterator Pattern
The Iterator Pattern is a behavioral design pattern that provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. It separates the traversal logic from the underlying collection, making it easier to iterate over the elements of a collection in a consistent manner.
Examples:
1 | import java.util.ArrayList; |
In this example, we have an Iterator
interface that defines the methods for iterating over a collection, including hasNext()
to check if there are more elements and next()
to retrieve the next element.
The Aggregate
interface defines the method createIterator()
to create an iterator for the collection.
The ListIterator
class is a concrete iterator implementation that iterates over a list of items. It keeps track of the current position and provides the implementation for the iterator methods.
The MyList
class is a concrete aggregate implementation that holds a list of items. It implements the createIterator()
method, which creates a ListIterator
and returns it as an Iterator
.
In the Main
class, we create an instance of MyList
and add some items to it. We then obtain an iterator by calling createIterator()
on the list. We can use the iterator to iterate over the elements using hasNext()
and next()
until all elements have been traversed.
The Iterator Pattern provides a standardized way to traverse the elements of a collection without exposing its internal structure. It decouples the collection from its iteration logic, making it easier to change the iteration algorithm or use different types of iterators.
In the example, the MyList
class encapsulates the list of items, and the ListIterator
provides a way to iterate over the items without exposing the list itself. This allows for a flexible and uniform way to iterate over different collections and provides a consistent interface for accessing elements regardless of the underlying data structure.
4.5 State Pattern
The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. It encapsulates state-specific logic into separate state classes and enables an object to transition between different states at runtime.
Examples:
1 | // Context class |
In this example, we have a Context
class that represents the object whose behavior changes based on its internal state. It has a reference to the current state and delegates the handling of operations to the state object.
The State
interface defines the methods that handle operations based on the current state. Each concrete state class implements the State
interface and provides its own implementation of the handleOperation()
method. The state classes can also modify the state of the context object as needed.
In the Main
class, we create an instance of the Context
class and set the initial state to ConcreteStateA
. We then call the performOperation()
method on the context object, which delegates the operation handling to the current state object.
By using the State Pattern, we can encapsulate the behavior associated with each state into separate state classes. This promotes better organization and maintainability of the code as each state can be treated as an independent object.
In the example, the context object can transition between ConcreteStateA
and ConcreteStateB
by calling the handleOperation()
method. The state classes handle the operations differently based on the current state and can modify the state of the context as needed.
The State Pattern is useful when an object’s behavior depends on its internal state and needs to change dynamically at runtime. It helps in eliminating conditional statements and promotes cleaner code by separating state-specific behavior into individual state classes.
4.6 Chain of Responsibility Pattern
The Chain of Responsibility Pattern is a behavioral design pattern that allows an object to pass a request along a chain of potential handlers until the request is handled or reaches the end of the chain. It decouples the sender of a request from its receivers and provides a way to handle the request dynamically.
Examples:
1 | // Handler interface |
In this example, we have a Handler
interface that defines the methods for setting the next handler in the chain and handling requests. The concrete handler classes, ConcreteHandlerA
and ConcreteHandlerB
, implement the Handler
interface.
Each concrete handler class has a reference to the next handler in the chain, and they handle requests based on their specific criteria. If a request cannot be handled by a handler, it is passed to the next handler in the chain until it reaches the end of the chain.
The Request
class represents a request with a specific type. The RequestType
enumeration defines the types of requests.
In the Main
class, we create a chain of responsibility by setting the next handler for each handler in the chain. We then create requests with different types and pass them to the first handler in the chain using the handleRequest()
method.
By using the Chain of Responsibility Pattern, we can dynamically build a chain of handlers and pass a request through the chain until it is handled or reaches the end. It provides flexibility in handling requests and allows for easy extension or modification of the chain without impacting the client code.
In the example, the requests are handled by ConcreteHandlerA
if the request type matches RequestType.TYPE_A
and by ConcreteHandlerB
if the request type matches RequestType.TYPE_B
. If a request type does not match any handler’s criteria or the chain ends, a message is displayed indicating that the request cannot be handled.
The Chain of Responsibility Pattern is useful in scenarios where there are multiple objects that can handle a request, and the handler needs to be determined dynamically at runtime. It promotes loose coupling between the sender of a request and its receivers, allowing for flexible and customizable handling of requests in a chain-like structure.
4.7 Command Pattern
The Command Pattern is a behavioral design pattern that encapsulates a request or operation as an object, allowing parameterization of clients with different requests, queueing or logging requests, and supporting undoable operations. It decouples the sender of a request from the object that performs the action, providing a way to issue requests without knowing the receiver’s details.
Examples:
1 | // Command interface |
In this example, we have a Command
interface that defines the execute()
method, which encapsulates an operation. The Light
class acts as the receiver and defines the actions that can be performed on the light, such as turning it on or off.
The concrete command classes, TurnOnCommand
and TurnOffCommand
, implement the Command
interface and encapsulate the corresponding operations. They hold a reference to the receiver object (Light
) and invoke the appropriate methods on it when the execute()
method is called.
The RemoteControl
class serves as the invoker. It has a Command
field and provides methods to set the command and execute it by calling the execute()
method on the command object.
In the Main
class, we create an instance of the RemoteControl
and the Light
objects. We then create concrete commands (TurnOnCommand
and TurnOffCommand
) and associate them with the remote control using the setCommand()
method. Finally, we press the button on the remote control, which triggers the execution of the associated command.
By using the Command Pattern, we decouple the sender (invoker) of a request from the receiver (command) that performs the action. This allows for the parameterization of clients with different requests, queueing or logging of requests, and support for undoable operations.
In the example, the remote control acts as the invoker, and the light object acts as the receiver. The commands encapsulate the operations (turnOn()
and turnOff()
) and can be executed by the remote control without knowing the details of the receiver.
The Command Pattern is useful in various scenarios, such as implementing menu systems, queuing requests, implementing undo/redo functionality, or supporting transactional operations. It provides flexibility, extensibility, and allows for better separation of concerns by encapsulating requests as objects.
4.8 Interpreter Pattern
The Interpreter Pattern is a behavioral design pattern that defines a language or grammar and provides a way to interpret and evaluate sentences or expressions in that language. It allows for the creation of a language interpreter by defining classes for different elements of the grammar and their behavior.
Examples:
1 | // Abstract expression interface |
In this example, we have an Expression
interface that represents the abstract expression. The TerminalExpression
class is a concrete implementation of the terminal expression, which evaluates a specific part of the grammar. The AndExpression
and OrExpression
classes are concrete implementations of non-terminal expressions that combine multiple expressions.
The expressions can interpret a given context (sentence or expression) and evaluate to true or false based on the interpretation. The AndExpression
class evaluates to true only if both expressions it contains evaluate to true, while the OrExpression
class evaluates to true if either of its expressions evaluates to true.
In the Main
class, we create expressions for various conditions, such as “John is married” and “Jane is single,” using the terminal expressions and combine them using non-terminal expressions. We can then interpret these expressions using different contexts to evaluate whether the expressions hold true or false.
The Interpreter Pattern is useful when there is a need to interpret and evaluate sentences or expressions in a specific language or grammar. It enables the creation of a language interpreter by defining classes for different elements of the grammar and their behavior.
In the example, the expressions represent conditions or rules that can be interpreted and evaluated based on the given context. By combining different expressions using non-terminal expressions, complex conditions can be built and interpreted to determine their truth value.
4.9 Mediator Pattern
The Mediator Pattern is a behavioral design pattern that promotes loose coupling between components by encapsulating their interaction within a mediator object. It centralizes communication and coordination between objects, reducing their direct dependencies and simplifying their interactions.
Examples:
1 | // Mediator interface |
In this example, we have a Mediator
interface that defines the communication protocol between colleagues. The ConcreteMediator
class is a concrete implementation of the mediator that manages the communication between colleagues.
The Colleague
interface represents the individual components that interact with each other through the mediator. The ConcreteColleague
class is a concrete implementation of a colleague that sends and receives messages.
In the Main
class, we create a mediator object (ConcreteMediator
) and two colleague objects (ConcreteColleague
). We set the colleagues in the mediator using the mediator’s setter methods. When a colleague sends a message, it calls the mediator’s sendMessage()
method, passing the message and itself as the sender. The mediator then relays the message to the other colleague.
By using the Mediator Pattern, the communication between colleagues is encapsulated within the mediator object. Colleagues are decoupled from each other and have no direct knowledge of other colleagues. They interact solely through the mediator, which reduces dependencies and simplifies their interactions.
In the example, the ConcreteMediator
acts as the central communication hub, relaying messages between the colleagues (ConcreteColleague
). The colleagues can send messages to each other by calling the sendMessage()
method on themselves, and the mediator ensures that the messages reach the intended recipients.
The Mediator Pattern is beneficial in scenarios where a set of objects need to communicate with each other in a loosely coupled manner. It promotes modularity, simplifies interactions, and allows for easy addition or removal of components without affecting others.
4.10 Visitor Pattern
The Visitor Pattern is a behavioral design pattern that allows adding new operations or behaviors to a set of related classes without modifying their code. It achieves this by separating the implementation of the operations into separate visitor classes. The visitor classes can visit and operate on the elements of the object structure, enabling new behaviors to be added without modifying the classes themselves.
Examples:
1 | // Element interface |
In this example, we have an Element
interface that represents the elements of an object structure. The concrete element classes (ConcreteElementA
and ConcreteElementB
) implement the Element
interface and provide their specific implementation of the accept()
method.
The Visitor
interface defines the visitor’s contract and declares the visit methods for each concrete element class. The concrete visitor class (ConcreteVisitor
) implements the Visitor
interface and provides the implementation for each visit method.
The ObjectStructure
class represents the object structure that contains the elements. It provides methods to add elements to the structure and accepts a visitor by iterating over the elements and calling their accept()
method.
In the Main
class, we create an instance of the ObjectStructure
and add some concrete elements to it. We also create an instance of the ConcreteVisitor
. Finally, we call the accept()
method on the object structure, passing the visitor as an argument. This triggers the visiting and operation on each element.
By using the Visitor Pattern, we can add new operations or behaviors to the elements without modifying their code. The elements accept a visitor, which visits and operates on them based on the specific visit method implementation in the visitor class.
In the example, the ConcreteVisitor
visits and operates on each concrete element class. The visitor can perform additional operations or logic on the elements while maintaining separation between the elements and their specific behaviors.
The Visitor Pattern is useful when there is a need to add new behaviors or operations to a set of related classes without modifying their code. It enables a flexible and extensible way to operate on elements in an object structure while keeping the code modular and maintainable.
4.10.1 More
classDiagram Visitor --* Client ObjectStructure --* Client Element --* ObjectStructure ElementA ..|> Element ElementB ..|> Element VisitorA ..|> Visitor VisitorB ..|> Visitor class Visitor { +visit(ElementA) void +visit(ElementB) void } class VisitorA { +visit(ElementA) void +visit(ElementB) void } class VisitorB { +visit(ElementA) void +visit(ElementB) void } class Element { +accept(Visitor) void } class ElementA { +accept(Visitor) void +operateA() void } class ElementB { +accept(Visitor) void +operateB() void } class Client { +Visitor visitor +ObjectStructure objectStructure } class ObjectStructure { +ArrayList~Element~ elements +accept(Visitor) void +addElement(Element) void +removeElement(Element) void }
访问者模式包含如下角色:
Visitor
:接口或者抽象类,定义了对每个Element
访问的行为,它的参数就是被访问的元素。方法个数理论上与元素的种类是一样的。因此,访问者模式要求元素的类型要稳定,如果经常添加、移除元素类,必然会导致频繁地修改Visitor
接口,如果出现这种情况,则说明不适合使用访问者模式- 方法可以是同名的,比如
visit(ElementA)
、visit(ElementB)
等等;也可以是不同名的,比如visitElementA(ElementA)
、visitElementB(ElementB)
等等
- 方法可以是同名的,比如
ConcreteVisitor
:具体的访问者,它需要给出对每一个元素类访问时所产生的具体行为Element
:元素接口或者抽象类,它定义了一个接受访问者的方法(accept
),其意义是指每一个元素都要可以被访问者访问ConcreteElement
:具体的元素类,它提供接受访问的具体实现。而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法ObjectStructure
:定义当中所提到的对象结构,对象结构是一个抽象表述,它内部管理了元素集合,并且可以迭代这些元素提供访问者访问
优势:
- 符合单一职责原则
- 优秀的扩展性
- 灵活性
劣势:
- 具体元素对访问者公布细节,违反了迪米特原则
- 具体元素变更比较困难
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象
动态双分派:
- 第一次分派:
element.accept(visitor)
,动态分派,根据element
的实际类型调用对应的accept
方法 - 第二次分派:
ConcreteElement::accept
的方法中会调用visitor.visit(this)
,静态分派,由于this
的类型是ConcreteElement
,在一系列的visit
重载方法中,调用匹配的那一个