Chain of Responsibility - Definicion:
El patrón Chain of Responsibility [Cadena de responsabilidad] es un patrón de diseño de programación orientada a objetos (OOP) común que desacopla al remitente de una solicitud del receptor al permitir que uno o varios objetos de controlador controlen la solicitud. Un controlador puede pasar la solicitud al siguiente hasta que un objeto de la cadena pueda controlar la solicitud.
Diagrama de clases UML
Participantes
Las clases y objetos que participannen en este patron son:
- El patrón de cadena de responsabilidad consta de tres componentes:
- [Handler]la interfaz de controlador,
- La interfaz de controlador define el contrato para controlar una solicitud en la cadena.
- Al menos un [ConcreteHandler] controlador concreto (un eslabon de la cadena)
- La clase de controlador concreto implementa la interfaz de controlador y controla la solicitud o la reenvía a su controlador sucesor.
- Maneja las solicitudes de las que es responsable
- Puede acceder a su sucesor
- Si el ConcreteHandler puede controlar la solicitud, lo hace; de lo contrario remite la solicitud a su sucesor
- y la aplicación cliente[Cliente].
- La aplicación cliente envía la solicitud al controlador concreto inicial.
Ejemplo
Estudia el codigo siguiente
Public Class PruebaConcepto ''' <summary> ''' Este es el valor que se genera por la cadena que lo atrapa ''' </summary> Public ValorDeRetorno As New System.Windows.Controls.ValidationResult(True, Nothing) Public Function Text(ByVal ClaveTareaARealizar As String) Select Case ClaveTareaARealizar Case Is = "uno" ValorDeRetorno = New System.Windows.Controls.ValidationResult(True, "Uno") Case Is = "dos" ValorDeRetorno = New System.Windows.Controls.ValidationResult(True, "dos") Case Is = "tres" ValorDeRetorno = New System.Windows.Controls.ValidationResult(True, "tres") Case Else ValorDeRetorno = New System.Windows.Controls.ValidationResult(False, "Tarea no realizada") End Select Return ValorDeRetorno End Function End Class
Este tipo de codigo es un codigo bastante limpio, pero si la tarea a realizar en cada opcion es extensa, o bien hay muchas opciones posibles se enmaraña y deja de ser claro y sencillo
Una forma de resolver este problema es usar el patron chain of responsability. La idea es colocar todas las tareas que se realizen dentro de nuestro hipotetico y muy abultado, [select case] en un objeto
A continuacion defino una estrututa de datos del tipo [Lista simple enlazada], (Wikipedia Lista enlazada) en la que cada objeto mantiene un puntero (una referenacia) a su sucesor (a su hijo
Cada eslabon de la lista enlazada es, como habras adivinado, nuestro[ConcreteHandler]
Cada eslabon tiene el codigo necesario para resolver un problema, y si no puede hacerlo, sabe quien es su hijo y le pasa el problema para que lo resuelva el, y asi hasta el final. Cuando una clase eslabon, resuleve el problema, devuelve la solucion a su padre, y asi sucesivamente hasta que el resultado llega al primer elemento de la cadena, que lo devuelve al cliente.
Un perfecccionamiento que podemos añadir a nuestro codigo es usar los llamados Nodos centinelas. (segun dice la Wikipedia) A veces las listas enlazadas tienen un nodo centinela (también llamado falso nodo o nodo ficticio) al principio o al final de la lista. Su propósito no es guardar datos, sino simplificar o agilizar algunas operaciones, asegurando que cualquier nodo tiene otro anterior o posterior, y que toda la lista (incluso alguna que no contenga datos) siempre tenga un "primer y un último" nodo.
Hay que resolver otros problemas:
¿Como sabe cada eslabon que es él, el que tiene que resolver el problema? , hay varias soluciones, pero la mas sencilla es imitar el comportamiento del [select case] que nos sirve de ejemplo conceptual inicial, cada opcion de la estructrura [Select Case], se identifica por medio de un valor, algo asi como si vale uno entonces hago la tarea. Podemos habilitar varios metodos, pero el mas sencilla es usar una clave que identifica al eslabon y por lo tanto a la tarea que realiza cada eslabon.
¿Como paso los datos de trabajo?. Evidentemente, para hacer aualquier tarea (por ejemplo una suma), el eslabon tiene que recibir los datos de trabajo, y tambien hay que definir claramente como y en que formato se devuelve la respuesta.
Resumiendo.
- De alguna forma tengo que declarar la lista (la cadena de nodos) definiendo los objetos y despues instanciandolos y diciendo a cada uno de ellos quien es su hijo. El ultimo nodo que definiremos, es el nodo centinela que nos avisara, de que el codigo ha llegado al final de la cadena y que no ha podido resolver el problema, o dicho de otro modo, no ha habido ningun eslabon de la cadena [ConcreteHandler] que haya resuelto el problema
- Cada eslabon estara identificado por una Clave (Key)
- Los datos de entrada tienen que estar perfectamente codificados. (por ejemplo en un objeto)
- Los datos de salida igual
- El cliente, llamara al primer eslabon, y le pasara los datos, y despues solo tiene que esperar la respuesta de la cadena
- La clase [Handler] del diagrama, es la que define la funcionalidad de cada eslabon.
''' <summary> ''' Esta clase Base que define un eslabón. Es la clase [Handler] del diagrama ''' </summary> Public MustInherit Class EslabonCadenaConcreto Implements IDisposable #Region "Propiedades y variables de la clase" ''' <summary> ''' Referencia al siguiente eslabón de la cadena ''' </summary> Public Property SiguienteEslabon As EslabonCadenaConcreto = Nothing ''' <summary> ''' Los datos de trabajo que circulan entre eslabones. ''' </summary> Public Property DatosTrabajoMensaje As MensajeVo = Nothing #End Region #Region "Constructores" ''' <summary> ''' Constructor estándar. Inicializa una nueva instancia de la clase ''' </summary> Public Sub New() ' End Sub ''' <summary> ''' Constructor estándar. Inicializa una nueva instancia de la clase ''' y define quien es el nodo hijo ''' </summary> Public Sub New(ByVal paramSiguienteEslabon As EslabonCadenaConcreto) Me.SiguienteEslabon = paramSiguienteEslabon End Sub #End Region ''' <summary> ''' Permite establecer el siguiente eslabón de la cadena ''' </summary> ''' <param name=paramSiguienteEslabon">" ''' Un objeto EslabonCadenaConcreto que es el siguiente nodo de la cadena, el nodo hijo ''' </param> Public Sub EstablecerSiguienteEslabon(ByVal paramSiguienteEslabon As EslabonCadenaConcreto) Me.SiguienteEslabon = paramSiguienteEslabon End Sub #Region "Miembros [MustOverride] de la clase " ''' <summary> ''' La clave del nodo. ''' Debe ser declarada por cada nodo hijo, por cada eslabón ''' </summary> Public MustOverride ReadOnly Property Key As String ''' <summary> ''' La función que hace el trabajo ''' Debe ser declarada por cada nodo hijo, por cada eslabón ''' </summary> ''' <param name=paramObjUnMensaje">" ''' El objeto [MensajeVo] que contiene los datos iniciales para hacer el trabajo ''' Se pasa [ByRef] porque tiene que visitar varios objetos y así evito que se hagan copias inútiles del mismo ''' </param> ''' <returns>Un objeto [RespuestaVo] que contiene la respuesta a la tarea encargada </returns> Public MustOverride Function ExecuteTarea(ByRef paramObjUnMensaje As MensajeVo) As RespuestaVo #End Region #Region "IDisposable Support. [Para clases normales y bases ] Implementación de .NET Version: [2020/04/29]" ''' <summary> ''' Variable local para detectar llamadas redundantes que indica [True] el objeto se ha eliminado, [False], en caso contrario. ''' </summary> Private campoDisposedValue As System.Boolean ''' <summary> ''' Obtiene un valor que indica si el objeto se ha eliminado. ''' </summary> ''' <value> ''' <para>Tipo: <see cref=System.Boolean>Boolean</see></para> ''' <para>Es true si objeto se ha eliminado; en caso contrario, es false.</para> ''' </value> Public Overloads ReadOnly Property IsDisposed() As System.Boolean Get Return campoDisposedValue End Get End Property ''' <summary> ''' [Dispose] Código de limpieza de la clase ''' </summary> ''' <param name=paramQuienMeLLama>[True], llama el usuario, [False], llama el sistema</param> ''' <remarks> ''' <para> ''' Dispose (bool quienMeLLama) se ejecuta en dos escenarios distintos. ''' </para> ''' <para> ''' Si [quienMeLLama] es igual a true, el método ha sido llamado ''' directamente, o indirectamente, por el código de un usuario. ''' Los recursos administrados y no administrados se pueden eliminar. ''' </para> ''' <para> ''' Si [quienMeLLama] es igual a false, el método ha sido llamado por el ''' [runtime] desde el interior del finalizador y usted no debe hacer ''' referencia a otros objetos, ni siquiera para eliminarlos. ''' Solo los recursos no administrados pueden ser eliminados. ''' </para> ''' </remarks> Protected Overridable Sub Dispose(paramQuienMeLLama As System.Boolean) If Me.campoDisposedValue = False Then Me.campoDisposedValue = True If paramQuienMeLLama Then '------------------------------------------------------------------------------------------ ' 1) Desechar objetos SI administrados. If Not (SiguienteEslabon Is Nothing) Then SiguienteEslabon = Nothing End If If Not (DatosTrabajoMensaje Is Nothing) Then DatosTrabajoMensaje = Nothing End If End If '------------------------------------------------------------------------------------------ ' 2) Liberar objetos NO administrados. '------------------------------------------------------------------------------------------ ' 3) Establecer campos grandes como Null. '------------------------------------------------------------------------------------------ End If End Sub ''' <summary> ''' [Dispose] Código de limpieza de la clase ''' </summary> ''' <remarks> ''' Visual Basic agregó este código para implementar correctamente el patrón descartable. ''' No cambie este código. Coloque el código de limpieza en Dispose(disposing As Boolean). ''' </remarks> Public Sub Dispose() Implements IDisposable.Dispose Me.Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class
Una clase [Eslabon concreto]
Por ejemplo, la clase eslabón que hace la suma
''' <summary> ''' Realiza la tarea de la suma ''' </summary> Public Class EslabonSuma Inherits EslabonCadenaConcreto #Region "Miembros [MustOverride] de la clase base" ''' <summary> ''' La clave que identifica a este eslabón ''' </summary> Public Overrides ReadOnly Property Key As String Get Return "Suma" End Get End Property ''' <summary> ''' La Función que hace el trabajo ''' o bien, llama al siguiente eslabón ''' </summary> Public Overrides Function ExecuteTarea(ByRef paramObjUnMensaje As MensajeVo) As RespuestaVo If String.Equals(paramObjUnMensaje.Key.ToUpper, Me.Key.ToUpper) Then ' Este eslabón es el que tiene que hacer el trabajo Return HacerLaTareaDelEslabon(paramObjUnMensaje) Else ' Pasar el problema al siguiente eslabón de la cadena Return Me.SiguienteEslabon.ExecuteTarea(paramObjUnMensaje) End If End Function #End Region #Region "HacerLaTareaDelEslabon" ''' <summary> ''' Función que hace el trabajo ''' </summary> ''' <param name=paramObjUnMensaje>El objeto [MensajeVo] que contiene los datos iniciales para hacer el trabajo</param> ''' <returns>Un objeto [RespuestaVo] que contiene la respuesta a la tarea encargada </returns> Private Shared Function HacerLaTareaDelEslabon(ByRef paramObjUnMensaje As MensajeVo) As RespuestaVo Dim localObjResultado As New RespuestaVo localObjResultado.Resultado = paramObjUnMensaje.DatoUno + paramObjUnMensaje.DatoDos Return localObjResultado End Function #End Region End Class
'*******************************
La clase [MensajeVo]
Esta es la clase que se pasa entre los distintos eslabones y que contiene la informacion de entrada neceasaria poarahacer le trabajo, asi como la clave (Key) que identifica al eslabón que tiene que hacer el trabajo
''' <summary> ''' Esta es la clase que se pasan los eslabones ''' Contiene los datos para hacer el trabajo ''' y la clave que identifica al eslabón que tiene que hacer el trabajo ''' </summary> Public Class MensajeVo Public Sub New() ' no hacer nada End Sub '---------------------------------------------------------------- ' En este ejemplo supongo que quiero hacer una operación matemática ' suma, resta, multiplicación, etc ' En este objeto, están los dos datos de trabajo ' También esta la clave, el identificador del eslabón que tiene que hacer el trabajo '---------------------------------------------------------------- ''' <summary> ''' La clave del nodo. ''' </summary> Public Property Key As String ' Uno de los números con los que se trabaja Public Property DatoUno As Integer ' El otro número Public Property DatoDos As Integer End Class
'*******************************
La case que encapsula todo el proceso
''' <summary> ''' Este es el objeto que inicia el proceso ''' </summary> Public Class Chain_Inicial Implements System.IDisposable ' '--------------------- ' Este es el Objeto que llama el cliente que quiere hacer uso del patrón Chain ' Se encarga de: ' a) Definir todos los objetos de la cadena ' b) Dirigir la pregunta del cliente al primer eslabón de la misma ' c) También es el que devuelve el resultado al cliente ' Una observación ' Esta clase es la que mantiene la referencia a todos los objetos que componen los eslabones ' si va a usarse muchas veces solo debería instanciarse una vez en la aplicación ' porque hay un coste (importante) en la declaración y en la eliminación de los objetos. ' ' Este proceso puede realizarse de muchas formas, pero esta es la que me parece mas sensata porque ' todo el proceso de instanciación de objetos, definición de relaciones y montaje de la cadena y la ' eliminación de los mismos [Dispose], esta todo junto y es mas fácil de mantener ' Por otra parte, funciona a modo de un objeto [Facade], ocultando toda la estructura de clases, ' y mostrando únicamente al cliente, la función a la que tiene que llamar [ExecuteTarea] ' '--------------------- ' Forma de uso ' '--------------------- ' ' A) Definición de objetos ' Dim localObjChain As New Chain_Inicio ' Dim localObjMensajeVo As New MensajeVo ' Dim localObjRespuestaVo As New RespuestaVo ' '--------------------- ' ' B) Carga de los valores de trabajo ' localObjMensajeVo.DatoUno = 90 ' localObjMensajeVo.DatoDos = 5 ' '--------------------- ' ' C) Operación a realizar ' localObjMensajeVo.Key = "suma" ' ' D) Iniciar la tarea de la cadena y obtener el resultado ' localObjRespuestaVo = localObjChain.ExecuteTarea(localObjMensajeVo) '------------------ ' /Eof forma de uso '------------------ '--------------------------------------------------------- ' ' A) Definición de objetos Private campoEslabonSuma As EslabonSuma Private campoEslabonResta As EslabonResta Private campoEslabonMultiplicacion As EslabonMultiplicacion Private campoEslabonDivision As EslabonDivision Private campoNodoCentinela As Eslabon_Centinela Public Sub New() '---------------------------- ' Instanciar los eslabones campoEslabonSuma = New EslabonSuma campoEslabonResta = New EslabonResta campoEslabonMultiplicacion = New EslabonMultiplicacion campoEslabonDivision = New EslabonDivision campoNodoCentinela = New Eslabon_Centinela '---------------------------- ' Decir quien es el siguiente elemento de la cadena campoEslabonSuma.EstablecerSiguienteEslabon(campoEslabonResta) campoEslabonResta.EstablecerSiguienteEslabon(campoEslabonMultiplicacion) campoEslabonMultiplicacion.EstablecerSiguienteEslabon(campoEslabonDivision) campoEslabonDivision.EstablecerSiguienteEslabon(campoNodoCentinela) ' En el nodo division no hace falta decir quien es el hijo, ' porque al crearlo, se coloca automáticamente el nodo centinela End Sub Public Function ExecuteTarea(ByRef paramObjUnMensaje As MensajeVo) As RespuestaVo ' Pasar el problema al PRIMER eslabón de la cadena Return Me.campoEslabonSuma.ExecuteTarea(paramObjUnMensaje) End Function #Region "IDisposable Support. [Para clases normales y bases ] Implementación de .NET Version: [2020/04/29]" ''' <summary> ''' Variable local para detectar llamadas redundantes que indica [True] el objeto se ha eliminado, [False], en caso contrario. ''' </summary> Private campoDisposedValue As System.Boolean ''' <summary> ''' Obtiene un valor que indica si el objeto se ha eliminado. ''' </summary> ''' <value> ''' <para>Tipo: <see cref=System.Boolean>Boolean</see></para> ''' <para>Es true si objeto se ha eliminado; en caso contrario, es false.</para> ''' </value> Public Overloads ReadOnly Property IsDisposed() As System.Boolean Get Return campoDisposedValue End Get End Property ''' <summary> ''' [Dispose] Código de limpieza de la clase ''' </summary> ''' <param name=paramQuienMeLLama">[True], llama el usuario, [False], llama el sistema</param>" ''' <remarks> ''' <para> ''' Dispose (bool quienMeLLama) se ejecuta en dos escenarios distintos. ''' </para> ''' <para> ''' Si [quienMeLLama] es igual a true, el método ha sido llamado ''' directamente, o indirectamente, por el código de un usuario. ''' Los recursos administrados y no administrados se pueden eliminar. ''' </para> ''' <para> ''' Si [quienMeLLama] es igual a false, el método ha sido llamado por el ''' [runtime] desde el interior del finalizador y usted no debe hacer ''' referencia a otros objetos, ni siquiera para eliminarlos. ''' Solo los recursos no administrados pueden ser eliminados. ''' </para> ''' </remarks> Protected Overridable Sub Dispose(paramQuienMeLLama As System.Boolean) If Me.campoDisposedValue = False Then Me.campoDisposedValue = True If paramQuienMeLLama Then '------------------------------------------------------------------------------------------ ' 1) Desechar objetos SI administrados. If Not (campoEslabonSuma Is Nothing) Then campoEslabonSuma.Dispose() campoEslabonSuma = Nothing End If If Not (campoEslabonResta Is Nothing) Then campoEslabonResta.Dispose() campoEslabonResta = Nothing End If If Not (campoEslabonMultiplicacion Is Nothing) Then campoEslabonMultiplicacion.Dispose() campoEslabonMultiplicacion = Nothing End If If Not (campoEslabonDivision Is Nothing) Then campoEslabonDivision.Dispose() campoEslabonDivision = Nothing End If If Not (campoNodoCentinela Is Nothing) Then campoNodoCentinela.Dispose() campoNodoCentinela = Nothing End If End If '------------------------------------------------------------------------------------------ ' 2) Liberar objetos NO administrados. ' 3) Establecer campos grandes como Null. '------------------------------------------------------------------------------------------ End If End Sub ''' <summary> ''' [Dispose] Código de limpieza de la clase ''' </summary> ''' <remarks> ''' Visual Basic agregó este código para implementar correctamente el patrón descartable. ''' No cambie este código. Coloque el código de limpieza en Dispose(disposing As Boolean). ''' </remarks> Public Sub Dispose() Implements IDisposable.Dispose Me.Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region End Class
'******************
Ejemplo
Tienes un ejemplo (En formato Zip) en el siguiente [Enlace] con el codigo Md5Hash [2E3C890EDA01047B83B51F34B480EE7C]