【问题标题】:Object inheritance involving enumerated types涉及枚举类型的对象继承
【发布时间】:2013-02-01 14:23:42
【问题描述】:

Delphi 2007,明年转移到 Delphi XE。

我们的产品广泛使用第三方组件。我们不直接使用组件,而是使用它的自定义后代,我们添加了很多额外的行为(自定义后代组件是几年前由已经退休的开发人员开发的)。

在第三方Parent类的源单元中,声明了一些枚举类型,控制组件的各种操作:

TSpecialKind = (skAlpha, skBeta, skGamma);
TSpecialKinds = set of TSpecialKind;

在我们的后代类中,我们想要添加新的行为,这需要扩展枚举类型的选择。本质上,我们想要这样:

TSpecialKind = (skAlpha, skBeta, skGamma, skDelta, skEpsilon);
TSpecialKinds = set of TSpecialKind;

显然,我们希望避免编辑第三方代码。简单地重新声明枚举类型,重复原始值并添加我们的新值,在我们自己的后代单元中是否有效?对现有代码有影响吗?

编辑: 示例场景(希望)澄清。假设您有一个(父)组件用于订购车辆零件。父单元有一个枚举类型 Tvkind 用于车辆种类,定义了值 vkCar 和 vkCycle。除其他外,这些值用于指示车辆有多少个轮子,4 个或 2 个。

现在,在您的后代组件中,您也希望能够处理三轮车辆。扩展 Tvkind 枚举类型以包含新值 vkTrike 似乎是显而易见的方法。但是如果您无权访问或不想修改父组件代码怎么办?

【问题讨论】:

  • 你打算用这些新类型做什么。您不能在原始组件中使用它们。
  • @David 新类型只会在我们的后代组件中使用。有很多例程(在父类和子类中)需要 TSpecialKind 类型的值。在我们的组件中,我们想要添加建议扩展 TSpecialKind 值的功能。我将在帖子中添加一个示例场景。
  • 所以假设你添加vkTrike。您将什么值传递给原始组件?您唯一的选择是vkCarvkCycle。我认为你提出的设计根本行不通。
  • 我们从未真正使用过原始组件。我们只使用我们自己的后代。我的直觉是,这行不通,您和 Cosmin 的回答支持这一点。幸运的是,我确实有父源,所以我可能会接受简单地添加它的非最佳方法。我只需要记住每次我们获得更新的组件时都要重新修改。
  • 您肯定使用的是原始组件。您创建了一个派生类。确保原始组件的代码将运行。

标签: delphi delphi-2007


【解决方案1】:

枚举类型的继承与类的工作方式不同,因为代码对枚举做出了它永远不会对类做出的假设。例如,给定您的原始枚举(TSpecialKind),第三方组件可能包含如下代码:

var Something: TSpecialKind;
[...]
case Something of
  skAlpha: ;
  skBeta: ;
  skGamma: ;
end;

即使您可以将不属于该枚举的内容强制转换为 TSpecialKind 类型,上述代码的结果也将是 undefined(而且肯定不好!)

枚举可能以另一种方式使用,如果第三方组件仅以这种方式使用它,那么您也许可以做一些“巫术”,但我不推荐它。如果原来的TSpecialKind只通过它的TSpecialKindsset类型使用,那么就只能这样使用:

if skBeta in VarOfTypeSpecialKinds then
begin
  ...
end;

(续)然后您可以引入一种新类型,它以相同的顺序枚举所有原始值,具有相同的值。如果在你这样做之后SizeOf(TSpecialKind) 等于SizeOf(TNewType),那么你可以将新的 set 值硬转换为旧值,旧代码也可以正常工作。但坦率地说,这是 hacky,在许多条件下它才能正常工作,太脆弱了。更好的解决方案是使用仅在后代组件中使用的新枚举类型:

type TExtraSpecialKind = (skDelta, skEpsilon);
     TExtraSpecialKinds = set of TExtraSpecialKind;

您可能会将此集发布在不同的属性中;该解决方案是干净的,将与后代代码很好地混合,并且也可以干净地使用。示例:

if (skAlpha in SpecialKind) or (skDelta in ExtraSpecialKind) then
begin
  // Do extra-sepcial mixed stuff here.
end;

