Patrón Chain of Responsibility
Pasa una petición por una cadena de objetos hasta que uno la atiende: aprobaciones por niveles, soporte técnico, filtros web o middlewares. Cada eslabón la resuelve o la pasa al siguiente.
nivel intermedioTambién: cadena de responsabilidad, cadena de manejadores, middleware, filtros
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Chain of Responsibility
Escribe los gastos separados por comas, con su concepto y su importe. Cada uno entra por el primer eslabón de la cadena.
Paso 1
Gasto de 120,00 € (material): el cliente solo conoce el primer eslabón y se lo da a él.
1void aprobar(Gasto g) { // gasto = material 120,00 €
2 if (puedeAprobar(g))
3 System.out.println("Lo aprueba " + nombre());
4 else if (siguiente != null)
5 siguiente.aprobar(g);
6 else
7 System.out.println("Nadie lo aprueba");
8}Variables
- gasto
- material 120,00 €
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Un gasto de empresa lo aprueba la jefa de equipo si es pequeño, el director si es mediano y el consejo si es grande. Si el código que pide la aprobación tiene que saber a quién mandar cada gasto, se llena de if con las reglas de todos los niveles, y cambiar un límite o añadir un nivel obliga a tocarlo.
El patrón Chain of Responsibility encadena los posibles responsables. Cada eslabón tiene una referencia al siguiente y, al recibir una petición, decide: la atiende o se la pasa. El que pide solo conoce el primer eslabón y no sabe quién acabará atendiéndola. Cambiar el orden, añadir o quitar niveles es cambiar cómo se enlaza la cadena.
Hay dos variantes. En la clásica, la petición avanza hasta que alguien la atiende (aprobaciones, soporte). En la de filtros o middlewares, cada eslabón hace su parte y decide si deja seguir a la siguiente (comprobar la sesión, registrar, comprimir) o corta la cadena. Los filtros de los servlets, los middlewares de Express, Laravel y ASP.NET y la propagación de excepciones funcionan así.
Cuándo usarlo
- Varios objetos pueden atender una petición y no se sabe de antemano cuál lo hará.
- El que pide no debe conocer a todos los posibles responsables ni sus reglas.
- Los responsables o su orden cambian con la configuración (niveles de soporte, filtros activados).
- Una petición tiene que pasar por varios pasos que pueden cortarla: autenticación, permisos, validación, registro.
Cuándo no
- Si siempre la atiende el mismo objeto: una llamada directa es más clara.
- Si es importante garantizar que alguien la atiende: con una cadena mal configurada, la petición puede llegar al final sin respuesta.
Participantes
- Manejador. La clase base (
Aprobador) con la referencia al siguiente y el método que atiende o pasa la petición. - Manejadores concretos. Cada eslabón (
JefeEquipo,Director,Consejo) decide si puede atender. - Cliente. Monta la cadena (o la recibe ya montada) y envía cada petición al primer eslabón.
Diagrama de clases
Ver el diagrama en PlantUML
1@startuml
2abstract class Aprobador {
3 -siguiente : Aprobador
4 +luego(siguiente : Aprobador) : Aprobador
5 +aprobar(g : Gasto) : void
6 #{abstract} nombre() : String
7 #{abstract} puedeAprobar(g : Gasto) : boolean
8}
9class JefeEquipo
10class Director
11class Consejo
12class Gasto {
13 +concepto : String
14 +importe : double
15}
16Aprobador <|-- JefeEquipo
17Aprobador <|-- Director
18Aprobador <|-- Consejo
19Aprobador --> "0..1" Aprobador : siguiente
20Aprobador ..> Gasto : aprueba
21@endumlPuedes copiarlo en el editor de diagramas UML y modificarlo.
El código
Aprobar gastos por niveles
El main solo llama al primer eslabón. Cada nivel aprueba si puede o pasa el gasto, y el último avisa si nadie puede.
1import java.util.*;
2
3record Gasto(String concepto, double importe) {}
4
5/** Un eslabón de la cadena: aprueba lo que puede y pasa el resto al siguiente. */
6abstract class Aprobador {
7 private Aprobador siguiente;
8
9 /** Enlaza el siguiente y lo devuelve, para escribir la cadena de un tirón. */
10 Aprobador luego(Aprobador siguiente) {
11 this.siguiente = siguiente;
12 return siguiente;
13 }
14
15 void aprobar(Gasto g) {
16 if (puedeAprobar(g)) {
17 System.out.println(" Lo aprueba " + nombre());
18 } else if (siguiente != null) {
19 System.out.println(" " + nombre() + " no puede: lo pasa");
20 siguiente.aprobar(g);
21 } else {
22 System.out.println(" " + nombre() + " tampoco puede: nadie lo aprueba");
23 }
24 }
25
26 protected abstract String nombre();
27 protected abstract boolean puedeAprobar(Gasto g);
28}
29
30class JefeEquipo extends Aprobador {
31 protected String nombre() { return "la jefa de equipo"; }
32 protected boolean puedeAprobar(Gasto g) { return g.importe() <= 500 && !g.concepto().equals("viaje"); }
33}
34
35class Director extends Aprobador {
36 protected String nombre() { return "el director"; }
37 protected boolean puedeAprobar(Gasto g) { return g.importe() <= 5000; }
38}
39
40class Consejo extends Aprobador {
41 protected String nombre() { return "el consejo"; }
42 protected boolean puedeAprobar(Gasto g) { return g.importe() <= 50000; }
43}
44
45public class Main {
46 public static void main(String[] args) {
47 Aprobador cadena = new JefeEquipo();
48 cadena.luego(new Director()).luego(new Consejo());
49 List<Gasto> gastos = List.of(new Gasto("material", 120), new Gasto("viaje", 300), new Gasto("servidor", 12000), new Gasto("edificio", 900000));
50 for (Gasto g : gastos) {
51 System.out.println("Gasto de " + String.format(Locale.ROOT, "%.2f", g.importe()).replace('.', ',') + " € (" + g.concepto() + "):");
52 cadena.aprobar(g); // el cliente solo conoce el primer eslabón
53 }
54 }
55}from abc import ABC, abstractmethod
class Aprobador(ABC):
"""Un eslabón de la cadena: aprueba lo que puede y pasa el resto al siguiente."""
def __init__(self):
self._siguiente = None
def luego(self, siguiente):
"""Enlaza el siguiente y lo devuelve, para escribir la cadena de un tirón."""
self._siguiente = siguiente
return siguiente
def aprobar(self, concepto, importe):
if self.puede_aprobar(concepto, importe):
print(f" Lo aprueba {self.nombre()}")
elif self._siguiente is not None:
print(f" {self.nombre()} no puede: lo pasa")
self._siguiente.aprobar(concepto, importe)
else:
print(f" {self.nombre()} tampoco puede: nadie lo aprueba")
@abstractmethod
def nombre(self): ...
@abstractmethod
def puede_aprobar(self, concepto, importe): ...
class JefeEquipo(Aprobador):
def nombre(self):
return "la jefa de equipo"
def puede_aprobar(self, concepto, importe):
return importe <= 500 and concepto != "viaje"
class Director(Aprobador):
def nombre(self):
return "el director"
def puede_aprobar(self, concepto, importe):
return importe <= 5000
class Consejo(Aprobador):
def nombre(self):
return "el consejo"
def puede_aprobar(self, concepto, importe):
return importe <= 50000
cadena = JefeEquipo()
cadena.luego(Director()).luego(Consejo())
for concepto, importe in [("material", 120), ("viaje", 300), ("servidor", 12000), ("edificio", 900000)]:
print(f"Gasto de {importe:.2f} € ({concepto}):".replace(".", ","))
cadena.aprobar(concepto, importe) # el cliente solo conoce el primer eslabón/** Un eslabón de la cadena: aprueba lo que puede y pasa el resto al siguiente. */
class Aprobador {
#siguiente = null;
/** Enlaza el siguiente y lo devuelve, para escribir la cadena de un tirón. */
luego(siguiente) { this.#siguiente = siguiente; return siguiente; }
aprobar(gasto) {
if (this.puedeAprobar(gasto)) {
console.log(` Lo aprueba ${this.nombre()}`);
} else if (this.#siguiente) {
console.log(` ${this.nombre()} no puede: lo pasa`);
this.#siguiente.aprobar(gasto);
} else {
console.log(` ${this.nombre()} tampoco puede: nadie lo aprueba`);
}
}
}
class JefeEquipo extends Aprobador {
nombre() { return "la jefa de equipo"; }
puedeAprobar(g) { return g.importe <= 500 && g.concepto !== "viaje"; }
}
class Director extends Aprobador {
nombre() { return "el director"; }
puedeAprobar(g) { return g.importe <= 5000; }
}
class Consejo extends Aprobador {
nombre() { return "el consejo"; }
puedeAprobar(g) { return g.importe <= 50000; }
}
const cadena = new JefeEquipo();
cadena.luego(new Director()).luego(new Consejo());
const gastos = [{ concepto: "material", importe: 120 }, { concepto: "viaje", importe: 300 }, { concepto: "servidor", importe: 12000 }, { concepto: "edificio", importe: 900000 }];
for (const g of gastos) {
console.log(`Gasto de ${g.importe.toFixed(2).replace(".", ",")} € (${g.concepto}):`);
cadena.aprobar(g); // el cliente solo conoce el primer eslabón
}using System;
using System.Globalization;
record Gasto(string Concepto, decimal Importe);
// Un eslabón de la cadena: aprueba lo que puede y pasa el resto al siguiente.
abstract class Aprobador {
private Aprobador? siguiente;
// Enlaza el siguiente y lo devuelve, para escribir la cadena de un tirón.
public Aprobador Luego(Aprobador siguiente) { this.siguiente = siguiente; return siguiente; }
public void Aprobar(Gasto g) {
if (PuedeAprobar(g)) {
Console.WriteLine(quot; Lo aprueba {Nombre}");
} else if (siguiente != null) {
Console.WriteLine(quot; {Nombre} no puede: lo pasa");
siguiente.Aprobar(g);
} else {
Console.WriteLine(quot; {Nombre} tampoco puede: nadie lo aprueba");
}
}
protected abstract string Nombre { get; }
protected abstract bool PuedeAprobar(Gasto g);
}
class JefeEquipo : Aprobador {
protected override string Nombre => "la jefa de equipo";
protected override bool PuedeAprobar(Gasto g) => g.Importe <= 500 && g.Concepto != "viaje";
}
class Director : Aprobador {
protected override string Nombre => "el director";
protected override bool PuedeAprobar(Gasto g) => g.Importe <= 5000;
}
class Consejo : Aprobador {
protected override string Nombre => "el consejo";
protected override bool PuedeAprobar(Gasto g) => g.Importe <= 50000;
}
class Program {
static void Main() {
Aprobador cadena = new JefeEquipo();
cadena.Luego(new Director()).Luego(new Consejo());
var gastos = new[] { new Gasto("material", 120), new Gasto("viaje", 300), new Gasto("servidor", 12000), new Gasto("edificio", 900000) };
foreach (var g in gastos) {
Console.WriteLine(quot;Gasto de {g.Importe.ToString("F2", CultureInfo.InvariantCulture).Replace('.', ',')} € ({g.Concepto}):");
cadena.Aprobar(g); // el cliente solo conoce el primer eslabón
}
}
}<?php
/** Un eslabón de la cadena: aprueba lo que puede y pasa el resto al siguiente. */
abstract class Aprobador {
private ?Aprobador $siguiente = null;
/** Enlaza el siguiente y lo devuelve, para escribir la cadena de un tirón. */
public function luego(Aprobador $siguiente): Aprobador {
$this->siguiente = $siguiente;
return $siguiente;
}
public function aprobar(string $concepto, float $importe): void {
if ($this->puedeAprobar($concepto, $importe)) {
echo " Lo aprueba {$this->nombre()}\n";
} elseif ($this->siguiente !== null) {
echo " {$this->nombre()} no puede: lo pasa\n";
$this->siguiente->aprobar($concepto, $importe);
} else {
echo " {$this->nombre()} tampoco puede: nadie lo aprueba\n";
}
}
abstract protected function nombre(): string;
abstract protected function puedeAprobar(string $concepto, float $importe): bool;
}
class JefeEquipo extends Aprobador {
protected function nombre(): string { return "la jefa de equipo"; }
protected function puedeAprobar(string $concepto, float $importe): bool { return $importe <= 500 && $concepto !== "viaje"; }
}
class Director extends Aprobador {
protected function nombre(): string { return "el director"; }
protected function puedeAprobar(string $concepto, float $importe): bool { return $importe <= 5000; }
}
class Consejo extends Aprobador {
protected function nombre(): string { return "el consejo"; }
protected function puedeAprobar(string $concepto, float $importe): bool { return $importe <= 50000; }
}
$cadena = new JefeEquipo();
$cadena->luego(new Director())->luego(new Consejo());
foreach ([["material", 120], ["viaje", 300], ["servidor", 12000], ["edificio", 900000]] as [$concepto, $importe]) {
echo "Gasto de ", number_format($importe, 2, ",", ""), " € ($concepto):\n";
$cadena->aprobar($concepto, $importe); // el cliente solo conoce el primer eslabón
}Salida al ejecutarlo (la misma en los 5 lenguajes)
Gasto de 120,00 € (material): Lo aprueba la jefa de equipo Gasto de 300,00 € (viaje): la jefa de equipo no puede: lo pasa Lo aprueba el director Gasto de 12000,00 € (servidor): la jefa de equipo no puede: lo pasa el director no puede: lo pasa Lo aprueba el consejo Gasto de 900000,00 € (edificio): la jefa de equipo no puede: lo pasa el director no puede: lo pasa el consejo tampoco puede: nadie lo aprueba
Filtros de servlets y excepciones
La variante de filtros: cada eslabón decide si deja seguir a la petición.
1// Los filtros de un servidor web son una cadena: cada uno hace lo suyo y decide si sigue
2@WebFilter("/*")
3public class FiltroLogin implements Filter {
4 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain cadena)
5 throws IOException, ServletException {
6 if (((HttpServletRequest) req).getSession().getAttribute("usuario") == null) {
7 ((HttpServletResponse) resp).sendRedirect("/login"); // corta la cadena aquí
8 return;
9 }
10 cadena.doFilter(req, resp); // pasa al siguiente filtro (o al servlet)
11 }
12}
13
14// Las excepciones recorren otra cadena: si un método no la captura, sube al que lo llamó,
15// y así hasta que alguien la atiende (o hasta el main).# Django: cada middleware recibe la petición, hace lo suyo y decide si llama al siguiente
def requiere_login(get_response):
def middleware(request):
if not request.user.is_authenticated:
return redirect("/login") # corta la cadena aquí
return get_response(request) # pasa al siguiente middleware (o a la vista)
return middleware// Express: cada middleware decide si llama a next() para seguir la cadena
app.use((req, res, next) => {
if (!req.session.usuario) return res.redirect("/login"); // corta la cadena aquí
next(); // pasa al siguiente
});
// Los eventos del DOM suben de un elemento a sus padres hasta que alguien los para
boton.addEventListener("click", (e) => e.stopPropagation());// ASP.NET Core: cada middleware recibe el siguiente y decide si lo llama
app.Use(async (contexto, siguiente) => {
if (contexto.User.Identity?.IsAuthenticated != true) {
contexto.Response.Redirect("/login"); // corta la cadena aquí
return;
}
await siguiente(); // pasa al siguiente middleware
});<?php
// Laravel: un middleware recibe la petición y el siguiente eslabón ($next)
class RequiereLogin {
public function handle(Request $peticion, Closure $next): Response {
if (!auth()->check()) return redirect("/login"); // corta la cadena aquí
return $next($peticion); // pasa al siguiente
}
}En la práctica
- Los filtros de los servlets (
FilterChain.doFilter) y los de Spring Security. - Los middlewares de Express (
next()), Laravel ($next($request)) y ASP.NET Core (await next()). - La propagación de excepciones: si un método no la captura, sube al que lo llamó.
- Los eventos del DOM que suben de un elemento a sus padres (burbujeo) hasta que alguien los atiende o los para.
Errores típicos
- Olvidar pasar la petición al siguiente cuando un eslabón no la atiende: la cadena se corta sin querer.
- No contemplar el final de la cadena: una petición que nadie atiende debe tener una respuesta clara.
- Ordenar mal los eslabones: si el más general va primero, los demás nunca reciben nada.
- Crear ciclos en la cadena (un eslabón que apunta a uno anterior): la petición da vueltas para siempre.
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. Filtros de comentarios
Antes de publicar un comentario, pasa por una cadena de filtros. Ya está el de longitud. Escribe los filtros Enlaces, Prohibidas y Mayusculas y enlázalos detrás, en ese orden: el primero que encuentra un problema da el motivo del rechazo.
- La primera línea es
prohibidas palabra,palabra; después, un comentario por línea. Salida:Publicado: textooRechazado (motivo): texto(un comentario vacío es soloRechazado (está vacío)), y al finalN publicados, M rechazados. - Motivos:
lleva enlaces(contienehttp://,https://owww., sin distinguir mayúsculas),contiene «palabra»(una palabra prohibida, sin distinguir mayúsculas; las palabras se separan por todo lo que no son letras) yestá escrito en mayúsculas(al menos 10 letras y más de la mitad en mayúscula).
Ejemplo
prohibidas tonto,feo Me ha encantado el tema de herencia Mira mi web: www.ejemplo.com Eres un TONTO ESTO ES UNA VERGÜENZA OK
Publicado: Me ha encantado el tema de herencia Rechazado (lleva enlaces): Mira mi web: www.ejemplo.com Rechazado (contiene «tonto»): Eres un TONTO Rechazado (está escrito en mayúsculas): ESTO ES UNA VERGÜENZA Publicado: OK 2 publicados, 3 rechazados
Ver la solución explicada
1import java.util.*;
2
3/** Un filtro de la cadena: si encuentra un problema, lo dice; si no, pasa el texto al siguiente. */
4abstract class Filtro {
5 private Filtro siguiente;
6
7 Filtro luego(Filtro siguiente) {
8 this.siguiente = siguiente;
9 return siguiente;
10 }
11
12 /** null si el comentario pasa toda la cadena desde aquí; si no, el motivo del primer rechazo. */
13 final String revisar(String texto) {
14 String motivo = problema(texto);
15 if (motivo != null) return motivo;
16 return siguiente == null ? null : siguiente.revisar(texto);
17 }
18
19 protected abstract String problema(String texto);
20}
21
22class Longitud extends Filtro {
23 protected String problema(String texto) {
24 if (texto.isBlank()) return "está vacío";
25 return texto.length() > 120 ? "es demasiado largo" : null;
26 }
27}
28
29class Enlaces extends Filtro {
30 protected String problema(String texto) {
31 String t = texto.toLowerCase();
32 return t.contains("http://") || t.contains("https://") || t.contains("www.") ? "lleva enlaces" : null;
33 }
34}
35
36class Prohibidas extends Filtro {
37 private final Set<String> palabras;
38 Prohibidas(Set<String> palabras) { this.palabras = palabras; }
39 protected String problema(String texto) {
40 for (String w : texto.toLowerCase().split("[^\\p{L}]+")) {
41 if (palabras.contains(w)) return "contiene «" + w + "»";
42 }
43 return null;
44 }
45}
46
47class Mayusculas extends Filtro {
48 protected String problema(String texto) {
49 int letras = 0, mayusculas = 0;
50 for (char c : texto.toCharArray()) {
51 if (Character.isLetter(c)) {
52 letras++;
53 if (Character.isUpperCase(c)) mayusculas++;
54 }
55 }
56 return letras >= 10 && mayusculas * 2 > letras ? "está escrito en mayúsculas" : null;
57 }
58}
59
60public class Main {
61 public static void main(String[] args) {
62 Scanner sc = new Scanner(System.in);
63 Set<String> prohibidas = new HashSet<>();
64 String primera = sc.hasNextLine() ? sc.nextLine().trim() : "";
65 if (primera.startsWith("prohibidas ")) {
66 for (String w : primera.substring(11).split(",")) if (!w.isBlank()) prohibidas.add(w.trim().toLowerCase());
67 }
68 // La cadena: el orden decide qué motivo se da cuando hay varios problemas
69 Filtro cadena = new Longitud();
70 cadena.luego(new Enlaces()).luego(new Prohibidas(prohibidas)).luego(new Mayusculas());
71 int publicados = 0, rechazados = 0;
72 while (sc.hasNextLine()) {
73 String texto = sc.nextLine();
74 String motivo = cadena.revisar(texto);
75 if (motivo == null) {
76 publicados++;
77 System.out.println("Publicado: " + texto.trim());
78 } else {
79 rechazados++;
80 System.out.println("Rechazado (" + motivo + ")" + (texto.isBlank() ? "" : ": " + texto.trim()));
81 }
82 }
83 System.out.println(publicados + " publicados, " + rechazados + " rechazados");
84 }
85}Cada filtro es independiente y no sabe qué otros hay; el orden de la cadena decide qué motivo se da primero.
Añadir un filtro nuevo es una clase y un luego más: revisar no cambia.
2. Soporte técnico por niveles
Los tickets de soporte pasan por niveles configurables. Manejador.atender atiende el ticket si el nivel puede y, si no, debería pasárselo al siguiente, pero de momento no lo hace. Complétalo, escribe los niveles Experto y Gerencia y añádelos en crear.
- La primera línea es
cadena nivel,nivel,...(el orden de la cadena, conbot,tecnico,expertoygerencia); después, ticketsticket prioridad normal|vip texto, con prioridad del 1 al 5. - Bot: atiende si el texto contiene
contraseñaowifi. Técnico: prioridad 1 o 2. Experto: prioridad hasta 4. Gerencia: los clientes vip. - Salida:
#n (prioridad p) → nivelosin atender, y al finalAtendidos: nivel n · ... · sin atender n. Un nivel desconocido en la cadena:Nivel desconocido: x.
Ejemplo
cadena bot,tecnico,experto,gerencia ticket 1 normal No me funciona la wifi ticket 2 normal El ratón no va ticket 4 normal Se ha caído el servidor ticket 5 vip Necesito un informe urgente ticket 5 normal Quiero hablar con el director
#1 (prioridad 1) → bot #2 (prioridad 2) → técnico #3 (prioridad 4) → experto #4 (prioridad 5) → gerencia #5 (prioridad 5) → sin atender Atendidos: bot 1 · técnico 1 · experto 1 · gerencia 1 · sin atender 1
Ver la solución explicada
1import java.util.*;
2
3record Ticket(int id, int prioridad, boolean vip, String texto) {}
4
5/** Un nivel de soporte: atiende el ticket si puede y, si no, lo pasa al siguiente nivel. */
6abstract class Manejador {
7 private Manejador siguiente;
8 int atendidos = 0;
9
10 void setSiguiente(Manejador siguiente) { this.siguiente = siguiente; }
11
12 /** El nombre de quien lo atiende, o null si nadie de aquí en adelante puede. */
13 final String atender(Ticket t) {
14 if (puede(t)) {
15 atendidos++;
16 return nombre();
17 }
18 return siguiente == null ? null : siguiente.atender(t);
19 }
20
21 abstract String nombre();
22 protected abstract boolean puede(Ticket t);
23}
24
25class Bot extends Manejador {
26 String nombre() { return "bot"; }
27 protected boolean puede(Ticket t) {
28 String s = t.texto().toLowerCase();
29 return s.contains("contraseña") || s.contains("wifi");
30 }
31}
32
33class Tecnico extends Manejador {
34 String nombre() { return "técnico"; }
35 protected boolean puede(Ticket t) { return t.prioridad() <= 2; }
36}
37
38class Experto extends Manejador {
39 String nombre() { return "experto"; }
40 protected boolean puede(Ticket t) { return t.prioridad() <= 4; }
41}
42
43class Gerencia extends Manejador {
44 String nombre() { return "gerencia"; }
45 protected boolean puede(Ticket t) { return t.vip(); }
46}
47
48public class Main {
49 static Manejador crear(String nombre) {
50 return switch (nombre) {
51 case "bot" -> new Bot();
52 case "tecnico" -> new Tecnico();
53 case "experto" -> new Experto();
54 case "gerencia" -> new Gerencia();
55 default -> throw new IllegalArgumentException("Nivel desconocido: " + nombre);
56 };
57 }
58
59 public static void main(String[] args) {
60 Scanner sc = new Scanner(System.in);
61 String primera = sc.hasNextLine() ? sc.nextLine().trim() : "";
62 if (!primera.startsWith("cadena ")) {
63 System.out.println("Falta la cadena");
64 return;
65 }
66 List<Manejador> niveles = new ArrayList<>();
67 try {
68 for (String n : primera.substring(7).split(",")) niveles.add(crear(n.trim()));
69 } catch (IllegalArgumentException e) {
70 System.out.println(e.getMessage());
71 return;
72 }
73 for (int i = 0; i + 1 < niveles.size(); i++) niveles.get(i).setSiguiente(niveles.get(i + 1));
74 int id = 0, sinAtender = 0;
75 while (sc.hasNextLine()) {
76 String[] p = sc.nextLine().trim().split("\\s+", 4);
77 if (p[0].isEmpty()) continue;
78 try {
79 if (!p[0].equals("ticket") || p.length < 4 || !p[2].matches("normal|vip")) throw new NumberFormatException();
80 int prioridad = Integer.parseInt(p[1]);
81 if (prioridad < 1 || prioridad > 5) throw new NumberFormatException();
82 Ticket t = new Ticket(++id, prioridad, p[2].equals("vip"), p[3]);
83 String quien = niveles.get(0).atender(t);
84 if (quien == null) sinAtender++;
85 System.out.println("#" + t.id() + " (prioridad " + prioridad + ") → " + (quien == null ? "sin atender" : quien));
86 } catch (NumberFormatException e) {
87 System.out.println("Ticket no válido: " + String.join(" ", p));
88 }
89 }
90 List<String> resumen = new ArrayList<>();
91 for (Manejador m : niveles) resumen.add(m.nombre() + " " + m.atendidos);
92 resumen.add("sin atender " + sinAtender);
93 System.out.println("Atendidos: " + String.join(" · ", resumen));
94 }
95}La cadena se monta a partir de la configuración: el código que reparte los tickets no cambia si se añade o se reordena un nivel.
Si el más general (experto) va antes que el técnico, el técnico no recibe nunca nada: el orden importa.
Test
Test: Chain of Responsibility
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.En Chain of Responsibility, ¿a quién envía el cliente la petición?
2.¿Qué hace un eslabón que no puede atender la petición?
3.¿Qué pasa si el manejador más general va el primero?
4.¿Cuál de estos sigue el patrón?
5.¿Qué hay que prever al final de la cadena?