Un nuevo canal en youtube

Hola a todos! solo para dejarles saber que he abierto un nuevo canal en youtube para demostrar algunas cosas que he estado discutiendo en este blog.

Para empezar les recomiendo seguir la kata de Gilded Rose en C#. Pueden ver el intro aqui:

Y el primer episodio aqui:

Actualmente solo he producido contenido en español, pero pronto pondre cosas en ingles tambien. Mientras tanto, hechenle un vistazo y si les gusta suscribanse!

Cómo manejar llamadas de observables dependientes en una llamada asincróna en Angular 2/4

Recientemente he estado trabajando con Angular de 2/4. Un día me encontré con algo como esto:

public getCustomersOnArea(zip:string):Observable<Customer>{...}

El problema fue que tuvimos que hacer 2 llamadas para obtener los datos. Pero una llamada depende de los datos de la otra. ¿Cómo resolver esto? Una manera es encapsular las 2 llamadas en una tercera y regresar esta para que se subscriban a ella.

public getCustomersOn(zip:string):Observable<Customer>{

return new Observable<Customer>(suscriptor => {
            this.http.post(zip)
                .subscribe (res => {}
                    this.http.post(res)
                        .subscribe (r => {}
                            Subscriber.Next(r);                           
                        },
                          e => subscriber.error(e),
                          () => subscriber.complete());
                });
}

Y eso es todo. ¿Conoces otra manera? Dejalo en la sección de comentarios

El desafío de la calculadora: resuelto

Un par de semanas atrás, compartía un ejercicio que utilizaba para evaluar habilidades de diseño OO en entrevistas de reclutamiento. Recibí algunos comentarios interesantes de diferentes personas. Hoy quiero compartir una respuesta. Como alguien señaló, este es un problema muy simple, pero aún así, no he podido encontrar nadie que solucionara el problema. Ni uno solo. En mi experiencia, el momento en que la entrevista fue en una dirección diferente a un ejercicio CRUD el desarrollador se perdia. Así que vamos a revisar el planteamiento del problema:

Diseñar un programa que dado una cadena como «(1+2)/3 * 4» devuelve el resultado correcto.

Encontrar las abstracciones

Así que tenemos un objeto que se evalúa una expresión y devuelve un valor numérico. Sencillo.


Sin embargo, tener un objeto que evalúa cada tipo de operación presente en la expresión es una violación del principio de responsabilidad individual. ¿La solución? Divide y vencerás. Vamos a crear un objeto para cada operación.


Ahora, lo único que debe hacer la calculadora es pasar la expresión a cada objeto operación. El valor devuelto de cada objeto de la operación debe ser la expresión con los valores resueltos para que puede pasarse al siguiente objeto de la operación. Para ampliar las capacidades de la calculadora todo lo que se necesita es añadir otro objeto operación. Esto se logra fácilmente utilizando una interfaz implementada por todos los objetos operación.


Centrándose en el qué, no cómo

Quiero llamar su atención sobre el hecho de que hasta ahora no hemos discutido cómo estos objetos van a evaluar las partes relevantes de la expresión. Aún no tenemos los tipos de datos en los mensajes (es expresión una cadena o un objeto?) Para mí esto es el sello distintivo de un desarrollador OO experimentado: la capacidad de concentrarse en el panorama global e ignorar los pequeños detalles hasta que se necesite. Esto se denomina abstracción. Tradicionalmente estamos capacitados para pensar de forma algorítmica, pensando en cada detalle. Toma algo de tiempo y esfuerzo para empezar a centrarse en el qué y posponer el cómo. Sin embargo de esta manera podemos utilizar otra técnica para el diseño de los objetos (los detalles de implementación) como TDD. En este caso, probablemente utilizaría una expresión regular para coincidir con el signo aritmético, extraer los valores, ejecutar la operación y reemplazar el valor en la expresión.

El próximo desafío