【讨论】:

    【解决方案2】:

    我不相信您可以合理地期望在不修改原始组件的情况下进行所需的更改。

    让我们以您的车辆类型为例进行更深入的研究。我希望原始组件会有这样的代码:

    case Kind of
    vkCar:
      CarMethod;
    vkCycle:
      CycleMethod;
    end;
    

    现在,假设你引入了一个带有额外枚举的枚举类型

    TExtendedVehicleKind = (vkCar, vkCycle, vkTrike);
    

    如果上面的case语句运行,ExtendedKind等于vkTrike,则不会调用任何方法。

    现在,当ExtendedKindvkTrike 时,也许可以通过将Kind 设置为vkCarvkCycle 来实现您希望从原始控件获得的行为。但这对我来说似乎不太可能。只有你才能确定,因为只有你有代码,并且知道你的实际问题是什么。

    【讨论】:

    • “不会调用任何方法”,如您的示例所示,可能还不错。如果为vkCar 创建一个TCarOperator,为vkCycle 创建一个TCycleOperator,然后调用TVehicleOperator.StartEngine 方法会怎样?它将为vkTrike 反病毒,因为没有创建TVehicleOperator,并且代码没有理由检查它是否创建了它。
    • @CosminPrund 通过什么都不做而不是做一些需要的事情来避免 AV 几乎不能解决问题。我总是选择 AV 而不是静默失败。
    • 我知道。我希望 OP 通过提出更直接的问题来更好地了解情况。
    • 谢谢。这基本上是我所期望的答案——不能或不应该在后代单元中“扩展”枚举类型。我们决定采用完全不同的方法。
    【解决方案3】:

    一直在“需要扩展一个属性的枚举类型”。

    快速的第一建议。将您的枚举添加为现有属性的新属性包装器:


    潜在的父类代码:


    unit AcmeMachines;
    
    interface
    
    type
       FoodCanEnum =
       (
         None,
         Fish,
         Bird,
         Beef
       );
    
       AcmeCanAutoOpenMachineClass= class (object)
       protected
       { protected declarations }
    
          F_FoodCanEnum: FoodCanEnum;
    
          function getFoodEnumProperty: FoodCanEnum;
          procedure setFoodEnumProperty(const AValue: FoodCanEnum);
       public
       { public declarations }
    
          property FoodEnumProperty
            read getFoodEnumProperty write setFoodEnumProperty;
       end;
    
    implementation
    
       function AcmeCanAutoOpenMachineClass.getMyFoodEnumProperty: FoodCanEnum;
       begin        
         Result := F_FoodCanEnum;
       end;
    
       procedure AcmeCanAutoOpenMachineClass.setMyFoodEnumProperty
         (const AValue: CatFoodCanEnum);
       begin    
         FoodEnumProperty:= AValue;
    
         // do some specific business logic
       end;
    
    end;
    

    后代类代码:


    unit UmbrellaMachines;
    
    interface
    uses AcmeMachines;
    
    type
       CatFoodCanEnum =
       (
         None, <--- matches "AcmeMachines.None"
         Fish, <--- matches "AcmeMachines.Fish"
         Bird, <--- matches "AcmeMachines.Bird"
         Beef, <--- matches "AcmeMachines.Beef"
         Tuna,
         Chicken
       );
    
       UmbrellaCanAutoOpenMachineClass = class (AcmeCanAutoOpenMachineClass)
       protected
       { protected declarations }
    
          F_CatFoodCanEnum: CatFoodCanEnum;
    
          function getMyFoodEnumProperty: CatFoodCanEnum;
          procedure setMyFoodEnumProperty(const AValue: CatFoodCanEnum);
       public
       { public declarations }
    
          // new property, "wraps" existing property
          property MyFoodEnumProperty
            read getMyFoodEnumProperty write setMyFoodEnumProperty;
       end;
    
    implementation
    
       function UmbrellaCanAutoOpenMachineClass.getMyFoodEnumProperty: CatFoodCanEnum;
       begin
         // wrap existing "FoodEnumProperty" property, using an existing value as dummy
    
         Result := F_CatFoodCanEnum;
       end;
    
       procedure UmbrellaCanAutoOpenMachineClass.setMyFoodEnumProperty
         (const AValue: CatFoodCanEnum);
       begin
         // wrap existing property, using an existing value as dummy
         // may be another value if necessary
         AcmeCanAutoOpenMachineClass.ExistingFoodEnumProperty := AcmeMachines.None;
    
         F_CatFoodCanEnum := AValue;
         // add extended business logic for this class instances
       end;
    
    end;
    
    • 额外。

    如果可能,请始终将“null”或“dummy”值添加到您自己的枚举中,通常是第一个值:

     type
       CatFoodCanEnum =
       (
         None, // <--- these one
         Tuna,
         Chicken,
         Beef
       );
    

    干杯。

    【讨论】:

    • 你在那里写的东西不能编译,因为它不是Delphi pascal。 Delphi 属性需要readwrite 访问器,而不是setget;属性需要指定它们的类型,除非它们是从父级继承的(在这种情况下你不能改变类型)。还有其他错误。你的答案是 99% 的代码,但它不能独立存在,因为它甚至不能编译。当您编译代码时,我将恢复我的反对意见,并请包括父类声明以及何时可能有用的演示,因为我没有看到它。
    • 谢谢。我修。我在这台机器上没有 Delphi。使用 C# 并搞混了。
    • 也许您应该删除答案并在通过Delphi编译器获得答案后再次发布,并请添加该父类+几个方法调用。我已经(尝试)为你这样做了,所以我知道你将面临一些困难的问题;枚举不能和父类同名,即使枚举元素也不能重名,调用父类中引入的获取枚举值的方法是相当困难的。我不会轻易投反对票,尤其是当我有一个相互竞争的答案时!
    • 呃,您的回答似乎更像是在增加帮助。是的,代码不完整,许多答案都是正确的,并且仍然标记为正确。枚举属性有不同的名字,两种枚举类型可以有相同id的值,程序员只需要前缀...
    • Trolling... 我告诉过你代码无法编译,经过 2 次编辑后仍然无法编译。我建议您删除答案,直到您可以修复它,您可以在通过编译器获得它后取消删除它。您甚至没有解决我在 first 评论中告诉您的所有内容。好吧,这是你的答案,我不会再自愿提供任何帮助或 cmets,尤其是因为你认为我在拖钓。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 2010-10-19
    相关资源
    最近更新 更多