Actualizado: Consulte el final de la pregunta sobre cómo implementé la solución.¿Cómo debo modelar mi código para maximizar la reutilización del código en esta situación específica?
Disculpe la pobremente formulada pregunta, pero no estaba seguro de cómo pedirla. No estoy seguro de cómo diseñar una solución que pueda reutilizarse donde la mayoría del código sea exactamente igual cada vez que se implemente, pero parte de la implementación cambiará cada vez, pero seguirá patrones similares. Estoy tratando de evitar copiar y pegar el código.
Tenemos un sistema de mensajería de datos interno para actualizar tablas en bases de datos en diferentes máquinas. Estamos ampliando nuestro servicio de mensajería para enviar datos a proveedores externos y deseo codificar una solución simple que pueda reutilizarse si decidimos enviar datos a más de un proveedor. El código se compilará en un EXE y se ejecutará regularmente para enviar mensajes al servicio de datos del proveedor.
He aquí un esbozo de lo que hace el código:
public class OutboxManager
{
private List<OutboxMsg> _OutboxMsgs;
public void DistributeOutboxMessages()
{
try {
RetrieveMessages();
SendMessagesToVendor();
MarkMessagesAsProcessed();
}
catch Exception ex {
LogErrorMessageInDb(ex);
}
}
private void RetrieveMessages()
{
//retrieve messages from the database; poplate _OutboxMsgs.
//This code stays the same in each implementation.
}
private void SendMessagesToVendor() // <== THIS CODE CHANGES EACH IMPLEMENTATION
{
//vendor-specific code goes here.
//This code is specific to each implementation.
}
private void MarkMessagesAsProcessed()
{
//If SendMessageToVendor() worked, run this method to update this db.
//This code stays the same in each implementation.
}
private void LogErrorMessageInDb(Exception ex)
{
//This code writes an error message to the database
//This code stays the same in each implementation.
}
}
Quiero escribir el código de tal manera que pueda volver a utilizar las piezas que no cambian sin tener que recurrir a la copia y pegar y completar el código para SendMessagesToVendor()
. Quiero que un desarrollador pueda usar un OutboxManager
y tenga todo el código de la base de datos escrito ya escrito, pero se verá obligado a proporcionar su propia implementación de envío de datos al proveedor.
Estoy seguro de que hay buenos principios orientados a objetos que pueden ayudarme a resolver ese problema, pero no estoy seguro de cuál (es) sería mejor usar.
Esta es la solución que acabé yendo con, inspirado por Victor's answer y Reed's answer (and comments) utilizar un modelo de interfaz. Todos los mismos métodos existen, pero ahora están escondidos en interfaces que el consumidor puede actualizar si es necesario.
No me di cuenta del poder de la implementación de la interfaz hasta que me di cuenta de que permitía al consumidor de la clase conectar sus propias clases para el acceso a datos (IOutboxMgrDataProvider
) y registro de errores (IErrorLogger
). Si bien aún proporciono implementaciones predeterminadas ya que no espero que este código cambie, aún es posible que el consumidor las anule con su propio código. Excepto por escribir varios constructores (I may change to named and optional parameters), realmente no me llevó mucho tiempo cambiar mi implementación.
public class OutboxManager
{
private IEnumerable<OutboxMsg> _OutboxMsgs;
private IOutboxMgrDataProvider _OutboxMgrDataProvider;
private IVendorMessenger _VendorMessenger;
private IErrorLogger _ErrorLogger;
//This is the default constructor, forcing the consumer to provide
//the implementation of IVendorMessenger.
public OutboxManager(IVendorMessenger messenger)
{
_VendorMessenger = messenger;
_OutboxMgrDataProvider = new DefaultOutboxMgrDataProvider();
_ErrorLogger = new DefaultErrorLogger();
}
//... Other constructors here that have parameters for DataProvider
// and ErrorLogger.
public void DistributeOutboxMessages()
{
try {
_OutboxMsgs = _OutboxMgrDataProvider.RetrieveMessages();
foreach om in _OutboxMsgs
{
if (_VendorMessenger.SendMessageToVendor(om))
_OutboxMgrDataProvider.MarkMessageAsProcessed(om)
}
}
catch Exception ex {
_ErrorLogger.LogErrorMessage(ex)
}
}
}
//...interface code: IVendorMessenger, IOutboxMgrDataProvider, IErrorLogger
//...default implementations: DefaultOutboxMgrDataProvider(),
// DefaultErrorLogger()
@Victor Gracias por la sugerencia. ¿Cuál es la ventaja de Dependency Injection sobre el modelo Abstract? –
@Ben: en realidad son solo dos opciones para trabajar. DI tiene la ventaja de permitir que sus clientes siempre trabajen con una sola clase y, potencialmente, ser más fácil para fines de prueba. El uso de la herencia es potencialmente más claro, ya que el usuario trabaja con una sola clase, y el Principio de Substición Liskov dice que pueden usarse de forma intercambiable por el lado del usuario, por lo que una clase vs. dos para que el usuario vea. –
@Ben: Mi respuesta fue básicamente esto: la opción 1 es "modelo abstracto"/herencia, la opción 2 es DI. –