我把我的问题简化为一个涉及动物的例子。 我想定义一组接口(/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
,同时仍然允许其他人为其他动物编写自己的具体实现?
答案是双重的:
- 在
Params<T>
中引入与Factory<T>
中相同的泛型类型参数,这将返回T
对象。 - 要真正遵守Liskov 替换原则,您需要进一步重构基类。
- 泛 型
首先,让我们来看看你的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");
}
}
}
- 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();
}
然而
- 泛型集合(
List<T>
)只能包含相同类型的元素 typeof(Factory<Elephant>) != typeof(Factory<Giraffe>)
- 你不能投射到
Factory<Animal>
,引用泛型方差
因此,List<object>
可能证明没有用。按照建议,您可以使用辅助接口或从抽象Factory
派生Factory<T>
。