在可扩展工厂中使用泛型?

时间:2018-08-21 03:15:42

标签: c# generics

我已将问题简化为一个涉及动物的例子。我想定义一组接口(/抽象类),允许任何人为给定动物创建工厂并向中央注册商注册: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;
}

1 个答案:

答案 0 :(得分:5)

  

如何编写基类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. 利斯科夫

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

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>

但是

  1. 通用集合(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(); } )只能包含相同类型的元素
  2. List<T>
  3. 您不能投射到typeof(Factory<Elephant>) != typeof(Factory<Giraffe>),请参考generic variance

因此,Factory<Animal>可能没有那么有用。如建议的那样,您可以使用辅助接口,也可以从抽象List<object>派生Factory<T>