Por lo tanto, si alguna vez estuviste en una entrevista conmigo, esta es la respuesta que probablemente casi encontraste. De todos modos, tengo otro reto: ¿puedes identificar los patrones de diseño utilizados en esta solución? ¿Qué patrones usarias para mejorarla?

Notas de The Phoenix Project

Acabo de terminar de leer The Phoenix Project, un libro al estilo de La Meta de Eliyahu M. Goldratt. Voy a intentar cubrir las ideas principales sin entrar demasiado en detalle. Si hay interés puedo escarbar más en un tema o una idea.

La premisa

Probablemente la idea principal es que el proceso de construcción de software es semejante a la fabricación de una línea de producción de la planta. Si nunca has estado en el proceso de fabricación hay algunos conceptos que debes entender antes de ir más lejos.

Lista de materiales

La lista de materiales es una lista que contiene todos los componentes necesarios para crear un producto. Un ejemplo podría ser algo como:

  • Producto X
    • Componente 1
      • Sub componente 1
      • Sub componente 2
        • Componente 3
    • Componente 2
      • Sub componente 3
        • Materia prima 1
        • Materia prima 2
    • Componente 3
      • Materia prima 3
      • Sub componente 4

La lista de materiales también incluye la cantidad de cada componente o material necesario.

Centro de trabajo

El centro de trabajo como su nombre lo indica es un lugar específico donde se realiza una actividad específica. Es decir, en una planta de fabricación de automóviles, podría existir un centro de trabajo donde se unen las puertas, otro donde se monta el motor en el vehículo y otro donde se pinta la carrocería. En The Phoenix Project hay 4 elementos en cualquier centro de trabajo: la máquina, el hombre, el método y las medidas. Un centro de trabajo también tiene un tiempo de espera: el tiempo que tardan en empezar a trabajar en una tarea asignada. Entre mas tareas pendientes tiene un centro de trabajo, más tardará en empezar a trabajar en la última tarea asignada. Esto se representa mediante la siguiente fórmula:

Tiempo de espera = % ocupado / % inactivo

Lista de recursos

La lista de recursos = Lista de materiales + centros de trabajo + enrutamiento, donde enrutamiento especifica que centros de trabajo se necesitan para terminar un producto y en qué orden. Básicamente una lista de todos los pasos necesarios para completar un producto.

La liberación de una característica de software como una línea de producción

Según el libro, hay algunos centros de trabajo donde ciertas actividades tienen lugar cuando estamos liberando una característica/Parche/actualización de software. Por ejemplo:

Código-> construcción -> prueba-> empaquetamiento -> liberación-> configuración-> Monitoreo

Estas son todas actividades que tienen lugar en su propio lugar, y que necesitan su propia gente, con sus propias técnicas y procedimientos y sus propias métricas.

Los 4 tipos de trabajo

Según el libro hay 4 tipos de trabajo:

  1. Proyectos de negocio.- Estas son las iniciativas empresariales para alcanzar los objetivos de la empresa.
  2. Proyectos de TI interno.- Son sobre todo mejoras en la infraestructura que pueden o no pueden ser derivados de proyectos empresariales.
  3. Cambios.- Diferentes actividades que derivan de proyectos internos de TI y del negocio que pueden afectar gravemente a los sistemas de producción.
  4. Trabajo no planificado / recuperación trabajo.- Corrección de errores de producción

La idea aquí es encontrar de donde viene el trabajo no planificado y arreglar la fuente lo antes posible, acelerando los otros tipos de trabajo que realmente agregan valor al negocio. Para ello el libro propone 3 fases de mejoramiento llamadas los 3 caminos.

Los 3 caminos

El primer camino

El primer camino se centra en acelerar los tipos de trabajo que agregan valor al negocio. Logra esto al simplificar el flujo de trabajo de los centros de trabajo, utilizando técnicas como las de manufactura esbelta: identificación de flujos de valor y uso de tableros kanban, entre otros, limitando el WIP (trabajo en proceso) y siguiendo la teoría de las restricciones: identificación de cuellos de botella y explotarlas al máximo. Se basa en mecanismos tales como integración e implementación continua, automatizar la creación de entornos virtuales bajo demanda (mediante herramientas como Vagrant y Docker) y así sucesivamente.

