herencia - patron builder c#
Patrón de diseño del constructor con herencia: ¿hay una mejor manera? (3)
Estoy creando una serie de constructores para limpiar la sintaxis que crea clases de dominio para mis simulacros como parte de la mejora de nuestras pruebas unitarias generales. Mis constructores completan esencialmente una clase de dominio (como un Schedule
) con algunos valores determinados invocando el WithXXX
apropiado y encadenándolos entre sí.
He encontrado algo en común entre mis constructores y quiero resumirlo en una clase base para aumentar la reutilización del código. Desafortunadamente, con lo que termino se ve así:
public abstract class BaseBuilder<T,BLDR> where BLDR : BaseBuilder<T,BLDR>
where T : new()
{
public abstract T Build();
protected int Id { get; private set; }
protected abstract BLDR This { get; }
public BLDR WithId(int id)
{
Id = id;
return This;
}
}
Tome nota especial de la protected abstract BLDR This { get; }
protected abstract BLDR This { get; }
.
Una implementación de ejemplo de un generador de clases de dominio es:
public class ScheduleIntervalBuilder :
BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder>
{
private int _scheduleId;
// ...
// UG! here''s the problem:
protected override ScheduleIntervalBuilder This
{
get { return this; }
}
public override ScheduleInterval Build()
{
return new ScheduleInterval
{
Id = base.Id,
ScheduleId = _scheduleId
// ...
};
}
public ScheduleIntervalBuilder WithScheduleId(int scheduleId)
{
_scheduleId = scheduleId;
return this;
}
// ...
}
Como BLDR no es del tipo BaseBuilder, no puedo usar return this
en el WithId(int)
de BaseBuilder
.
Está exponiendo el tipo de elemento secundario con la propiedad abstract BLDR This { get; }
abstract BLDR This { get; }
Mi única opción aquí, o me falta algún truco de sintaxis?
Actualización (ya que puedo mostrar por qué estoy haciendo esto un poco más claro):
El resultado final es tener constructores que crean clases de dominio perfiladas que uno esperaría recuperar de la base de datos en un formato legible [programador]. No hay nada malo con ...
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new Schedule
{
ScheduleId = 1
// ...
}
);
ya es bastante legible La sintaxis del generador alternativo es:
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new ScheduleBuilder()
.WithId(1)
// ...
.Build()
);
la ventaja que estoy buscando al usar constructores (y la implementación de todos estos métodos WithXXX
) es abstraer la creación de propiedades complejas (expandir automáticamente nuestros valores de búsqueda de bases de datos con Lookup.KnownValues
correcto sin golpear la base de datos obviamente) y proporcionar el constructor perfiles de prueba comúnmente reutilizables para clases de dominio ...
mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
new ScheduleBuilder()
.AsOneDay()
.Build()
);
Todo lo que puedo decir es que, si hay una forma de hacerlo, también quiero saberlo: uso exactamente este patrón en mi puerto Protocol Buffers . De hecho, me complace ver que alguien más ha recurrido a él, ¡significa que al menos es probable que tengamos razón!
Esta es una buena estrategia de implementación para C #.
Algunos otros lenguajes (no se puede pensar en el nombre del lenguaje de investigación en el que he visto esto) tienen sistemas tipo que apoyan directamente un "yo" covariante / "esto", o tienen otras formas inteligentes de expresar este patrón, pero con C # ''s tipo de sistema, esta es una buena (¿única?) solución.
Sé que esta es una vieja pregunta, pero creo que puedes usar un molde simple para evitar el abstract BLDR This { get; }
abstract BLDR This { get; }
El código resultante sería entonces:
public abstract class BaseBuilder<T, BLDR> where BLDR : BaseBuilder<T, BLDR>
where T : new()
{
public abstract T Build();
protected int Id { get; private set; }
public BLDR WithId(int id)
{
_id = id;
return (BLDR)this;
}
}
public class ScheduleIntervalBuilder :
BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder>
{
private int _scheduleId;
// ...
public override ScheduleInterval Build()
{
return new ScheduleInterval
{
Id = base.Id,
ScheduleId = _scheduleId
// ...
};
}
public ScheduleIntervalBuilder WithScheduleId(int scheduleId)
{
_scheduleId = scheduleId;
return this;
}
// ...
}
Por supuesto, podrías encapsular el constructor con
protected BLDR This
{
get
{
return (BLDR)this;
}
}