【问题标题】:Generic way of constructing objects构造对象的一般方法
【发布时间】:2021-12-15 14:05:06
【问题描述】:

我有两个班级:

class DoorRequest : IRequest
{
   string message;
}

class WindowRequest : IRequest
{
  string message;
}

它们不相互继承,而是实现一个通用接口。 我想有一个通用的方法来创建这些类的实例。语义上等同于:

public static T MakeOpenRequest<T>() where T : IRequest
{
  // If T is DoorRequest return new DoorRequest{message="please open the door"}
  // If T is WindowRequest return new WindowRequest{message="please open the window"}
}

对我来说重要的是函数的结果是T,而不是IRequest。我可以扩展DoorRequestWindowRequest 的功能来提供实际的实现。 MakeOpenRequest 的实际逻辑可以想象得要复杂得多,因此编写一个普通的工厂会导致大量代码重复。该方法唯一关心的是它应该以命名方式创建某些类型的实例,即为每个类单独定义。

我怀疑常规构造函数在这里会有所帮助,因为几乎没有任何通用性可以利用。构造方法必须是静态的,所以我不能指望它们的多态性。

完成这项任务的最佳方法是什么?反射会作弊,但如果没有其他办法,那也没关系。

【问题讨论】:

  • 您可以使用Dictionary&lt;Type, Func&lt;IRequest&gt;&gt; 集合来封装构造。这些Funcs 是如何产生的则是另一回事;您可以静态初始化这些或使用反射。
  • 为什么界面上没有SetOpenMessage()之类的方法?然后你可以只要求T 有一个无参数的构造函数,调用那个方法,然后返回对象。
  • 如果我写自己的请求class FooRequest: IRequest { int foo; }怎么办? MakeOpenRequest&lt;FooRequest&gt;() 会做什么?
  • 一旦你输入if (T is something) {},你应该停下来,因为你做错了。
  • 要利用Polymorphism,您应该能够针对IRequest 进行编程。例如,不要在 DoorRequest 上调用 OpenDoor(),在 WindowRequest 上调用 OpenWindow(),您应该在 IRequest 上调用 Open()(或者更通用的 Activate())(否则您将需要执行 @ 987654342@@BanksySan 指出的事情)。

标签: c# generics


【解决方案1】:

TL;DR
你真的应该考虑一个不依赖泛型的解决方案;因为您的预期代码对于该泛型类型的所有混凝土的行为方式不同。这是泛型不是正确方法的第一个重大危险信号。


实例化泛型类型的唯一方法是所选类型具有无参数构造函数。这也需要您将其指定为泛型类型约束。

由于您希望您的代码与所有可能的类型无关,因此您的方法实际上无法确定要使用的正确消息(因为这需要知道特定类型,这会导致违反 OCP)。因此,应该在具体的IRequest 实现本身中定义消息。

但是,这会让您的方法本身除了调用构造函数外无事可做:

interface IRequest { }

class DoorRequest : IRequest
{
    public string Message { get; }

    public DoorRequest()
    {
        Message = "Please open the door";
    }
}

class WindowRequest : IRequest
{
    public string Message { get; }

    public WindowRequest()
    {
        Message = "Please open the window";
    }
}

static class Foo
{
    public static T MakeOpenRequest<T>() where T : IRequest, new()
    {
        var obj = new T();

        return obj;
    }
}

如果您需要在MakeOpenRequest 函数而不是IRequest 实现本身中决定消息,并且消息特定于正在使用的T 类型,那么唯一可能的解决方案是@987654327 @ 根据特定类型更改其行为,这对于泛型来说不是一个好的用例。这表明设计不佳,违反了 OCP。

在这种情况下,您应该寻找一种替代方法,因为泛型不是可行的方法。最有可能的是,您最好选择特定方法(MakeDoorRequestMakeWindowRequest),因为消费者是否必须调用 MakeOpenRequest&lt;WindowRequest&gt;MakeWindowRequest 并不重要,因为两者都需要具体类型的相同知识。

您在问题中提到事情可能会变得更加复杂。此时,您应该考虑使用factory pattern,而不是尝试对泛型做同样的事情。

【讨论】:

  • 如果我也有MakeCloseRequest 和其他类似的东西,它会如何工作?
  • @Radrow 我无法仅根据名称来回答这个问题,但建议可能是相同的。
  • 我的意思是您使用无参数构造函数来创建打开请求。因此,您不能将其用于任何其他人。
  • @radrow:你真的应该考虑一个不依赖泛型的解决方案;因为对于该泛型类型的所有混凝土,您的预期代码的行为方式不同。这是泛型不是正确方法的第一个重大危险信号。
  • 知道了。您可以将您的最后一条评论添加到答案中,因为这正是我的方法错误的地方。
【解决方案2】:

你想错了。您需要一个根据传递的参数创建 IRequest 对象的工厂。

例子:

enum RequestTypes {
   DoorRequest,
   WindowRequest
}

class RequestFactory {
    public IRequest Create(RequestType requestType) {
        switch(requestType) {
            case RequestType.DoorRequest:
                return new DoorRequest();
            case RequestType.WindowRequest:
                return new WindowRequest();
            default:
                throw new ArgumentException("requestType");
        }
    }
}

不需要泛型,只需要老式的多态性。

这假设IRequest 是正确的。应该是:

interface IRequest {
    string message;
}

从不需要将其转换为显式的DoorRequest,您只需使用返回的IRequest

【讨论】:

  • 这让我无法写DoorRequest r = Create(...)(模动态转换)
  • 是的,因为那是错误的。您需要DoorRequestIRequestDoorRequest 吗?我强烈怀疑是后者。
  • string message 应该在IRequest 中。
  • 我特别需要DoorRequest(或WindowRequest),因为我在调用创建代码的地方依赖它。
  • 那代码是错误的。该代码应该接受IRequest,而不是具体类型。
猜你喜欢
  • 2018-04-29
  • 2018-06-23
  • 2016-05-05
  • 1970-01-01
  • 1970-01-01
  • 2014-10-08
  • 2017-01-31
  • 2018-03-03
  • 1970-01-01
相关资源
最近更新 更多