El segundo camino

El segundo camino se centra en deshacerse del trabajo no planificado cuanto antes. Esto se logra mediante la creación de bucles de retroalimentación más rápidos de los centros de trabajo hacia el centro de trabajo anterior hasta el centro de trabajo responsable. Esto permite la detección de anomalías y errores de manera mas temprana en el ciclo de implementación. Utiliza técnicas como katas de mejora, creación de objetivos compartidos entre departamentos y crear una telemetría de producción para asegurarse de que 1) los sistemas están funcionando bien, 2) los ambientes funcionan bien y 3) se están cumpliendo objetivos cliente.

El tercer camino

El tercer camino pone el foco en la creación de una cultura que 1) fomenta la experimentación continua y 2) la repetición y práctica. Estos son los requisitos para la innovación y la calidad que son una necesidad para la supervivencia de cualquier negocio estos días.

Alineación a los objetivos de negocio

Una de las cosas que nos pasa mucho a los técnicos, es que nos quedamos tan centrados en tecnología que hemos prestado menos atención a la empresa a la que sirve. Simplemente elegimos aprender tecnología en lugar del negocio. Esto es comprensible ya que las habilidades de tecnología son más transferibles que el conocimiento del dominio para nosotros. Pero uno de los puntos enfatizados en el libro es que primero tenemos que entender los objetivos de la empresa para la que estamos trabajando. Esto nos dará los criterios para discriminar qué proyectos y cambios son importantes y deben recibir la mayor parte de nuestros recursos. Para mi esto suena como la regla 80/20. Sólo al alinear la tecnología con los objetivos de negocio, puede convertirse en una verdadera ventaja para el negocio.

Impresiones sobre el libro

Encontré el libro muy fácil de leer. Si bien no tan cautivador como «La Meta», la historia y estilo de escritura no eran aburridos. Después de leer la novela entera, he encontrado algunas reflexiones en la guía de recursos de proyecto Phoenix y capítulos posteriores. El termino DevOps comprende una gran cantidad de herramientas y prácticas y Gene Kim y compañía han hecho un buen trabajo organizadolos en los 3 caminos. En fin, una lectura muy recomendada.

Aprender varios lenguajes de programación o especializarse en uno?

Recuerdo una clase en la universidad cuando un maestro nos dijo que antes la tendencia era aprender varios lenguajes de programación, pero ahora se trataba de especializarse en uno. Aunque estoy de acuerdo en que volverse un especialista es indispensable en este tiempo, dejar de aprender otros lenguajes de programación no solo nos pone en desventaja sino que puede ser peligroso (profesionalmente hablando). Esta es la razón:

TDD y la premisa de "Agregar valor"

Descubrir lo que aporta un valor

Sólo hay 2 tipos de código: el que aporta valor al cliente y el que no. Yo los llamo el código de dominio y el código de plomería. Pero ¿qué significa esto?

Código de dominio

En términos simples si no es en la jerga de negocios, no es código de dominio. Es decir, todos los conceptos de negocio y las reglas que dictan cómo se relacionan entre sí, los servicios proporcionados por la empresa a sus clientes, las acciones en situaciones específicas (procedimientos) que son parte del dominio del negocio y la automatización de estos (completo o parcial) son las cosas que ayudan a la empresa a incrementar ingresos, disminuir costos, acelerar la ejecución de procedimientos y tomar mejores decisiones. De lo contrario no añade valor al negocio.

Código de plomería

Este es el tipo de código que directamente no agrega valor al negocio. Está compuesto sobre todo por aspectos técnicos como la base de datos, arquitectura de software, el stack de tecnología, frameworks, etc. Es necesario en un sistema de información, pero no es la razón por la qué el sistema fue creado en primer lugar.

La paradoja del «huevo o la gallina»

