我已将问题简化为一个涉及动物的例子。我想定义一组接口(/抽象类),允许任何人为给定动物创建工厂并向中央注册商注册: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);
但是,我对此确实不满意:
在编译时,没有什么可以阻止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);
}
}
基类:
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 { }
具体实现:
大象:
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;
}
长颈鹿:
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;
}
答案 0 :(得分:5)
如何编写基类
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);
}
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 { }
请注意从AnimalParams
到Params<T> where T : Animal
的更改。这是提供类型安全性的关键。
public class ElephantParams : Params<Elephant>
{
public readonly int Weight;
public ElephantParams(int weight) => Weight = weight;
}
只允许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");
}
}
}
您可以跳过此部分,但是,先前的示例具有某种代码味道。
public override Elephant Create(Params<Elephant> p)
{
return new Elephant((ElephantParams)p);
}
很难反驳我们正在做事的感觉。它从抽象基类开始。
public abstract class Animal
{
public abstract int Size { get; }
}
public abstract class Params<T> where T : Animal { }
Params<T>
仅是标记界面。 Liskov替换原理是基于以下事实:接口应定义所有实例都实现的多态行为。因此,在始终存在该功能的情况下,确保对此类实例的每次调用都可以提供有意义的结果。
为了争辩,让我们将Animal
设为标记界面(也不是一个好主意)。
public abstract class Animal { }
public abstract class Params<T> where T : Animal
{
public abstract int Size { get; }
}
这反映出以下变化。
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;
}
使我们能够解决代码异味并遵守 Liskov替换原则。
public override Elephant Create(Params<Elephant> p)
{
return new Elephant(p);
}
可以肯定地说,这带来了很大的改变,现在基类设计人员必须抽象出将来的开发人员在Params<T>
定义中可能需要的所有可能的概念。如果不是,它们将被强制转换为Create
方法中的特定类型,并妥善处理该类型不是预期类型的情况。否则,如果有人注入另一个派生类(在基类T
中具有相同类型参数Params<T>
),则该应用程序仍可能崩溃。
注册表类型:
Register
即时生成服务,我们需要提供一个TService
类型的参数,该参数是一个具体的类(在我们的示例中为默认构造函数),例如{{1} }。 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);
}
}
的第二个类型参数和增加的类型安全性之外,组成根仍然与以前非常相似。
Register
更新
如果我想编写一个GetInstances()方法来返回注册表中的所有AnimalFactory实例,我将如何键入该方法?
您可以使用反射来过滤扩展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);
的类型。
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();
}
)只能包含相同类型的元素List<T>
typeof(Factory<Elephant>) != typeof(Factory<Giraffe>)
,请参考generic variance 因此,Factory<Animal>
可能没有那么有用。如建议的那样,您可以使用辅助接口,也可以从抽象List<object>
派生Factory<T>
。