我把我的问题简化为一个涉及动物的例子.我想定义一组接口(/抽象类),允许任何人为给定的动物创建工厂并将其注册到中央注册器:AnimalRegistry跟踪所有已注册的AnimalFactory对象,然后生成并提供一致的集合Animal对象的功能.
通过我写这个(下面的代码)的方式,我有一个非常简单的界面来处理泛型动物:
AnimalRegistry registry = new AnimalRegistry();
registry.Register<ElephantFactory>();
registry.Register<GiraffeFactory>();
Animal a1 = registry.GetInstance<ElephantFactory>().Create(new ElephantParams(weight: 1500));
Animal a2 = registry.GetInstance<GiraffeFactory>().Create(new GiraffeParams(height: 180));
registry.Serialize(a1);
registry.Serialize(a2);
Run Code Online (Sandbox Code Playgroud)
但是,我真的不喜欢这个:
在编译时没有什么可以阻止ElephantParams意外传递给registry.GetInstance<GiraffeFactory>().Create(AnimalParams).
如何AnimalFactory以这样的方式编写基类,以确保在编译时只能AnimalParams传递正确的类型,同时仍然允许其他人为其他动物编写自己的具体实现?
我可以...
Create(ElephantParams)和Create(GiraffeParams)各自的班级,但这需要抛弃所有的基类有一个Create()方法的合同.AnimalRegistry在它们AnimalParams和相应的工厂之间添加一个额外的映射,并Create()在注册表中定义一个新方法,但这不是一个优雅的解决方案,因为问题只是移动而不是解决.我怀疑答案在于更多类型的泛型,但它现在逃脱了我.
AnimalRegistry:
public class AnimalRegistry
{
Dictionary<Type, AnimalFactory> registry = new Dictionary<Type, AnimalFactory>();
public void Register<T>() where T : AnimalFactory, new()
{
AnimalFactory factory = new T();
registry[typeof(T)] = factory;
registry[factory.TypeCreated] = factory;
}
public T GetInstance<T>() where T : AnimalFactory
{
return (T)registry[typeof(T)];
}
public AnimalFactory GetInstance(Animal animal)
{
return registry[animal.GetType()];
}
public string Serialize(Animal animal)
{
return GetInstance(animal).Serialize(animal);
}
}
Run Code Online (Sandbox Code Playgroud)
基类:
public abstract class AnimalFactory
{
public abstract string SpeciesName { get; }
public abstract Type TypeCreated { get; }
public abstract Animal Create(AnimalParams args);
public abstract string Serialize(Animal animal);
}
public abstract class Animal
{
public abstract int Size { get; }
}
public abstract class AnimalParams { }
Run Code Online (Sandbox Code Playgroud)
具体实施:
象:
public class ElephantFactory : AnimalFactory
{
public override string SpeciesName => "Elephant";
public override Type TypeCreated => typeof(Elephant);
public override Animal Create(AnimalParams args)
{
if (args is ElephantParams e)
{
return new Elephant(e);
}
else
{
throw new Exception("Not elephant params");
}
}
public override string Serialize(Animal animal)
{
if (animal is Elephant elephant)
{
return $"Elephant({elephant.Weight})";
}
else
{
throw new Exception("Not an elephant");
}
}
}
public class Elephant : Animal
{
public int Weight;
public override int Size => Weight;
public Elephant(ElephantParams args)
{
Weight = args.Weight;
}
}
public class ElephantParams : AnimalParams
{
public readonly int Weight;
public ElephantParams(int weight) => Weight = weight;
}
Run Code Online (Sandbox Code Playgroud)
长颈鹿:
public class GiraffeFactory : AnimalFactory
{
public override string SpeciesName => "Giraffe";
public override Type TypeCreated => typeof(Giraffe);
public override Animal Create(AnimalParams args)
{
if (args is GiraffeParams g)
{
return new Giraffe(g);
}
else
{
throw new Exception("Not giraffe params");
}
}
public override string Serialize(Animal animal)
{
if (animal is Giraffe giraffe)
{
return $"Giraffe({giraffe.Height})";
}
else
{
throw new Exception("Not a giraffe");
}
}
}
public class Giraffe : Animal
{
public readonly int Height;
public override int Size => Height;
public Giraffe(GiraffeParams args)
{
Height = args.Height;
}
}
public class GiraffeParams : AnimalParams
{
public int Height;
public GiraffeParams(int height) => Height = height;
}
Run Code Online (Sandbox Code Playgroud)
如何
AnimalFactory以这样的方式编写基类,以确保在编译时只AnimalParams传递正确的类型,同时仍然允许其他人为其他动物编写自己的具体实现?
答案是双重的:
Params<T>,如Factory<T>,它返回T对象.首先,让我们来看看你的AnimalFactory.
public abstract class AnimalFactory
{
public abstract string SpeciesName { get; }
public abstract Type TypeCreated { get; }
public abstract Animal Create(AnimalParams args);
public abstract string Serialize(Animal animal);
}
Run Code Online (Sandbox Code Playgroud)
该Create方法是强类型的理想候选者args.但是,AnimalParams太粗糙,阻止编译器强制执行正确的类型.
该Serialize方法,在另一方面,是好的事情是这样的.我们不想缩小论证的范围.保持它的宽度Animal给我们最大的灵活性.
这些相互冲突的利益引发了一个问题 我们是否试图在抽象类的界面中进行过多的建模?不应该提供动物是工厂的唯一责任吗?让我们遵循接口隔离原则并将该Serialize方法分解出来.
重写AnimalFactory,澄清它的意图.
public abstract class Factory<T> where T : Animal
{
public abstract string SpeciesName { get; }
public abstract Type TypeCreated { get; }
public abstract T Create(Params<T> args);
}
public interface ISerialize
{
string Serialize(Animal animal);
}
public abstract class Animal
{
public abstract int Size { get; }
}
public abstract class Params<T> where T : Animal { }
Run Code Online (Sandbox Code Playgroud)
请注意从更改AnimalParams到Params<T> where T : Animal.这是提供类型安全的关键.
public class ElephantParams : Params<Elephant>
{
public readonly int Weight;
public ElephantParams(int weight) => Weight = weight;
}
Run Code Online (Sandbox Code Playgroud)
只Params<Elephant>允许一个后代,由演员强制执行(ElephantParams)p.
public class ElephantService : Factory<Elephant>, ISerialize
{
public override string SpeciesName => "Elephant";
public override Type TypeCreated => typeof(Elephant);
public override Elephant Create(Params<Elephant> p)
{
return new Elephant((ElephantParams)p);
}
public string Serialize(Animal animal)
{
if (animal is Elephant elephant)
{
return $"Elephant({elephant.Weight})";
}
else
{
throw new Exception("Not an elephant");
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以跳过此部分,但是,之前的示例有一些代码味道.
public override Elephant Create(Params<Elephant> p)
{
return new Elephant((ElephantParams)p);
}
Run Code Online (Sandbox Code Playgroud)
很难摆脱我们反过来做事的感觉.它从抽象基类开始.
public abstract class Animal
{
public abstract int Size { get; }
}
public abstract class Params<T> where T : Animal { }
Run Code Online (Sandbox Code Playgroud)
哪里不过Params<T>是标记界面.在里氏替换原则是基于一个事实,即一个接口应该定义多态行为,所有的情况下实现.因此,在功能始终存在的情况下,确保对这样的实例的每次调用都可以提供有意义的结果.
为了论证,让我们制作Animal标记界面(这也不是一个好主意).
public abstract class Animal { }
public abstract class Params<T> where T : Animal
{
public abstract int Size { get; }
}
Run Code Online (Sandbox Code Playgroud)
这反映在以下变化中.
public class Elephant : Animal
{
public int Weight;
public Elephant(Params<Elephant> args) => Weight = args.Size;
}
public class ElephantParams : Params<Elephant>
{
private readonly int weight;
public ElephantParams(int weight) => this.weight = weight;
public override int Size => weight;
}
Run Code Online (Sandbox Code Playgroud)
允许我们解决代码气味并遵守Liskov替代原则.
public override Elephant Create(Params<Elephant> p)
{
return new Elephant(p);
}
Run Code Online (Sandbox Code Playgroud)
可以肯定地说,这带来了相当大的改变,现在基类设计者必须抽象未来开发人员在Params<T>定义中可能需要的所有可能概念.如果没有,他们将被强制转换为方法中的特定类型,Create并优雅地处理类型不是预期类型的情况.否则,如果有人注入另一个派生类(T在基类中使用相同的类型参数Params<T>),应用程序可能仍会崩溃.
注册表类型:
Register生成服务,我们需要提供一个TService类型参数,它是一个具体的类(在我们的例子中是一个带有默认构造函数的类),例如ElephantService.TService : Factory<TAnimal>, ISerialize, new().Factory<TAnimal>用来表明我们的工厂类型,我们也需要指定TAnimal.该Serialize签名是和以前一样,再次,它不会作出太大的意义缩小现场和灵活性屈服.因为这需要我们Animal在序列化之前指定派生类型.
public class AnimalRegistry
{
Dictionary<Type, object> registry = new Dictionary<Type, object>();
public void Register<TService, TAnimal>()
where TService : Factory<TAnimal>, ISerialize, new()
where TAnimal : Animal
{
TService service = new TService();
registry[service.GetType()] = service;
registry[service.TypeCreated] = service;
}
public Factory<TAnimal> GetInstance<TAnimal>()
where TAnimal : Animal
{
return (Factory<TAnimal>)registry[typeof(TAnimal)];
}
public string Serialize(Animal animal)
{
return ((ISerialize)registry[animal.GetType()]).Serialize(animal);
}
}
Run Code Online (Sandbox Code Playgroud)
组合根仍然像以前一样,除了Register第二种类型的参数和添加的类型安全性.
AnimalRegistry registry = new AnimalRegistry();
registry.Register<ElephantService, Elephant>();
registry.Register<GiraffeService, Giraffe>();
Animal a1 = registry.GetInstance<Elephant>().Create(new ElephantParams(weight: 1500));
Animal a2 = registry.GetInstance<Giraffe>().Create(new GiraffeParams(height: 180));
//Doesn't compile
//Animal a3 = registry.GetInstance<Elephant>().Create(new GiraffeParams(height: 180));
registry.Serialize(a1);
registry.Serialize(a2);
Run Code Online (Sandbox Code Playgroud)
UPDATE
如果我想编写一个GetInstances()方法,它将返回注册表中的所有AnimalFactory实例,我将如何键入该方法?
您可以使用反射来过滤扩展的类型Factory<T>.
private bool IsFactory(Type type)
{
return
type.BaseType.IsGenericType &&
type.BaseType.GetGenericTypeDefinition() == typeof(Factory<>);
}
public List<object> GetInstances()
{
var factoryTypes = registry.Keys.Where(IsFactory);
return factoryTypes.Select(key => registry[key]).ToList();
}
Run Code Online (Sandbox Code Playgroud)
然而,
List<T>)只能包含相同类型的元素typeof(Factory<Elephant>) != typeof(Factory<Giraffe>) Factory<Animal>,参考泛型方差所以,List<object>可能不会证明有用.如建议的那样,您可以使用辅助接口或Factory<T>从摘要派生Factory.