El sentido común dicta que las cosas que son más importantes para el negocio deben ponerse primero. Eso es, un equipo de desarrollo debe asegurarse que las políticas, reglas y lógica se apliquen correctamente en el código antes que nada. La práctica común, es una historia diferente. Eso es porque el desarrollador necesita generalmente una cantidad mínima de código de plomería para probar si una regla de negocio está funcionando como se esperaba. Consideremos el caso cuando un desarrollador tiene que probar una regla simple: no puede obtener más dinero de una cuenta que el disponible. Para probar esto el desarrollador puede empezar a crear una tabla BankAccount, luego escribir el código para la regla, luego crear un programa de prueba para ejercitar ese código. Y entonces él tendría que agregar código para el manejo en caso de falla con la conexión de base de datos. Así que escribir el código que agrega valor (código) es sólo una pequeña fracción de toda la operación. La mayoría de las acciones son sobre la configuración de la infraestructura. Más aún, muchos de los desarrolladores toman esto hasta crear una API o interfaz de usuario. Esto hace más difícil probar el código relacionado con la regla, puesto que ahora hay varios puntos donde algo puede ir mal. Ahora para saber si la regla se implementa correctamente, el desarrollador tiene que poner una aplicación de extremo a extremo que tendrá que ser modificada en caso de que el código deba reescribirse. ¿Que es primero: dominio o código de plomería? 

Definir lo que hay que hacer

TDD cambiar el enfoque del código de plomería al código de dominio. Lo hace al obligar a los desarrolladores a crear especificaciones funcionales en forma de pruebas y crear código para cumplir con las expectativas de la especificación.

En un proyecto de desarrollo típico, mucho del análisis inicial entra el código de plomería: base de datos, estructuras, sistemas operativos, hardware y así sucesivamente. Por otro lado, la idea de negocio de lo que el sistema se supone que hace esta sujeto a evolucionar tan pronto como los desarrolladores y el negocio empiecen descubrir los requerimientos y necesidades. Por desgracia, muchos desarrolladores no cavan mas en esto hasta más tarde cuando se ha decidido todo el stack de tecnología. Esto crea una situación donde el código de dominio ahora está restringido por las limitaciones del stack de tecnología.

TDD invierte esta situación. Al contar con los desarrolladores para crear las especificaciones en primer lugar, se encuentran en la necesidad de entender mejor cual es el resultado esperado para una pieza de software. Tomando nuevamente el ejemplo anterior, ¿qué se supone que debe suceder cuando en la cuenta bancaria no hay suficiente dinero para cumplir con una operación de retiro? ¿Una excepción? ¿Devuelve un mensaje? ¿Pueden ser una cadena o una estructura de clases? ¿O una función (closure)?¿Debe indicarse al titular de la cuenta ir a través de algún tipo de procedimiento (como préstamo)? Para responder a estas preguntas, el desarrollador debe entender lo que espera el negocio. Esto lo llevará a volver al negocio y hacer preguntas hasta que entienda lo suficiente como para continuar. Este proceso generalmente ocurre en cualquier proyecto de desarrollo, especialmente si está siguiendo una metodología ágil, pero el uso de TDD lo acelera grandemente. Permite al equipo de desarrollo no sólo para escribir el software de la manera correcta, sino ayudar a la empresa para decidir si es lo correcto.

Así que ¿estás listo para empezar?

Code vs Database: Alojando la logica del negocio

En el 2008 empece a trabajar para una startup que tenia un producto para la administración de gobiernos. La cosa con el gobierno es que invierten mucho dinero en infraestructura de TI: licencias, hardware, software a la medida etc. Esto hace casi imposible que acepten una nueva oferta si no pueden reutilizar lo que ya tienen. Con el tiempo la empresa se hizo de varios clientes. Como le hicimos para desplegar el producto usando Oracle, MS SQL o cualquier sistema de base de datos sin cambiar el código? Desde entonces cada vez que me encuentro en un proyecto que esta restringido por una tecnología (como la base de datos) la pregunta vuelve a mi mente.

 Como conseguir independencia de la base de datos

