Behavioral patterns involve the allocation of responsibilities between algorithms and objects. Behavioral patterns describe the patterns of objects and classes, as well as their communication patterns. They delineate complex control flows that are difficult to track during program execution and can be divided into behavioral class patterns and behavioral object patterns.
- Behavioral class patterns use inheritance mechanisms to dispatch behavior between classes.
- Behavioral object patterns use object aggregation to allocate behavior. Some behavioral object patterns describe how a set of peer objects collaborate to accomplish tasks that any individual object cannot complete alone.
Chain of Responsibility#
To avoid coupling the request sender with multiple request handlers, all request handlers are linked in a chain by having each object remember a reference to its next object; when a request occurs, it can be passed along this chain until an object handles it.
Structure#
- Abstract Handler: Defines the interface for handling requests (e.g.,
handleRequest()
) and holds a reference to the next handler. - Concrete Handler: Implements the handling logic, deciding whether to handle the request or pass it along.
- Client: Creates the handling chain and triggers requests.
Implementation
// Abstract Handler
public abstract class Approver {
protected Approver nextApprover;
public void setNext(Approver next) { this.nextApprover = next; }
public abstract void handleRequest(LeaveRequest request);
}
// Concrete Handler: Group Leader
public class GroupLeader extends Approver {
@Override
public void handleRequest(LeaveRequest request) {
if (request.getDays() <= 2) {
System.out.println("Group Leader approves leave");
} else if (nextApprover != null) {
nextApprover.handleRequest(request); // Pass the request
}
}
}
// Client builds the chain
Approver groupLeader = new GroupLeader();
Approver manager = new Manager();
groupLeader.setNext(manager);
groupLeader.handleRequest(new LeaveRequest("Zhang San", 3)); // Forward to manager
Iterator Pattern#
Iterator Pattern is a behavioral design pattern that provides a unified way to access elements in a collection object without exposing the internal representation of the collection. Simply put, it encapsulates the responsibility of traversing a collection into a separate object, allowing us to access elements in the collection in a specific manner. (I don't understand, Smitda)
Difference between array iteration and iterator: The iterator pattern provides greater flexibility (different traversal strategies: pagination, parallel) and extensibility by abstracting traversal logic and decoupling data structures, especially suitable for scenarios that require hiding internal implementations and supporting diverse traversal strategies. It ensures type safety through generics (e.g., Iterator<T>
) while providing a unified interface for different collections (List
, Set
). For example, Java's Iterable
interface allows for-each
loops to be compatible with all collections. Direct iteration over arrays is more performant in simple, fixed data structure scenarios.
CS61B 11. Inheritance IV: Iterators, Object Methods
Application
Traversing a music playlist: When we play music on our phone or computer, we typically create a playlist. The playlist can be viewed as a collection, with each song as an element in that collection. Using the iterator pattern, we can access songs in the playlist one by one through an iterator object, performing operations like play, pause, or skip.
Components of the Iterator Pattern#
- Abstract Iterator: Defines the methods required to traverse the aggregate object, including
hashNext()
andnext()
, for traversing elements in the aggregate. - Concrete Iterator: The specific implementation class of the iterator interface, responsible for the actual traversal logic. It maintains the current position information and can traverse the collection elements forward or backward as needed.
- Abstract Aggregate: Generally an interface that provides an
iterator()
method, such as Java's Collection interface, List interface, Set interface, etc. - Concrete Aggregate: The specific implementation class of the abstract container, such as the ordered list implementation ArrayList for the List interface, the linked list implementation LinkList for the List interface, and the hash list implementation HashSet for the Set interface.
Implementation#
Design Patterns Lecture 16 - Iterator Pattern (Iterator) - CSDN Blog
State Pattern#
Allows an object to change its behavior when its internal state changes. Different states are isolated; each state is a separate class.
Core Roles#
- Context: Maintains a reference to the current state.
- Abstract State: Declares the interface for state behaviors.
- Concrete State: Implements behaviors specific to a particular state and may trigger state transitions.
Implementation#
// Abstract State Interface: Order State Behavior Contract (refer to webpage 2[2](@ref) and webpage 8[8](@ref))
public interface OrderState {
void pay(OrderContext context); // Payment operation
void cancel(OrderContext context); // Cancel order
void ship(OrderContext context); // Shipping operation
void refund(OrderContext context); // Request refund
void confirmReceipt(OrderContext context); // Confirm receipt
}
// Pending Payment
public class PendingPaymentState implements OrderState {
@Override
public void pay(OrderContext context) {
System.out.println("✅ Payment successful, order enters paid state");
context.setState(new PaidState());
}
@Override
public void cancel(OrderContext context) {
System.out.println("❌ Order has been canceled");
context.setState(new CanceledState());
}
// Other operations are prohibited (refer to webpage 8's state constraints[8](@ref))
@Override
public void ship(OrderContext context) {
throw new IllegalStateException("Pending payment orders cannot be shipped");
}
// ... Similar implementation for prohibiting other methods
}
// Paid
public class PaidState implements OrderState {
@Override
public void ship(OrderContext context) {
System.out.println("🚚 Goods have been shipped");
context.setState(new ShippedState());
}
@Override
public void refund(OrderContext context) {
System.out.println("🔄 Refund request is being processed");
context.setState(new RefundingState());
}
// Prohibit duplicate payments (refer to webpage 3's state transitions[3](@ref))
@Override
public void pay(OrderContext context) {
throw new IllegalStateException("Paid orders cannot be paid again");
}
}
// Shipped
public class ShippedState implements OrderState {
@Override
public void confirmReceipt(OrderContext context) {
System.out.println("🎁 Confirm receipt, transaction completed");
context.setState(new CompletedState());
}
@Override
public void refund(OrderContext context) {
System.out.println("📦 Initiate return process");
context.setState(new RefundingState());
}
}
// Context Class (Order Body)
public class OrderContext {
private OrderState currentState;
private String orderId;
public OrderContext() {
this.currentState = new PendingPaymentState(); // Initial state Pending Payment
this.orderId = UUID.randomUUID().toString();
}
// State transition entry (refer to webpage 2's TransitionTo design[2](@ref))
public void setState(OrderState state) {
System.out.printf("【State Change】%s → %s%n",
currentState.getClass().getSimpleName(),
state.getClass().getSimpleName());
this.currentState = state;
}
// Delegate operations to the current state (refer to webpage 3's context design[3](@ref))
public void pay() { currentState.pay(this); }
public void cancel() { currentState.cancel(this); }
// ... Other operation delegation methods
}
// Client calling example
public class Client {
public static void main(String[] args) {
OrderContext order = new OrderContext();
order.pay(); // ✅ Normal payment
order.ship(); // ✅ Shipping operation
order.pay(); // ❌ Throws IllegalStateException
try {
order.cancel(); // ❌ Shipped orders cannot be canceled
} catch (Exception e) {
System.out.println("Operation failed: " + e.getMessage());
}
}
}
Template Method Pattern#
An abstract class defines a template for executing its methods. Its subclasses can override method implementations as needed, but the calls will be made in the manner defined in the abstract class. In simple terms, the template method pattern defines the skeleton of an algorithm in an operation while deferring some steps to subclasses, allowing subclasses to redefine certain specific steps of an algorithm without changing its structure. This type of design pattern belongs to behavioral patterns. The core idea is "encapsulate the invariant, extend the variable".
Structure
-
Abstract Class
- Template Method: Defines the skeleton of the algorithm (usually marked as
final
to prevent subclass overriding), including basic methods and abstract methods. - Basic Methods: Implementation of common steps (e.g.,
boilWater()
to boil water), which can be directly inherited or provided with default implementations. - Abstract Methods: Steps that must be implemented by subclasses (e.g.,
brew()
to brew coffee). - Hook Methods: Optional steps (e.g.,
customerWantsCondiments()
to determine whether to add condiments), which subclasses can choose to override.
- Template Method: Defines the skeleton of the algorithm (usually marked as
-
Concrete Subclasses
- Inherit from the abstract class, implement abstract methods, or override hook methods to provide specific logic (e.g.,
Americano
implements coffee brewing details).
- Inherit from the abstract class, implement abstract methods, or override hook methods to provide specific logic (e.g.,
Implementation
// Abstract Class
public abstract class CoffeeMaker {
// Template Method (defines the skeleton of the algorithm)
public final void makeCoffee() {
boilWater();
brew();
pourInCup();
if (needCondiments()) {
addCondiments();
}
}
// Basic Method (common steps)
private void boilWater() { System.out.println("Boiling water"); }
// Abstract Method (must be implemented by subclasses)
protected abstract void brew();
// Hook Method (optional step)
protected boolean needCondiments() { return true; }
protected void addCondiments() {} // Default empty implementation
}
// Concrete Subclass
public class Americano extends CoffeeMaker {
@Override
protected void brew() { System.out.println("Brewing Americano"); }
@Override
protected boolean needCondiments() { return false; } // No condiments added
}
Strategy Pattern#
Strategy Pattern Explained - Zhihu
Strategy Pattern defines a set of algorithms of the same type, encapsulated in different classes, allowing each algorithm to be interchanged based on the current scenario, thus making the changes in algorithms independent of the clients that use them (i.e., the callers of the algorithms).
- Algorithm encapsulation allows for mutual replacement.
- Makes algorithms independent of the users of the algorithms.
For example, when a user pays, different payment methods need to be determined to redirect to the corresponding payment page. The drawbacks of this approach are evident:
- Poor extensibility.
- Any logic modification will affect the current method.
Logic with more than three layers of if-else can be implemented using guard clauses, strategy patterns, state patterns, etc.
The strategy pattern mainly improves code extensibility by creating an interface (defining the strategy interface) and providing different implementations (implementing the strategy interface).
The interface class is responsible only for defining business strategies, while each strategy's specific implementation is placed in separate implementation classes. The factory class only retrieves specific implementation classes, while the actual calling code is responsible for orchestrating business logic. These implementations utilize interface-oriented programming rather than implementation-oriented programming, satisfying the single responsibility and open-closed principles, thereby achieving high cohesion and low coupling in functionality, improving maintainability, extensibility, and code readability.
Factory Pattern Creation
public class PaymentFactory {
private static final Map<PayTypeEnum, Payment> payStrategies = new HashMap<>();
static {
payStrategies.put(PayTypeEnum.WX, new WxPayment());
payStrategies.put(PayTypeEnum.ALIPAY, new AlipayPayment());
payStrategies.put(PayTypeEnum.BANK_CARD, new BankCardPayment());
}
public static Payment getPayment(PayTypeEnum payType) {
if (payType == null) {
throw new IllegalArgumentException("pay type is empty.");
}
if (!payStrategies.containsKey(payType)) {
throw new IllegalArgumentException("pay type not supported.");
}
return payStrategies.get(payType);
}
}
Usage
Order order = order information
PayResult payResult = PaymentFactory.getPayment(payType).pay(order);
if (payResult == PayResult.SUCCESS) {
System.out.println("Payment successful");
} else if (payType == Alipay) {
System.out.println("Payment failed");
}
Observer Pattern#
The observer pattern is a software design pattern that belongs to behavioral patterns. This pattern is used to establish a dependency relationship between objects, where when the state of one object changes, all objects that depend on it are notified and automatically updated. Here, the object that changes is called the subject, while the notified objects are called observers. One subject can correspond to multiple observers, and these observers are not interconnected, allowing observers to be added or removed as needed, making the system easier to extend; this is the motivation behind the observer pattern.
In the observer pattern, there are two main roles:
- Subject: The target (theme)
- ConcreteSubject: The specific subject, providing methods to add, remove, and notify observers. When the subject's state changes, it traverses the observer list and calls each observer's method to notify them.
- Observer: The observer
- ConcreteObserver: The specific observer, defining an update method that, when the subject's state changes, all observers dependent on that subject receive notifications and perform corresponding actions.
Implementation
Observer Pattern (Observer) Java Implementation_java implements observer - CSDN Blog
Subject Abstract Theme Role Class
public abstract class Subject {
/**
* Used to store registered observer objects
*/
private List<Observer> list = new ArrayList<>();
/**
* Register observer object
* @param observer Observer object
*/
public void attach(Observer observer) {
list.add(observer);
System.out.println("Attached an observer");
}
/**
* Remove observer object
* @param observer Observer object
*/
public void detach(Observer observer) {
list.remove(observer);
}
/**
* Notify all registered observer objects
*/
public void notifyObservers(String newState) {
for (Observer observer : list) {
observer.update(newState);
}
}
}
ConcreteSubject Specific Theme
public class ConcreteSubject extends Subject {
private String state;
public String getState() {
return state;
}
public void change(String newState) {
state = newState;
System.out.println("Subject state is: " + state);
// State has changed, notify all observers
this.notifyObservers(state);
}
}
Observer Abstract Observer Role Class
public interface Observer {
/**
* Update interface
* @param state Updated state
*/
void update(String state);
}
ConcreteObserver Specific Observer Role Class
public class ConcreteObserver implements Observer {
// Observer state
private String observerState;
@Override
public void update(String state) {
/**
* Update the observer's state to keep it consistent with the target's state
*/
observerState = state;
System.out.println("State is: " + observerState);
}
}
Client Class
public class Client {
public static void main(String[] args) {
// Create subject object
ConcreteSubject subject = new ConcreteSubject();
// Create observer object
Observer observer = new ConcreteObserver();
// Register the observer object to the subject object
subject.attach(observer);
// Change the state of the subject object
subject.change("new state");
}
}
Advantages
- Achieves separation between the presentation layer and data logic layer, defining a stable message update transmission mechanism, abstracting the update interface, allowing for various different presentation layers as specific observer roles.
- Establishes an abstract coupling between the subject and observers.
- Supports broadcast communication.
- Meets the requirements of the "open-closed principle".
Disadvantages
- Notifying all observers can take a lot of time when there are many observers.
- If there is a circular dependency between observers and the subject, the subject may trigger circular calls between them, potentially causing system crashes.
- There is no corresponding mechanism for observers to know how the observed target object has changed, only that the observed target has changed.
Application
The observer pattern can be used in any scenario involving one-to-one or one-to-many object interactions.
Extension#
MVC (Model-View-Controller)
The MVC pattern is an architectural pattern that includes three roles: Model, View, and Controller. The observer pattern can be used to implement the MVC pattern, where the observed target in the observer pattern corresponds to the Model in the MVC pattern, and the observers correspond to the View in the MVC pattern, with the Controller acting as a mediator between the two. When the data in the model layer changes, the view layer will automatically change its displayed content.
-
Model: Represents the application's data layer or business logic layer. It is responsible for managing the application's state and directly interacting with databases or other persistent storage.
-
View: The user interface, which displays data to the user and receives user input. It typically only communicates with the Controller.
-
Controller: Acts as a mediator between the Model and View. It receives user input (e.g., through HTTP requests), processes that input (which may involve calling methods on the Model), and then decides which View to display.
Mobile development in iOS Swift (UIKit) and Android (Activity/Fragment)
Introduction to MVC, MVP, MVVM
MVC, MVP, and MVVM Illustrated - Ruanyifeng's Blog
MVC (Model-View-Controller)
-
Model: Represents the application's data layer or business logic layer. It is responsible for managing the application's state and directly interacting with databases or other persistent storage.
-
View: The user interface, which displays data to the user and receives user input. It typically only communicates with the Controller.
-
Controller: Acts as a mediator between the Model and View. It receives user input (e.g., through HTTP requests), processes that input (which may involve calling methods on the Model), and then decides which View to display.
Mobile development in iOS Swift (UIKit) and Android (Activity/Fragment)
MVVM (Model-View-ViewModel)
-
Model: Like the previous two patterns, responsible for data and business logic.
-
View: The user interface, which interacts with the ViewModel through a binding mechanism. It is unaware of the ViewModel's existence and only cares about how to present data.
-
ViewModel: A bridge connecting the Model and View. It exposes public properties and commands to the View and interacts with the View through data binding and command binding. The ViewModel is unaware of the specific View, making unit testing easier.
JavaScript frameworks like Vue.js, Angular, React (although React leans more towards component-based design, it can also implement a similar MVVM pattern with state management libraries like Redux)
The main differences between the two patterns are:
- Data Binding: A significant feature of MVVM is two-way data binding, which allows changes in data to automatically sync to the UI, while also allowing user input data to directly reflect back to the data model. In the MVC pattern, this synchronization usually needs to be manually implemented through the controller.
- Separation of Responsibilities: Although both MVC and MVVM emphasize the principle of separation of responsibilities, MVVM further reduces the direct dependency between the view and model by introducing the ViewModel, improving code testability and maintainability.
- Relationship between View and Controller: In MVC, there is direct interaction between the view and controller, while in MVVM, the view primarily interacts with the ViewModel, reducing the view's direct dependency on business logic and making UI design more flexible.