玩转类型转换和继承

3

注意:这个问题是用类似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;
 }

在下级类中,粗体字段是公开的。

类上没有可用的接口。

现在,我有一个想要传递自定义标签并设置粗体属性的方法。是否可以在不必要进行以下步骤的情况下完成: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 5 的旧版本)。我不知道它是否适用于该语言(我仍需要尝试一下),但我很想知道它是否适用于 C++、C# 或 Java。

如果不行,有什么其他方法可以实现吗?(请记住没有提供接口,也不能修改类。)

有人猜测吗?


你为什么想要这样做? - alternative
AngleLabelLabel不能共用Controlbold标志吗?为什么它们必须有自己的标志?(如果Control.bold与问题无关,为什么要显示它?) - sbi
@sbi - 对于混淆感到抱歉,我会删除控件引用。就像我所说的那样,这个例子来自Delphi。在Delphi中,控件类没有加粗属性。 - Vaccano
@~mathepic - 实际上,show方法的例子比仅仅设置粗体值要复杂得多(我进行了简化)。在更大的方法中,有很多冗余的代码行,用于设置AngeleLabel或者Label的相同值。我希望通过只在一个地方进行更改来减少维护过程中出错的风险。 - Vaccano
你引起了一些混淆,因为看起来你的派生类在声明它们自己的bold字段,而实际上你只是想说明它们仅改变了从祖先类继承的字段的访问方式。 - Rob Kennedy
在Java中,子类实际上是隐藏了超类的字段。 - Michael Myers
6个回答

7

这段代码可以在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 继承了共同的部分,所以它们中的所有属性都是相同的,即使该属性已经在后代中公开或发布:

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

您可以更改属性的可见性,但不能更改字段的可见性。如果您尝试对字段执行相同的操作,则会声明一个具有相同名称并隐藏继承字段的字段。
在C ++中可以执行类似的操作,但它不像Delphi那样常见,因此可能会引起一些不满,特别是如果您打算编写可移植代码。
像Delphi一样声明第四个类。C ++对成员访问不像Delphi那么松散,但它有友元的概念,在这种情况下同样适用。
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成员与任何其他CustomLabel后代的bold成员在相对位置上保持一致。


Delphi和C++代码中的类型转换执行类型截取。在C#或Java中您不能这样做;它们会检查其转换的结果,所以如果customLabel实际上并没有包含AccessCustomLabel的实例,您将会得到一个异常。在这些语言中,您必须使用反射来访问不相关类的受保护成员。演示超出了我的深度。


确实它们是属性,所以这应该很好用。我会试一试。谢谢。 - Vaccano

3
由于父类已被保护,所以除非在定制版本的后代中执行相关代码,否则它不应该在任何语言中正常工作,除非进行额外的工作。
Delphi有一个选项可以使用受保护的hack使其正常工作。C#和Java可以使用反射。C++友元可以用来使其正常工作。
但是,如果您在CustomLabel中声明Bold为公共属性,则该功能将在您指定的所有语言中正常工作,无需进行任何特殊操作。这包括Delphi、C++、C#和Java。

1

如果假设SetBold在你的示例中等同于Bold,那么C++可以通过模板来解决这个问题:

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

1
在给定的用例中,这并不真正起作用。在C++语法中,输入参数是CustomLabel* customLabel。模板确实进行编译时多态性,但要做到这一点,您需要在编译时知道实际类型,这意味着您无法避免检查每个已知的CustomLabel后代并访问类特定的Bold成员的代码。模板使您可以避免为每个单独的类实现SetBold,但它对分派没有帮助。 - Rob Kennedy

1
如果您正在使用 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);

1
在C# 3.0及更高版本中,您可以使用扩展方法;这些方法类似于Delphi类助手,正如Gerry所提到的那样。
它遵循这些线路(注意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);

--jeroen


0
你可以做的是在CustomLabel和另外两个类之间添加一个中间类(例如命名为CCC)。
public class CCC : CustomLabel

这个类应该有一个名为SetBold(bool bold)的函数,它将设置受保护的字段。
public void SetBold(bool bold)
{
    base.Bold = bold;
}

AngleLabel和Label都将继承CCC

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


唉,正如我在问题末尾所暗示的那样。我无法更改AngleLabel或Label类。 - Vaccano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接