El gobierno es una institución compleja con todas sus reglas y regulaciones. Fue todo un reto hacer el producto suficientemente flexible para adaptarlo a las necesidades de cada cliente. Hasta tenia un motor de scripting! Pero en retrospectiva, creo que esto fue posible por 2 cosas: el uso de un ORM y evitar a toda costa poner lógica de negocios en la base de datos.

Usar un ORM nos permitió usar una representación de datos independiente. Eso quiere decir que todos los datos que necesita el sistema están representados por los objetos, independientemente de como se persistan estos. Esto nos dio la libertad de cambiar de una tecnología de base de datos a otra sin necesidad de cambiar el código. Todo por lo que teníamos que preocuparnos era por cambiar el proveedor, un objeto con el conocimiento de como serializar un objeto a una base de datos. De haberlo deseado hasta podríamos haber escrito un proveedor para guardar en archivos de texto.

Por lo anterior era claro que no era una buena idea poner lógica de negocios en la base de datos. Si lo hubiéramos hecho nos meteríamos en un problema cada vez que migráramos de una base de datos a otra. También es mas difícil de probar.

Encapsulamiento como un atributo del sistema

Ya he hablado del encapsulamiento como un atributo del código. Sin embargo el mismo principio puede aplicarse a un sistema en general. Un requisito es tener una representación de datos independiente, con lo que reducimos el impacto de fuerzas externas (como cambiar las tecnologías de presentación o de almacenamiento). Ese es el propósito de la mayoría de las arquitecturas: hacer que la lógica del negocio sea resistente a cambios externos. En mi experiencia este es un efecto automático cuando seguimos este principio a nivel de objetos.

Acerca de SQL

Algo que siempre me ha llamado la atención es que la especificación estándar de SQL no tiene estructuras de control. Es por eso que cuesta tanto trabajo mover la lógica de un sistema de base de datos a otro: cada quien implementa estas estructuras a su manera. Por que lidiar con esto cuando puedes usar un lenguaje de programación general que ya incluye todo esto? Si el ANSI SQL no lo implementa, por que forzarlo?

Niveles de abstracción

«Abstracción» es la acción de simplificar algo hasta lo mas esencial.

Me gusta explicarlo como una especie de google maps pero en lugar de utilizar el zoom dentro y fuera de un mapa del mundo estas explorando un modelo. Al igual que con google maps tenemos varias vistas con diferentes niveles de detalle. Llamamos a estos niveles de abstracción. En el contexto del diseño de un sistema es una herramienta útil. Así que usando google maps como una metáfora que quiero compartir una manera personal para ver los niveles de abstracción en un sistema.

Nivel de abstracción Propósito
10000 pies Vista de todas las acciones que se pueden realizar en el sistema
8000 pies Vista de como un usuario ejecuta las acciones sobre el sistema (Interface de Usuario)
6000 pies Vista de los pasos necesarios para la ejecución de la acción solicitada
4000 pies Vista de los objetos que llevan a la acción solicitada
Suelo Detalles de la implementación de los objetos

Para esta discusión voy a dejar la vista de 8000 pies fuera.

10000 pies

Este nivel puede ser representado de varias maneras. Mi favorito es utilizando un diagrama de caso de uso.

La otra manera que me resulta muy útil es utilizando «features» de BDD.

 Feature: Create Loan
 In order to pay for a necessary item
 As a customer with no cash at hand
 I want to get a loan

Lo bueno de esto es que también expresa el objetivo del usuario.

6000 pies

En este punto de vista nos asomamos dentro de las acciones del sistema (caso de uso). Por lo general, una acción tiene más de un camino de ejecución: la ruta por defecto (esperada) y una o más alternativas. Esta visión puede explorarse mediante un diagrama de actividad.
Este punto de vista también puede ser explorado con el artefacto «scenario» de BDD.

 Scenario: Apply for a loan while having an open loan with a different provider
 Given I already have an account on the site
 And I have an open loan on with a different provider
 When I try to create a new loan
 Then I'll see a message saying "Sorry you can't open a new loan if you already have one open"

