Frage

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?

War es hilfreich?

Lösung 2

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.

Andere Tipps

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;
  • Extra.

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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top