使用具有可扩展工厂的泛型?

Ben*_*jin 8 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);
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)

ing*_*gen 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);
}
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)

请注意从更改AnimalParamsParams<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)
  1. 里氏

您可以跳过此部分,但是,之前的示例有一些代码味道.

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)

然而,

  1. 泛型collection(List<T>)只能包含相同类型的元素
  2. typeof(Factory<Elephant>) != typeof(Factory<Giraffe>)
  3. 你不能转换Factory<Animal>,参考泛型方差

所以,List<object>可能不会证明有用.如建议的那样,您可以使用辅助接口或Factory<T>从摘要派生Factory.