Scenario: Apply for a loan with an amount exceding the maximum allowed by the state
 Given I already have an account on the site
 When I try to create a new loan
 And the amount requested exceeds the maximum allowed by the state I live in
 Then I'll see a message saying "Sorry the amount you applied for exceeds the amount allowed by the state"

Scenario: Get a loan
 Given I already have an account on the site
 And I have no open loan
 When I try to create a new loan
 Then the loan will be created

4000 pies

Si la vista de 6000 pies nos permite echar un vistazo en la acción, que nos muestra los varios caminos de ejecución, entonces la vista de 4000 pies nos muestra de que se tratan y la forma en que los objetos de negocio la llevan a cabo. Normalmente utilizo diagramas de interacciones a este nivel.

Como se puede ver este diagrama se enfoca exclusivamente en los objetos de negocio, sus responsabilidades y las interacciones entre ellos con el fin de cumplir con los objetivos de la acción. En este ejemplo en particular estoy incluyendo 2 caminos, como se puede ver pero generalmente tendría uno para cada escenario.
El punto aquí es que estos métodos están vagamente definidos. Aquí es donde entra TDD. Puede crear una prueba declarando el comportamiento esperado y luego codificar ese método en particular, aislando cualquier dependencia exterior.

TDD vs BDD

La razón por la que hice esta tabla fue para explicar a un compañero que TDD y BDD son básicamente lo mismo en un nivel de abstracción diferente.
Así que si creas pruebas antes de escribir cualquier código, si es en el nivel de abstracción de 4000 pies entonces se llama TDD, mientras que si es para cualquier cosa por encima de ese nivel de abstracción, se llama BDD.

Tu que opinas?

Pide ayuda, no datos

Hace algunas semanas recibimos un correo solicitando confirmación de nuestra asistencia a un evento de la empresa. Incluido venia un pequeño código:

if (employee.WantsToAttend()) {
	if (employee.IsWorkingFromOffice1())
	{         employee.reply(manager1, "I wanna be there");     }
	else if (employee.IsWorkingFromOffice2())
	{         employee.reply(manager2, "Dude! I wanna be there!");      }
}

Entiendo que el propósito del código era mas de marketing, pero he visto código como este en varias ocasiones. Vamos a revisarlo.

Pilares de la programación orientada a objetos

Todos hemos escuchado de los principios de la programación orientada a objetos: encapsulamiento, polimorfismo, herencia y abstracción. Vamos a aplicarlos al código que acabamos de ver.

Encapsulamiento

Encapsulamiento es un principio que dicta que debemos esconder los mecanismos internos de un objeto, de manera que si los modificamos esto no afecte a los demás objetos relacionados.

Con eso en mente, revisemos los siguientes métodos:

employee.WantsToAttend()
employee.IsWorkingFromOffice1()
employee.reply(manager1, "I wanna be there");

Estos métodos son detalles de implementacion del escenario «confirmar asistencia». La verdad no nos importa como el empleado confirma la asistencia, solo que lo haga. Podemos mover la primera evaluación:

class Employee
{
   ...
   public void ConfirmAssistance(string manager, string msg)
   {
      if(wantsToAttend())
		reply(manager,msg);
   }
}

Ahora el código cliente quedaría un poco mas limpio:

if (employee.IsWorkingFromOffice1())
	    employee.ConfirmAssistance(manager1, "I wanna be there");     
	else if (employee.IsWorkingFromOffice2())
	    employee.ConfirmAssistance(manager2, "Dude! I wanna be there!"); 

Al hacer la evaluación dentro del objeto evitamos la necesidad de exponer datos internos. Esto implica un cambio en las responsabilidades del objeto. De ahora en adelante no tenemos que preocuparnos por averiguar si el employee quiere asistir o no. Es automático.

