gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 19:40:26 26 4
gpt4 key购买 nike

我已经将我的问题简化为一个涉及动物的例子。我想定义一组接口(interface)(/抽象类),允许任何人为给定的动物创建工厂并将其注册到中央注册商: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()方法,但这并不是一个真正优雅的解决方案,因为问题只是移动而不是解决。

  • 我怀疑答案在于更多类型的泛型,但它目前让我无法理解。

    动物登记:
    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;
    }

    最佳答案

    How can I write the base class AnimalFactory in such a way that ensures at compile-time that only the correct type of AnimalParams can be passed, while still allowing others to write their own concrete implementations for other animals?



    答案是双重的:
  • 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 一样宽给了我们最大的灵活性。

    这些相互冲突的利益引发了一个问题。我们是否试图在抽象类的接口(interface)中建模太多?提供动物不应该是工厂的唯一责任吗?让我们遵循接口(interface)隔离原则并分解出 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>的唯一一个后代允许,由 Actor 强制执行 (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 替换原则基于这样一个事实,即接口(interface)应该定义所有实例都实现的多态行为。因此,在功能始终存在的情况下,确保对此类实例的每次调用都可以提供有意义的结果。

    为了便于论证,让我们制作 Animal标记界面(这也不是一个好主意)。
    public abstract class Animal { }

    public abstract class Params<T> where T : Animal
    {
    public abstract int Size { get; }
    }

    这反射(reflect)在以下变化中。
    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> 中抽象 future 开发人员可能需要的所有可能的概念。定义。如果没有,它们将被迫强制转换为 Create 中的特定类型。方法并优雅地处理类型不是预期类型的​​情况。否则,如果有人注入(inject)另一个派生类(在基类 T 中具有相同的类型参数 Params<T>),应用程序可能仍会崩溃。

    注册表类型:
  • 给定 Register动态生成服务,我们需要提供 TService类型参数是一个具体的类(在我们的例子中是一个带有默认构造函数的类),例如 ElephantService .
  • 然而,为了保持它的多态性,我们将使用 TService : Factory<TAnimal>, ISerialize, new() 对其进行抽象。 .
  • 因为我们正在使用 Factory<TAnimal>要表明我们的工厂类型,我们需要指定 TAnimal以及。
  • 当我们检索服务时,我们将通过引用所需的接口(interface)而不是具体类来实现。
  • 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);

    更新

    If I wanted to write a GetInstances() method that would return all AnimalFactory instances in the registry, how would I go about typing that method?



    您可以使用反射来过滤扩展 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> , 引用 generic variance

  • 所以, List<object>可能不会证明有用。按照建议,您可以使用辅助接口(interface)或派生 Factory<T>来自摘要 Factory .

    关于c# - 在可扩展工厂中使用泛型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51941091/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com