7

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 似乎是显而易见的方法。但是,如果您无权访问或不想修改父组件代码怎么办?

4

3 回答 3

4

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

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

即使您可以将不属于该枚举的内容强制转换为TSpecialKind类型,上面代码的结果也将是未定义的(并且绝对不好!)

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

if skBeta in VarOfTypeSpecialKinds then
begin
  ...
end;

(续)然后您可以引入一种新类型,该类型以相同的顺序枚举所有原始值,具有相同的值。如果你这样做之后SizeOf(TSpecialKind)等于,那么你可以将新的设置SizeOf(TNewType)值硬转换为旧值,旧代码也可以正常工作。但坦率地说,这是 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;
于 2013-02-01T15:06:44.337 回答
2

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

让我们以您的车辆类型为例,深入研究一下。我希望原始组件将具有如下代码:

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

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

TExtendedVehicleKind = (vkCar, vkCycle, vkTrike);

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

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

于 2013-02-01T16:00:18.067 回答
-2

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

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


潜在的父类代码:


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
   );

干杯。

于 2013-02-01T18:30:28.387 回答