Allen Holub se refiere a esto como pedir ayuda no datos. Esta es una consecuencia directa del encapsulamiento y probablemente el consejo que mas me ha ayudado en el cambio del pensamiento relacional al orientado a objetos.

Vamos a encapsular la evaluación de las oficinas:

class Employee
{
   ...
   public void ConfirmAssistance(Func<string,string> msgFactory)
   {
      if(wantsToAttend())
		reply(Office.Manager,msgFactory(Office.Id));
   }
}

Y así nos quedamos con una linea en el cliente:

employee.ConfirmAssistance(officeId => officeId == 1?"I wanna be there": "Dude! I wanna be there!");

En este caso el texto del mensaje se decide fuera del objeto, así que pasamos la función encargada de eso al objeto. De esta forma evitamos exponer el funcionamiento y al mismo tiempo inyectamos el comportamiento deseado.

Si el código te resulta extraño, solo estamos declarando una función anónima usando una sintaxis llamada lambda expressions (los ejemplos están en C#).

Comparada con la versión previa, cual código te parece mas reutilizable?

Algunas observaciones:

1) ahora tenemos un mensaje para la oficina 1 y para el resto de las oficinas (no solo la oficina 2)

2) realmente no nos importa como se almacenan los datos de la oficina. Podríamos cambiarlos y no afectaríamos nada.

Separando responsabilidades

El método reply implica la llamada a un tercero. Almacenar una referencia a un tercero en este caso esta de mas. Vamos a separar esto en 2 partes: la creación y el envió del mensaje.

class MessageGateway
{
    Send(Message msg){...}
}

class Message 
{
   public Message(string recipient, string body)
   {
      Recipient = recipient;
      Body = body;      
   }

   public string Recipient {get;set;}
   public string Body {get;set;}
}

class Employee
{
   ...
   public Message ConfirmAssistance(Func<string,string> msgFactory)
   {
      if(wantsToAttend())
		return new Message(Office.Manager,msgFactory(Office.Id));
	  else
	    return null;
   }
}

El código cliente quedaría:

Message reply = employee.ConfirmAssistance(officeId => officeId == 1?"I wanna be there": "Dude! I wanna be there!");    

if(reply != null) new MessageGateway().Send(reply);

Ahora el objeto employee es el encargado de crear el mensaje y el objeto MessageGateway el encargado de enviarlo. Separar las responsabilidades de esta manera cumple con el Single Responsibility Principle.

Pero ahora estamos rompiendo el encapsulamiento en el objeto Message.

Vamos a arreglarlo.

class MessageGateway
{
    Send(string recipient, string body){...}
}

class Message 
{
   public Message(string recipient, string body)
   {
      Recipient = recipient;
      Body = body;      
   }

    string Recipient;
    string Body;
    
    public SendThrough(MessageGateway gateway)
    {
       gateway.Send(Recipient,Body);
    }
}

class Employee
{
   ...
   public Message ConfirmAssistance(Func<string,string> msgFactory)
   {
      if(wantsToAttend())
		return new Message(Office.Manager,msgFactory(Office.Id));
	  else
	    return null;
   }
}

En el código cliente:

Message reply = employee.ConfirmAssistance(officeId => officeId == 1?"I wanna be there": "Dude! I wanna be there!");    

if(reply != null) reply.SendThrough(new MessageGateway());

Quizá esto no tenga mucho sentido ahora. Todo lo que hicimos fue invertir la dirección en que se pasan los datos. Pero ahora podemos usar polimorfismo para remover esa validación de null.

Polimorfismo

Hay un concepto que indica que entre mas bifurcaciones tiene un programa, mas difícil es su mantenimiento. Se llama complejidad ciclomatica y es un indicador de la calidad del código. En resumen entre menos ‘if’ y ‘switch’ mejor.

