Tell don’t ask es un estilo de programación en el cual los objetos solo toman decisiones sobre sus datos internos o sobre los que reciben como parámetros y no deben de hacerlo sobre los datos internos de otros objetos.
Es decir que en lugar de pedirle a los objetos datos, debemos decirles que hacer y esperar el resultado de esa acción, con esto evitamos usar la información interna de los objetos para tomar decisiones fuera de ellos y luego pedirles que hacer o afectar su comportamiento interno.
Por lo cual en lugar de tener algo así.
$age = $sebastian->getAge(); if ($age >= 18) { $store->sellsBeerTo($sebastian) }
if ($sebastian->isAdult()) { $store->sellsBeerTo($sebastian) }
Es muy posible que estés pensando, sí muy bien pero ese es un ejemplo muy sencillo, ¿cómo lo voy a emplear en un controlador lleno de condiciones, ciclos y consultas?
Afortunadamente uno de los síntomas que debes de observar es cuando comienzas a tener código espagueti orientado a objetos, es ese tipo de código que puede estar en un controlador y tiene muchas condiciones y ciclos operando los resultados de un objeto sin mencionar que el código se vuelve poco legible a simple vista. Así que, es muy probable que una buena parte de ese código realmente deba de estar en otro lugar, como por ejemplo dentro de un Modelo.
¿Cómo puedo evitar violar tell don’t ask?
Para evitar caer en la trampa del código espagueti orientado a objetos no debes de perder de vista que tus clases deben de tener responsabilidades claras, es decir que cualquier lógica que afecte el estado de un objeto debe de estar dentro de él.
Qué te parece si dejamos la teoría y vemos un ejemplo de la vida real, de esos que quieres ver y que se acerca más a lo que necesitas, posiblemente en algún oscuro rincón de alguno de tus proyectos y que no quieres mencionar que existe.
No hagas lo que puede hacer el objeto por si mismo
Vamos a suponer que tenemos un sistema para inscripción a talleres que se realiza mediante un código que se te proporciona cuando te inscribes, para darte de alta en un taller requieres tener un código válido y además no haberte inscrito en otro taller previamente. El código relacionado con esta parte tiene el siguiente aspecto.
public function postWorkshop(Request $request) { /** @var Participant $participant **/ $participant = Participant::whereCode($request->code)->first(); if (!$participant) { \Alert::message('El código no existe o su código no a sido autorizado', 'warning'); return redirect()->route('workshop.search'); } if (!$participant->isRegisteredAtWorkshop()) { \Alert::message('Ya esta inscrito a un Taller', 'warning'); return redirect()->route('workshop.search'); } $workshops = Workshop::all(); return view('subscribe.workshop-form', compact('participant', 'workshops')); }
¿Puedes ver donde aplicar tell don’t ask? Si no es así no te preocupes, que te parece si organizamos un poco y quitamos el código repetido que se encuentra en las condiciones.
Así, podemos crear un método donde moveremos ese código duplicado como te muestro.
public function postWorkshop(Request $request) { /** @var Participant $participant **/ $participant = Participant::whereCode($request->code)->first(); if (!$participant) { return $this->notifyException('El código no existe o no a sido autorizado'); } if (!$participant->isRegisteredAtWorkshop()) { return $this->notifyException('Ya esta inscrito a un Taller'); } $workshops = Workshop::all(); return view('subscribe.workshop-form', compact('participant', 'workshops')); } public function notifyException($message) { \Alert::message($message, 'warning'); return redirect()->route('workshop.search'); }
¿Qué te parece?, creo que ahora se ve un poco mejor y resalta el problema, ¿no lo crees?.
Si observas las dos condiciones vas a notar que tienen algo en común y es que ambas siguen el patrón de; si pasa esto, realiza esto otro y si te das cuenta esas acciones se hacen sobre los datos que regresa el modelo Participant y no se tú, pero esto tiene pinta de que el controlador está tomando decisiones que pueden estar dentro del modelo Participant.
La forma de resolver esto es trasladar esas decisiones al modelo y que éste se encargue por si mismo de responder de forma adecuada.
Que te parece si te muestro como podemos hacer eso.
class Participant extends Model { public function isRegisteredAtWorkshop() { if ($this->workshops->isEmpty()) { return false; } return true; } public static function checkCode($code) { try { $participant = self::whereCode($code)->firstOrFail(); } catch (ModelNotFoundException $e) { throw new InvalidCodeException('El código no existe o no esta autorizado'); } if (!$participant->isRegisteredAtWorkshop()) { throw new RegisteredAtWorkshopException('Ya esta inscrito a un Taller'); } return $participant; } }
Con esto, ahora solo vamos a obtener un participante si el código es válido y no está inscrito en ningún otro taller, para todo lo demás el modelo Participante lanzará excepciones que el controlador puede manejar fácilmente sin tener que hacerle preguntas al modelo para saber qué hacer.
Quitando las condiciones del controlador y reemplazando por try/catch; nuestro código finalmente debe de quedar como te muestro a continuación.
public function postWorkshop(Request $request) { try { /** @var Participant $participant * */ $participant = Participant::checkCode($request->code); $workshops = Workshop::all(); } catch (InvalidCodeExceptionn $e) { return $this->notifyException($e->getMessage()); } catch (RegisteredAtWorkshopException $e) { return $this->notifyException($e->getMessage()); } return view('subscribe.workshop-form', compact('participant', 'workshops')); }
Adiós condiciones!, Ahora el código es más claro y a simple vista podemos darnos una idea de lo que se pretende en este controlador, además ya no es necesario especificar los mensajes de error ya que los trasladamos a las excepciones y con eso, podemos aprovecharlas para mandar el mensaje correspondiente a nuestra vista si algo sucede.
¿Qué sigue?
Practicar y experimentar en tu código, comienza mirado tus controladores y decide que partes de código corresponden a tus modelos o a otras clases que estas usando. No va ser algo que vas a hacer en un día, te va tomar un tiempo aprender a aplicar el principio, pero si lo haces un paso a la vez muy pronto lo dominaras.
Debes de recordar que no existe nada de malo en tener objetos que nos pueden compartir algo de su estado siempre que en el código no tomes decisiones que pueden realizar tus objetos.
Si aún tienes dudas de como distinguir en qué momento aplicar tell don’t ask solo recuerda lo siguiente.
- No hagas lo que el objeto puede hacer por sí mismo.
- No le pidas datos al objeto para tomar decisiones por él.
Si te ha servido este articulo por favor compartelo en tus redes sociales.
No olvides poner en práctica lo que acabas de leer y déjame saber como te va aplicando el principio en los comentarios.
Autor: ISC Herminio Heredia Santos, @HerminioHeredia , herminioheredia.com.mx