将泛型与可扩展工厂一起使用



我把我的问题简化为一个涉及动物的例子。 我想定义一组接口(/abstract 类),允许任何人为给定的动物创建一个工厂,并将其注册到中央注册器: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() 方法的约定。
  • AnimalParams和相应工厂之间AnimalRegistry添加额外的映射,并在注册表中定义一个新的Create()方法,但这并不是一个优雅的解决方案,因为问题只是移动而不是解决。

我怀疑答案在于更多的类型泛型,但它目前逃脱了我。

动物登记处:

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;
}

我怎样才能以这样一种方式编写基类AnimalFactory,以确保在编译时只能传递正确类型的AnimalParams,同时仍然允许其他人为其他动物编写自己的具体实现?

答案是双重的:

  1. Params<T>中引入与Factory<T>中相同的泛型类型参数,这将返回T对象。
  2. 要真正遵守Liskov 替换原则,您需要进一步重构基类。

  1. 泛 型

首先,让我们来看看你的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 { }

请注意从AnimalParamsParams<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");
}
}
}

  1. Liskov

您可以跳过此部分,但是,前面的示例具有某种代码异味。

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方法中的特定类型,并正常处理该类型不是预期类型的情况。否则,如果有人注入另一个派生类(在基类Params<T>中注入了相同的类型参数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);
}
}

组合根仍然和以前很相似,除了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);

更新

如果我想编写一个返回注册表中所有 AnimalFactory 实例的 GetInstances() 方法,我将如何键入该方法?

可以使用反射来筛选扩展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();
}

然而

  1. 泛型集合(List<T>)只能包含相同类型的元素
  2. typeof(Factory<Elephant>) != typeof(Factory<Giraffe>)
  3. 你不能投射到Factory<Animal>,引用泛型方差

因此,List<object>可能证明没有用。按照建议,您可以使用辅助接口或从抽象Factory派生Factory<T>

最新更新