Con los cambios iniciales habíamos removimos todos los ‘if’. Pero luego introdujimos uno nuevo al validar si el mensaje venia null. Vamos a quitarlo. Una técnica común en la programación orientada a objetos es el null object pattern. Utiliza polimorfismo para eliminar bifurcaciones en el código cliente. Vamos a verlo.

1) extraemos una interface común

interface IMessage
{
   SendThrough(MessageGateway gateway);
}

2) creamos un objeto que no haga nada, como lo haríamos si recibiéramos un null.

class Message: IMessage
{
   public Message(string recipient, string body)
   {
      Recipient = recipient;
      Body = body;      
   }

    string Recipient;
    string Body;
    
    public SendThrough(MessageGateway gateway)
    {
       gateway.Send(Recipient,Body);
    }

   //usually the null object it's used in a singleton fashion
   class NullMessage: IMessage
   {
	   public SendThrough(MessageGateway gateway) 
	    {
	       //Do nothing :)
	    }
    }

   public static IMessage Null{get;private set;}

   public static Message()
   {
      Null = new NullMessage();
   }
  
}

3) regresa ese objeto en lugar de null

class Employee
{
   ...
   public Message ConfirmAssistance(Func<string,string> msgFactory)
   {
      if(wantsToAttend())
		return new Message(Office.Manager,msgFactory(Office.Id));
	  else
	    return Message.Null;
   }
}

Presto! actualizamos el código cliente:

Message reply = employee.ConfirmAssistance(officeId => officeId == 1?"I wanna be there": "Dude! I wanna be there!");    

reply.SendThrough(new MessageGateway());

y de vuelta a una linea

employee
	.ConfirmAssistance(officeId => 
	   officeId == 1? "I wanna be there": "Dude! I wanna be there!")   
	.SendThrough(new MessageGateway());

Polimorfismo nos permite cambiar el comportamiento de un sistema sin cambiar el código. Esto se logra creando variaciones de un método e intercambiándolos según se necesite.

Si no es programación orientada a objetos, entonces que es?

Repasemos:

objeto – datos (estado) = modulo (recuerdan vb6?)

objeto – métodos (comportamiento) = struct (disponible desde C)

Es facil escribir un programa que usa módulos y structs. Y funciona bien en muchos casos (forms over data ;))

En conclusión

1) Encapsulamiento habilita el Polimorfismo

2) Polimorfismo habilita el uso de patrones de diseño y otras bondades de la programación orientada a objetos

La programación orientada a objetos permite la creación de aplicaciones sumamente flexible pero tiene un costo: indireccion. Si tu proyecto es relativamente simple (como este ejemplo) quizá te convenga usar otro paradigma como programación estructurada (módulos + structs). Pero si decides usar la programación orientada a objetos, ten en mente que los objetos hacen cosas. Pide ayuda, no datos!

Extra: un toque funcional

Closures pueden simplificar mucho este código. Ya que están presentes desde smalltalk, los considero parte de la programación orientada a objetos. Así es como quedaria:

class MessageGateway
{
    Send(string recipient, string body){...}
}

class Employee
{
   ...
   public Message ConfirmAssistance(Action<string,string> confirm)
   {
      if(wantsToAttend())
		confirm(Office.Manager,Office.Id);  
   }
} 

//client code

employee.ConfirmAssistance((manager,officeId)=> {
   var response = officeId == 1? "I wanna be there": "Dude! I wanna be there!";
   new MessageGateway().Send(manager, response);
});

La verdadera programación orientada a objetos

Sólo quería compartir esto contigo:

https://blog.udemy.com/Object-Oriented-Programming-a-Critical-Approach/

Como se mencionó en el post, también creo que mucha de la belleza de la programación orientada a objetos definida originalmente (por smalltalk) se ha perdido.

Si no has aprendido a smalltalk, deberías. Cambiará tu manera de pensar acerca de programación orientada a Objetos.

Aquí hay algo para ayudarte a comenzar:
http://Rmod-Pharo-MOOC.Lille.INRIA.fr/MOOC/WebPortal/Co/Content.html

¡Diviertete!