Chain of Responsibility Design Pattern
The Chain of Responsibility is a behavioral design pattern that passes requests sequentially through a chain of handlers. The order of these handlers is predetermined. Upon receiving a request, each handler decides whether it is capable of processing it. If so, it processes the request; otherwise, it forwards the request to the next handler in the chain. Some handlers may choose not to perform any action on the request and simply pass it along.
The key advantages of this approach include:
Clear separation of concerns: Each handler is responsible solely for its own specific task – much like an assembly line in a factory, with the difference that here each station may skip the task and pass the item to the next station.
Reduced coupling: The sender of the request does not need to know which handler will ultimately process it; it only needs to know the chain. This significantly reduces dependencies between system components.
Flexibility and extensibility: New handlers can be easily added to the chain, or their order can be changed, without affecting other parts of the system.
A common real‑world example is authentication in web applications: a user request passes through a password validation layer, then an access‑level verification layer, and finally a logging layer – each layer responding as needed or forwarding the request onward.
By leveraging this capability, complex systems can be managed in a well‑organized, modular, and extensible manner.
Below is an example of how this pattern can be used in a frontend application for a login page.
export interface FormContext {
email: string;
password: string;
errors: string[];
}
export type Middleware = (context: FormContext, next: () => void) => void;
export const createMiddlewareChain = (middlewares: Middleware[]) => {
return (initialContext: FormContext): FormContext => {
let index = 0;
const next = (): void => {
if (index < middlewares.length) {
const middleware = middlewares[index];
index++;
middleware(initialContext, next);
}
};
next();
return initialContext;
};
};
export const emailMiddleware: Middleware = (context, next) => {
if (!context.email || context.email.trim().length === 0) {
context.errors.push("Email is required");
return;
}
next();
};
export const formatMiddleware: Middleware = (context, next) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (context.email && !emailRegex.test(context.email)) {
context.errors.push("Invalid email format");
return;
}
next();
};
export const passwordMiddleware: Middleware = (context, next) => {
if (!context.password || context.password.trim().length === 0) {
context.errors.push("Password is required");
return;
}
next();
};
export const passwordLengthMiddleware: Middleware = (context, next) => {
if (context.password && context.password.length < 6) {
context.errors.push("Password must be at least 6 characters");
return;
}
next();
};
interface SignupFormProps {
onSubmitSuccess?: (data: { email: string; password: string }) => void;
}
const SignupForm: React.FC<SignupFormProps> = ({ onSubmitSuccess }) => {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [errors, setErrors] = useState<string[]>([]);
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
setErrors([]);
setIsSubmitting(true);
const context: FormContext = {
email: email.trim(),
password: password.trim(),
errors: [],
};
const chain = createMiddlewareChain([
emailMiddleware,
formatMiddleware,
passwordMiddleware,
passwordLengthMiddleware,
]);
const result = chain(context);
if (result.errors.length > 0) {
setErrors(result.errors);
setIsSubmitting(false);
return;
}
setErrors(["✅ Registration successful!"]);
setIsSubmitting(false);
if (onSubmitSuccess) {
onSubmitSuccess({ email: result.email, password: result.password });
}
setTimeout(() => {
setEmail("");
setPassword("");
setErrors([]);
}, 2000);
};
return (
<form onSubmit={handleSubmit} aria-label="Signup form">
<h3>📝 Signup Form</h3>
<div>
<label htmlFor="email">Email</label>
<input
id="email"
value={email}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setEmail(e.target.value)
}
autocomplete="off"
placeholder="example@email.com"
disabled={isSubmitting}
aria-describedby={errors.length > 0 ? "error-message" : undefined}
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setPassword(e.target.value)
}
placeholder="Minimum 6 characters"
disabled={isSubmitting}
aria-describedby={errors.length > 0 ? "error-message" : undefined}
/>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? "Processing..." : "Sign Up"}
</button>
{errors.length > 0 && (
<div
id="error-message"
role={errors[0].includes("✅") ? "status" : "alert"}
>
{errors.map((err, index) => (
<div key={index}>{err}</div>
))}
</div>
)}
</form>
);
};
export default SignupForm;
Top comments (0)