Delphi 2007,明年转向Delphi XE。
我们的产品广泛使用第三方组件。我们不直接使用该组件,而是使用它的自定义后代,我们已经添加了相当多的额外行为(自定义后代组件是几年前由已经退役的开发人员开发的)。 / p>
在第三方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似乎是一种显而易见的方法。但是,如果您无权访问或不想修改父组件代码,该怎么办?
答案 0 :(得分:4)
枚举类型的继承与它对Classes的工作方式不同,因为代码会假设它永远不会对类进行枚举。例如,给定您的原始枚举(TSpecialKind
),第三方组件可能包含以下代码:
var Something: TSpecialKind;
[...]
case Something of
skAlpha: ;
skBeta: ;
skGamma: ;
end;
即使您可以将不属于该枚举的内容转换为TSpecialKind
类型,上面代码的结果也将是 undefined (并且最终不好!)
枚举可能以另一种方式使用,如果第三方组件仅以这种方式使用它,那么您可以做一些“魔法”,但我不推荐它。如果原始TSpecialKind
仅通过它的TSpecialKinds
集类型使用,那么它只会像这样使用:
if skBeta in VarOfTypeSpecialKinds then
begin
...
end;
(续)然后你可以引入一个新类型,它以相同的顺序枚举所有原始值,具有相同的值。如果执行此操作后SizeOf(TSpecialKind)
等于SizeOf(TNewType)
,则可以将新的 set 值强制转换为旧值,旧代码的工作方式也相同。但坦率地说,这很麻烦,因为它很多条件才能正常工作,太脆弱了。更好的解决方案是使用仅在后代组件中使用的新枚举类型:
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;
答案 1 :(得分:2)
我不相信您可以合理地期望在不修改原始组件的情况下进行所需的更改。
让我们以您的车辆为例,深入研究一下。我希望原始组件的代码如下:
case Kind of
vkCar:
CarMethod;
vkCycle:
CycleMethod;
end;
现在,假设您引入了带有额外枚举的枚举类型
TExtendedVehicleKind = (vkCar, vkCycle, vkTrike);
如果上面的case语句运行,ExtendedKind
等于vkTrike
,则不会调用任何方法。
现在,通过在Kind
为vkCar
时将vkCycle
设置为ExtendedKind
或vkTrike
,可能可以实现原始控件所需的行为。但这对我来说似乎不太可能。只有你可以肯定地知道,因为只有你有代码,并知道你的实际问题是什么。
答案 2 :(得分:-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
);
干杯。