【问题标题】:Fun with casting and inheritance铸造和继承的乐趣
【发布时间】:2010-05-25 21:19:59
【问题描述】:

注意:这个问题是用类似 C# 的伪代码编写的,但我真的要问哪些语言有解决方案。请不要拘泥于语法。

假设我有两个班级:

 class AngleLabel: CustomLabel
 {
     public bool Bold;  // Just upping the visibility to public
     // code to allow the label to be on an angle
 }

 class Label: CustomLabel
 {
     public bool Bold;  // Just upping the visibility to public
     // Code for a normal label
     // Maybe has code not in an AngleLabel (align for example).
 }

他们都来自这个班级:

 class CustomLabel
 {
     protected bool Bold;
 }

粗体字段在后代类中公开。

类上没有可用的接口。

现在,我有一个方法,我希望能够传入一个 CustomLabel 并设置 Bold 属性。是否可以做到这一点而不必 1) 找出对象的真实类是什么,2) 转换为该对象,然后 3) 为每个标签类型的每个变量制作单独的代码以设置粗体。有点像这样:

 public void SetBold(customLabel: CustomLabel)
 {
     AngleLabel angleLabel;
     NormalLabel normalLabel;


     if (angleLabel is AngleLabel )
     {
        angleLabel= customLabel as AngleLabel 
        angleLabel.Bold = true;
     }

     if (label is Label)
     {
        normalLabel = customLabel as Label
        normalLabel .Bold = true;
     }
 }

最好进行一次强制转换,然后在一个变量上设置粗体。

我想的是创建第四个类,它只公开粗体变量并将我的自定义标签投射到该类。

这行得通吗?

如果是这样,它适用于哪些语言? (这个例子取自旧版本的 Delphi (Delphi 5))。我不知道它是否适用于那种语言,(我仍然需要尝试一下)但我很好奇它是否适用于 C++、C# 或 Java。

如果没有,有什么想法可行吗? (记住没有提供接口,我不能修改类。)

有人猜到了吗?

【问题讨论】:

  • 你为什么要这样做?
  • 不能AngleLabelLabelControlbold 标志生活?为什么他们必须有自己的? (如果Control.bold 与问题无关,为什么要显示它?)
  • @sbi - 很抱歉造成混淆,我将删除控件引用。正如我所说,这个例子来自 Delphi。 Delphi 中的控件类没有粗体属性。
  • @~mathepic - 示例方法 show 实际上比仅设置粗体值更大。 (我简化了它。)在较大的方法中,我有许多设置相同值的冗余行,但对于 AngeleLabel 或 Label。我想通过只在一个地方进行更改来降低维护搞砸的风险。
  • 您造成了一些混乱,因为看起来您的后代类正在声明 他们自己的 bold 字段,而实际上您只是想说明它们只是在改变对继承自祖先类的字段的访问。

标签: c# c++ delphi inheritance casting


【解决方案1】:

它可以在 Delphi 中工作。与它使用的类在同一单元中的代码可以隐式访问受保护的(但不是严格保护的)成员,即使是在另一个单元中声明的那些成员。您在CustomLabel 中声明了受保护的属性:

type
  CustomLabel = class
  private
    FBold: Boolean;
  protected
    property Bold: Boolean read FBold write FBold;
  end;

粗体设置程序,在另一个单元中,将有自己的CustomLabel后代:

type
  TAccessCustomLabel = class(CustomLabel);

procedure SetBold(customLabel: CustomLabel)
begin
  TAccessCustomLabel(customLabel).Bold := True;
end;

您不能对其使用as 强制转换,因为实际参数永远不会是TAccessLabel 的实例。它将是AngleLabelNormalLabel 的一个实例,但是由于所有三个类从CustomLabel 继承的部分都是共同的,所以Bold 属性在所有这些类中都是相同的。即使在财产已被公开或在后代中发布后,这仍然是正确的:

type
  AngleLabel = class(CustomLabel)
  public
    property Bold;
  end;

您可以更改属性的可见性,但不能更改字段。如果您对字段尝试相同的操作,您将声明一个具有相同名称的 new 字段,该字段隐藏了继承的字段。


您可以在 C++ 中执行类似的操作,但它不像在 Delphi 中那样普遍,因此可能会引起一些愤怒,特别是如果您打算编写可移植代码。

声明第四个类,就像在 Delphi 中一样。 C++ 在成员访问方面不像 Delphi 那样松散,但它具有 friendship 的概念,在这种情况下同样适用。

class AccessCustomLabel: public CustomLabel
{
  friend void SetLabel(CustomLabel* customLabel);
};

