c# - poo - patrones de diseño de software fabrica abstracta
Método de fábrica con DI y IoC (6)
Consideraría darle a las dependencias una buena estructura para que pueda utilizar algo similar a la respuesta de Wiktor, pero me gustaría abstraer la fábrica del automóvil. Entonces, no usas la estructura if..then.
public interface ICar
{
string Make { get; set; }
string ModelNumber { get; set; }
IBody Body { get; set; }
//IEngine Engine { get; set; }
//More aspects...etc.
}
public interface IBody
{
//IDoor DoorA { get; set; }
//IDoor DoorB { get; set; }
//etc
}
//Group the various specs
public interface IBodySpecs
{
//int NumberOfDoors { get; set; }
//int NumberOfWindows { get; set; }
//string Color { get; set; }
}
public interface ICarSpecs
{
IBodySpecs BodySpecs { get; set; }
//IEngineSpecs EngineSpecs { get; set; }
//etc.
}
public interface ICarFactory<TCar, TCarSpecs>
where TCar : ICar
where TCarSpecs : ICarSpecs
{
//Async cause everything non-trivial should be IMHO!
Task<TCar> CreateCar(TCarSpecs carSpecs);
//Instead of having dependencies ctor-injected or method-injected
//Now, you aren''t dealing with complex overloads
IService1 Service1 { get; set; }
IBuilder1 Builder1 { get; set; }
}
public class BaseCar : ICar
{
public string Make { get; set; }
public string ModelNumber { get; set; }
public IBody Body { get; set; }
//public IEngine Engine { get; set; }
}
public class Van : BaseCar
{
public string VanStyle { get; set; }
//etc.
}
public interface IVanSpecs : ICarSpecs
{
string VanStyle { get; set; }
}
public class VanFactory : ICarFactory<Van, IVanSpecs>
{
//Since you are talking of such a huge number of dependencies,
//it may behoove you to properly categorize if they are car or
//car factory dependencies
//These are injected in the factory itself
public IBuilder1 Builder1 { get; set; }
public IService1 Service1 { get; set; }
public async Task<Van> CreateCar(IVanSpecs carSpecs)
{
var van = new Van()
{
//create the actual implementation here.
};
//await something or other
return van;
}
}
No lo mencioné, pero ahora puedes implementar varios tipos de autos y sus fábricas correspondientes y usar DI para inyectar todo lo que necesites.
Estoy familiarizado con estos patrones, pero todavía no sé cómo manejar la siguiente situación:
public class CarFactory
{
public CarFactory(Dep1,Dep2,Dep3,Dep4,Dep5,Dep6)
{
}
public ICar CreateCar(type)
{
switch(type)
{
case A:
return new Car1(Dep1,Dep2,Dep3);
break;
case B:
return new Car2(Dep4,Dep5,Dep6);
break;
}
}
}
En general, el problema es con la cantidad de referencias que deben ser inyectadas. Será aún peor cuando haya más autos.
El primer enfoque que me viene a la mente es inyectar Car1 y Car2 en el constructor de fábrica, pero va en contra del enfoque de fábrica porque la fábrica siempre devolverá el mismo objeto. El segundo enfoque es inyectar el dispositivo de seguimiento pero es antipatrón en todas partes. ¿Cómo resolverlo?
Editar:
Manera alternativa 1:
public class CarFactory
{
public CarFactory(IContainer container)
{
_container = container;
}
public ICar CreateCar(type)
{
switch(type)
{
case A:
return _container.Resolve<ICar1>();
break;
case B:
return _container.Resolve<ICar2>();
break;
}
}
}
Manera alternativa 2 (demasiado difícil de usar debido a demasiadas dependencias en el árbol):
public class CarFactory
{
public CarFactory()
{
}
public ICar CreateCar(type)
{
switch(type)
{
case A:
return new Car1(new Dep1(),new Dep2(new Dep683(),new Dep684()),....)
break;
case B:
return new Car2(new Dep4(),new Dep5(new Dep777(),new Dep684()),....)
break;
}
}
}
Muchos contenedores DI admiten la noción de dependencias con nombre.
Ej. (Sintaxis de Structuremap)
For<ICar>().Use<CarA>().Named("aCar");
Container.GetNamedInstance("aCar") // gives you a CarA instance
Si utiliza algo así como una convención, una regla de cómo se deriva el nombre del tipo concreto de carro en sí, tiene una situación donde ya no necesita tocar la fábrica cuando extiende el sistema.
Usar esto en una fábrica es sencillo.
class Factory(IContainer c) {
public ICar GetCar(string name) {
Return c.GetNamedInstance(name);
}
}
Primero, si tiene una fábrica de concreto, un contenedor de IoC podría ser una alternativa en lugar de algo que lo ayude allí.
Luego, simplemente refactorice la fábrica para no esperar una lista de parámetros posible completa en el constructor de la fábrica. Este es el problema principal: ¿por qué pasa tantos parámetros si el método de fábrica no los necesita?
Prefiero pasar parámetros específicos al método de fábrica
public abstract class CarFactoryParams { }
public class Car1FactoryParams : CarFactoryParams
{
public Car1FactoryParams(Dep1, Dep2, Dep3)
{
this.Dep1 = Dep1;
...
}
public class Car2FactoryParams
...
public class CarFactory
{
public ICar CreateCar( CarFactoryParams params )
{
if ( params is Car1FactoryParams )
{
var cp = (Car1FactoryParams)params;
return new Car1( cp.Dep1, cp.Dep2, ... );
}
...
if ( params is ...
Al encapsular la lista de parámetros en una clase específica, simplemente hace que el cliente proporcione exactamente estos parámetros que son necesarios para la invocación de método de fábrica específica.
Editar:
Desafortunadamente, de tu publicación no quedó claro qué son estos Dep1
, ... y cómo los utilizas.
Sugiero el siguiente enfoque que separa al proveedor de la fábrica de la implementación real de la fábrica. Este enfoque se conoce como el patrón de fábrica local :
public class CarFactory
{
private static Func<type, ICar> _provider;
public static void SetProvider( Func<type, ICar> provider )
{
_provider = provider;
}
public ICar CreateCar(type)
{
return _provider( type );
}
}
La fábrica en sí no tiene ninguna implementación, está aquí para establecer las bases de la API de tu dominio, donde deseas que las instancias de tu auto se creen solo con esta API.
Luego, en Composition Root (en algún lugar cerca del punto de inicio de la aplicación donde configura su contenedor real), configura el proveedor:
CarFactory.SetProvider(
type =>
{
switch ( type )
{
case A:
return _container.Resolve<ICar1>();
case B:
return _container.Resolve<ICar2>();
..
}
);
Tenga en cuenta que esta implementación de ejemplo del proveedor de la fábrica utiliza un delegado, pero una interfaz también podría utilizarse como una especificación para un proveedor real.
Esta implementación es básicamente # 1 de su pregunta editada, sin embargo, no tiene desventajas particulares. El cliente todavía llama:
var car = new CarFactory().CreareCar( type );
Refuerza tu comentario sobre el ejemplo de código con Composition Root
. Puede crear lo siguiente y este no es un Localizador de servicios.
public class CarFactory
{
private readonly Func<Type, ICar> carFactory;
public CarFactory(Func<Type, ICar> carFactory)
{
this.carFactory = carFactory;
}
public ICar CreateCar(Type carType)
{
return carFactory(carType);
}
y así es como mira su Composition Root
usando el contenedor Unity DI:
Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
Respondí una pregunta similar hace algún tiempo. Básicamente se trata de tu elección. Debe elegir entre la verbosidad (que le brinda más ayuda de un compilador) y la automatización, que le permite escribir menos código, pero es más propenso a errores.
This es mi respuesta de apoyo de la verbosidad.
Y this también es una buena respuesta que respalda la automatización.
EDITAR
Creo que el enfoque que consideras incorrecto es el mejor. A decir verdad, generalmente no habrá tantas dependencias allí. Me gusta este enfoque porque es muy explícito y rara vez da como resultado errores en el tiempo de ejecución.
Manera alternativa 1:
Este es malo En realidad, es un localizador de servicios, que se considera un anti-pattern .
Manera alternativa 2
Como usted escribió, no es fácil de usar si se mezcla con un containter de IOC. Sin embargo, en algún caso, un enfoque similar ( DI del pobre ) puede ser útil.
En general, no me molestaría en tener "muchas" dependencias en sus fábricas. Es un código simple y declarativo. Lleva unos segundos escribir y puede ahorrarle horas de lucha con los errores de tiempo de ejecución.
Tener una declaración de mayúsculas y minúsculas dentro de una fábrica es un olor a código. Curiosamente, parece que no se está enfocando en resolver ese problema en absoluto.
La mejor y más amigable solución DI para este escenario es el patrón de estrategia . Le permite a su contenedor DI inyectar las dependencias en las instancias de fábrica a las que pertenecen, sin saturar otras clases con esas dependencias ni recurrir a un localizador de servicios.
Interfaces
public interface ICarFactory
{
ICar CreateCar();
bool AppliesTo(Type type);
}
public interface ICarStrategy
{
ICar CreateCar(Type type);
}
Suerte
public class Car1Factory : ICarFactory
{
private readonly IDep1 dep1;
private readonly IDep2 dep2;
private readonly IDep3 dep3;
public Car1Factory(IDep1 dep1, IDep2 dep2, IDep3 dep3)
{
if (dep1 == null)
throw new ArgumentNullException("dep1");
if (dep2 == null)
throw new ArgumentNullException("dep2");
if (dep3 == null)
throw new ArgumentNullException("dep3");
this.dep1 = dep1;
this.dep2 = dep2;
this.dep3 = dep3;
}
public ICar CreateCar()
{
return new Car1(this.dep1, this.dep2, this.dep3);
}
public bool AppliesTo(Type type)
{
return typeof(Car1).Equals(type);
}
}
public class Car2Factory : ICarFactory
{
private readonly IDep4 dep4;
private readonly IDep5 dep5;
private readonly IDep6 dep6;
public Car1Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
{
if (dep4 == null)
throw new ArgumentNullException("dep4");
if (dep5 == null)
throw new ArgumentNullException("dep5");
if (dep6 == null)
throw new ArgumentNullException("dep6");
this.dep4 = dep4;
this.dep5 = dep5;
this.dep6 = dep6;
}
public ICar CreateCar()
{
return new Car2(this.dep4, this.dep5, this.dep6);
}
public bool AppliesTo(Type type)
{
return typeof(Car2).Equals(type);
}
}
Estrategia
public class CarStrategy : ICarStrategy
{
private readonly ICarFactory[] carFactories;
public CarStrategy(ICarFactory[] carFactories)
{
if (carFactories == null)
throw new ArgumentNullException("carFactories");
this.carFactories = carFactories;
}
public ICar CreateCar(Type type)
{
var carFactory = this.carFactories
.FirstOrDefault(factory => factory.AppliesTo(type));
if (carFactory == null)
{
throw new Exception("type not registered");
}
return carFactory.CreateCar();
}
}
Uso
// I am showing this in code, but you would normally
// do this with your DI container in your composition
// root, and the instance would be created by injecting
// it somewhere.
var strategy = new CarStrategy(new ICarFactory[] {
new Car1Factory(dep1, dep2, dep3),
new Car2Factory(dep4, dep5, dep6)
});
// And then once it is injected, you would simply do this.
// Note that you could use a magic string or some other
// data type as the parameter if you prefer.
var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));
Tenga en cuenta que, debido a que no existe una declaración de mayúsculas y minúsculas, puede agregar fábricas adicionales a la estrategia sin cambiar el diseño, y cada una de esas fábricas puede tener sus propias dependencias que se inyectan en el contenedor DI.
var strategy = new CarStrategy(new ICarFactory[] {
new Car1Factory(dep1, dep2, dep3),
new Car2Factory(dep4, dep5, dep6),
new Car3Factory(dep7, dep8, dep9)
});
var car1 = strategy.CreateCar(typeof(Car1));
var car2 = strategy.CreateCar(typeof(Car2));
var car3 = strategy.CreateCar(typeof(Car3));