Object inheritance involving enumerated types
Delphi 2007, moving to Delphi XE over the next year.
Our product makes extensive use of a third-party component. We don't use the component directly, but instead use a custom descendant of it, to which we've added quite a lot of extra behavior (the custom descendant component was developed several years ago by developers who have since have retired).
In the source unit of the third-party Parent class, some enumerated types are declared, which control various operations of the component:
TSpecialKind = (skAlpha, skBeta, skGamma);
TSpecialKinds = set of TSpecialKind;
In our descendant class, we want to add new behavior, which would require expanding the selection of enumerated types. Essentially, we want this:
TSpecialKind = (skAlpha, skBeta, skGamma, skDelta, skEpsilon);
TSpecialKinds = set of TSpecialKind;
Obviously, we want to avoid editing the third-party code. Is it valid to simply redeclare the enumerated type, repeating the original values and adding our new ones, in our own descendent unit? Will it have any effect on existing code?
Edit: Example scenario to (hopefully) clarify. Say you've got a (parent) component for ordering vehicle parts. The parent unit has an enumerated type Tvkind for vehicle kind, with values vkCar and vkCycle defined. These values are used, among other things, to indicate how many wheels the vehicle has, 4 or 2.
Now, in your descendent component, you want to be able to handle 3-wheeled vehicles as well. Extending the Tvkind enumerated type to include a new value vkTrike seems like the obvious approach. But what if you don't have access to or don't want to modify the parent component code?
I don't believe that you can reasonably expect to make the change that you want without modifying the original component.
Let's take your vehicle kind example and delve a bit deeper. I expect that the original component will have code like this:
case Kind of
vkCar:
CarMethod;
vkCycle:
CycleMethod;
end;
Now, suppose you introduce an enumerated type with an extra enumeration
TExtendedVehicleKind = (vkCar, vkCycle, vkTrike);
If the case statement above runs, with ExtendedKind
equal to vkTrike
, no method will be called.
Now, perhaps the behaviour that you want from the original control can be achieved by setting Kind
to vkCar
or vkCycle
when ExtendedKind
is vkTrike
. But that seems unlikely to me. Only you can know for sure, because only you have the code, and know what your actual problem is.
Inheritance for enumeration types doesn't work the same way it works for Classes because code makes assumptions about enumerations that it would never make about a class. For example, given your original enumeration (the TSpecialKind
), the third party component likely includes code like this:
var Something: TSpecialKind;
[...]
case Something of
skAlpha: ;
skBeta: ;
skGamma: ;
end;
Even if you could cast something that's not part of that enumeration to the TSpecialKind
type, the result of that code above would be undefined (and definitively not good!)
Enumerations might be used in one other way, and if the third party component only uses it that way, then you might be able to do some "wizardry", but I don't recommend it. If the original TSpecialKind
is only used through it's TSpecialKinds
set type, and then it's only used like this:
if skBeta in VarOfTypeSpecialKinds then
begin
...
end;
(continued) then you could introduce a new type that enumerates all of the original values, in the same order, with the same value. If after you do that SizeOf(TSpecialKind)
equals SizeOf(TNewType)
then you can hard-cast the new set value to the old value and the old code would work the same. But frankly this is hacky, to many conditions for it to work properly, too fragile. The better solution would be to use a new enumeration type that's only used in your descendant component:
type TExtraSpecialKind = (skDelta, skEpsilon);
TExtraSpecialKinds = set of TExtraSpecialKind;
You'll probably have this set published in a different property; The solution is clean, will mix well with the descendant code and can be used cleanly too. Example:
if (skAlpha in SpecialKind) or (skDelta in ExtraSpecialKind) then
begin
// Do extra-sepcial mixed stuff here.
end;
Been in the "need to extended the enumerated type of a property".
Quick First Suggestion. Add your enumeration, as a new property wrapper to the existing property:
Potential Parent class code:
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;
Descendant Class code:
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;
If possible, always add a "null" or "dummy" value to your own enumerations, usually, the first value:
type
CatFoodCanEnum =
(
None, // <--- these one
Tuna,
Chicken,
Beef
);
Cheers.
链接地址: http://www.djcxy.com/p/69136.html下一篇: 涉及枚举类型的对象继承