该函数现在可以完全访问该类的成员:

void SetLabel(CustomLabel* customLabel)
{
  // Not allowed:
  // customLabel->bold = true

  // Not ordinarily allowed; requires friendship
  reinterpret_cast<AccessCustomLabel*>(customLabel)->bold = true;
}

这在技术上是未定义的行为,因为我们已经将一个对象类型转换为它实际上并不具有的类型。我们依赖CustomLabel 的所有后代拥有相同的布局,特别是AccessCustomLabelbold 成员与任何其他bold 成员的相对位置相同CustomLabel 后代.


Delphi 和 C++ 代码中的类型转换执行类型双关。你不会在 C# 或 Java 中摆脱它。他们检查他们的演员阵容的结果,所以如果customLabel 没有真正拥有AccessCustomLabel 的实例,你会得到一个例外。您必须使用反射来访问这些语言中不相关类的受保护成员。证明这超出了我的理解范围。

【讨论】:

  • 确实它们是属性,所以这应该很好用。我会试一试。谢谢。
【解决方案2】:

由于父级受到保护,答案是它不应该在没有额外工作的情况下以任何语言工作,除非相关代码是在自定义版本的后代中执行的。

Delphi 可以选择使用protected hack 来使其工作。 C# 和 Java 可以使用反射。可以使用 C++ 朋友来使其工作。

但是,如果您在 CustomLabel 中将 Bold 声明为 Public,那么该功能将适用于您指定的所有语言。 Delphi、C++、C# 和 Java,无需做任何特别的事情。

【讨论】:

    【解决方案3】:

    C++ 将使用模板解决此问题(假设 SetBold 相当于您的示例中的 Bold):

    template<typename T> void SetBold(T t) {
        t.SetBold();
    }
    

    【讨论】:

    • 这在给定的用例中实际上不起作用。在 C++ 语法中,输入参数是CustomLabel* customLabel。模板具有编译时多态性,但要做到这一点,您需要在编译时知道实际类型,这意味着您无法逃避检查CustomLabel 的每个已知后代并访问特定于类的Bold 的代码成员。模板可以让你避免为每个单独的类实现 SetBold,但它对调度没有帮助。
    【解决方案4】:

    如果您使用的是 Delphi 2005 或更高版本,则可以使用类助手。类助手可以访问“帮助”类的受保护字段和方法。 C# 扩展方法可能会出现类似的情况,但这不是我熟悉的领域。

    注意:我无法对此进行测试,因为我手头没有编译器。

    type
       TLabelHelper = class helper for CustomLabel
       public 
         procedure SetBolded(ABold : Boolean);
       end;
    
    procedure TLabelHelper.SetBolded(ABold : Boolean);
    begin
      Bold := ABold;
    end;
    

    ...

    Label.SetBolded(True);
    

    【讨论】:

      【解决方案5】:

      在 C# 3.0 及更高版本中,您可以使用extension methods;这些类似于Gerry 提到的Delphi Class Helpers

      它遵循这些路线(注意 this 关键字)。

      public static class CustomLabelExtensions // name here is not important, just make it readable
      {
          public static void SetBolded(this CustomLabel customLabel, bool newValue)
          {
              customLabel.Bold = newValue;
          }
      }   
      

      请注意,我像您在伪代码中所做的那样省略了命名空间。
      但是,请确保您的代码可以看到 CustomLabelExtensions,方法是使用其命名空间或显式指定该命名空间。

      另请注意,扩展方法只允许您添加方法,而不是属性(来自 Delphi 背景,这对我来说很奇怪)。

      然后你可以像这样使用上面的代码:

      AngleLabel angleLabel;
      NormalLabel normalLabel;
      // some code that assigns values to the variables
      angleLabel.SetBolded(true);
      normalLabel.SetBolded(true);
      

      --杰罗恩

      【讨论】:

        【解决方案6】:

        您可以做的是在 CustomLabel 和其他两个类之间添加一个中间类(例如命名为 CCC)。

        public class CCC : CustomLabel
        

        这个类应该有一个名为 SetBold(bool bold) 的函数来设置受保护的字段。

        public void SetBold(bool bold)
        {
            base.Bold = bold;
        }
        

        AngleLabel 和 Label 都将继承 CCC

        SetBold(...) 的参数将是 CCC 类型

        【讨论】:

        • 唉,正如我在问题结束时提到的那样。我无法更改 AngleLabel 或 Label 类。
        猜你喜欢
        • 1970-01-01
        • 2011-08-01
        • 2021-06-14
        • 2013-01-25
        • 1970-01-01
        • 2018-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多