【问题标题】:Upcasting helper method?向上转型辅助方法?
【发布时间】:2012-01-12 00:59:07
【问题描述】:

我有以下课程:

class Alpha
{
}
class Beta : Alpha
{
}
class Gamma : Beta
{
}
class Epsilon : Beta
{
}

我有几个方法可以将它们作为参数:

void Receiver(Gamma gamma);
void Receiver(Epsilon epsilon);
void Receiver(Beta beta);
void Receiver(Alpha alpha);

我在这里需要一些不寻常的东西。
我希望能够将 Alpha 对象传递给所有方法,但我不希望像 void Receiver(Beta beta); 这样的方法能够接收从 Beta 继承的类型的对象(也就是说,我想在最坏的情况下引发Exception 如果传递了 GammaEpsilon 对象。

我怎样才能以足够通用的方式最好地实现这一点?

我在考虑第一部分,我应该有一个允许向上转换的方法,例如

class Alpha
{
    public T Upcast<T>() where T : Alpha
    {
        // somehow return a T
    }
}

这里的问题是我希望它能够在所有级别上工作,例如我也希望能够“向上转换”Beta

第二部分应该更简单,因为我只会抛出超出我需要的类型的传递对象。

这样就足够了:

void Receiver(Epsilon epsilon)
{
    // if epsilon inherits from Epsilon, throw.
}

你能帮我解决我的问题的第一部分吗?谢谢!

【问题讨论】:

  • 如果您的方法接受Base 作为参数并且不能使用Derived,那么您的类设计是错误的。这就是你需要解决的问题。
  • 这是一种特殊情况,我使用反射将属性解析为查询字符串参数,我不想接受派生类,因为这些类可能包含其属性会导致非法查询字符串参数的属性相关的 API 调用。
  • 我同意你的观点,虽然也许一切都应该是sealed,但对我来说重要的是向上转换的方法。
  • 然后只需编写代码,根据白名单过滤参数/值属性,该白名单取决于您打算调用的 API,并将其作为“使用这些参数调用 API”方法的一部分运行。
  • 如果发现参数不在白名单中,则抛出异常。无论如何你都会扔一个,这个解决方案也涵盖了你的情况。将属性添加到 Beta 并忘记您的查询会在它上面出错。我的观点是,您正在关注错误的方法,因此我让这个主题休息一下。 :)

标签: c# casting


【解决方案1】:

您似乎在处理可以处理 给定 类型的代码,但不能相信它可以正确处理子类型。一般来说,这违反了许多 OOP 原则,其中之一是 LSP,或Liskov Substitution Principle

您可能处于可以将visitor pattern 视为解决此问题的方法的地方。它与您已有的非常接近,稍作修改,这很好!当你的代码中出现模式而不是被强加于它时,这很好。

您的void Receiver 方法本质上已经是访问者实现,所以让我们重命名它们并创建一个接口。

interface IVisitor
{
    void Visit(Gamma gamma);
    void Visit(Epsilon epsilon);
    void Visit(Beta beta);
    void Visit(Alpha alpha);
}

处理每种元素类型的类只需要实现该接口。

class Visitor : IVisitor
{
    public void Visit(Gamma gamma) { Console.WriteLine("Visiting Gamma object"); }
    public void Visit(Epsilon epsilon) { Console.WriteLine("Visiting Epsilon object"); }
    public void Visit(Beta beta) { Console.WriteLine("Visiting Beta object"); }
    public void Visit(Alpha alpha) { Console.WriteLine("Visiting Alpha object"); }
}

然后让每个元素类型也实现一个简单通用的接口

interface IElement { void Accept(IVisitor visitor); }

class Alpha : IElement 
{
    public virtual void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
} 

class Beta : Alpha 
{
    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// etc.  

(严格来说,接口不是必需的,只是很好的形式。)

然后就是这样,一种双重调度方法,其中元素调用访问者方法特定于它自己的类型,即使你可能通过基类型引用它。

Alpha alpha = new Beta();
IVisitor visitor = new Visitor();
alpha.Accept(visitor);

如果您已正确实施,您应该会在屏幕上看到“正在访问 Beta 对象”(或者,用您的话来说,您应该看到执行了所需的“接收者”行为。)


您可能会考虑的另一个选择是简单地将Receiver 设置为基类上的虚拟方法,并让派生类覆盖它,并在类中包含行为,如果这对您的层次结构有意义的话。如果行为需要是外部的,您还可以考虑一种工厂方法,该方法知道如何为给定的类创建特定的处理器。如果将子类型传递给期望基类的方法,则有许多选项不会让您的程序发疯。


我在这里需要一些不寻常的东西。我希望能够通过 所有方法的 Alpha 对象,但我不想要像 void 这样的方法 接收器(Beta beta);能够接收类型的对象 从 Beta 继承(也就是说,我想在最坏的情况下引发异常,如果 传递了 Gamma 或 Epsilon 对象。

这确实很奇怪。我不确定你真的想要那个。您似乎将继承层次结构颠倒过来了,其中超类型可以替换派生类型,而不是相反,您应该真的重新考虑您正在尝试做什么。

【讨论】:

    【解决方案2】:

    您可以执行 GetType 并检查其是否为 beta。

    【讨论】:

    • 我想向上转型,不确定它是派生类型。
    • 从 epsilon 制作新的测试版?
    • Beta 新建一个Epsilon
    【解决方案3】:

    我希望能够将 Alpha 对象传递给所有方法

    您无法可靠地做到这一点。 Alpha 不是 Gamma,因此您不能可靠地将其用作 Receiver(Gamma) 的参数。否则你可以这样做:

    Alpha a = new Gamma(); // legal
    Receiver((Epsilon)a);    // not legal
    

    如果您像在问题中那样定义 Receiver 函数,编译器将根据声明的类型(或者如果您转换它,则为转换类型)选择正确的方法:

    Epsilon e = new Epsilon();
    Alpha a = new Gamma();
    
    Receiver(e);         // calls Receiver(Epsilon)
    Receiver(a);         // calls Receiver(Alpha)
    Receiver((Gamma)a);  // calls Receiver(Gamma)
    Receiver((Beta)e);   // calls Receiver(Beta)
    

    所以如果你调用得当,编译器会选择正确的方法。

    但我不希望像 void Receiver(Beta beta); 这样的方法能够接收从 Beta 继承的类型的对象

    那么你没有正确使用继承。 Gamma 是一个 Beta。为什么要阻止继承类型使用该方法?如果您希望 Gamma 和 Beta 在不继承的情况下共享某些属性/方法,请选择另一种模式,例如 EncapsulationComposition 并使用接口。

    也就是说,你的 Upcast 函数应该很简单:

    public T Upcast<T>() where T : Alpha
    {
        return this as T;  // will return null is this is not a T
    }
    
    ...
    
    new Beta().Upcast<Gamma>();  // will return null
    new Epsilon().Upcate<Epsilon>();  // will return the Epsilon
    

    【讨论】:

      猜你喜欢
      • 2015-03-27
      • 1970-01-01
      • 2020-01-27
      • 2023-03-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多