Static as pseudo-enums

Posted by jmls on 09-Jun-2009 14:28

When coding, there often is a need for "constants". We've used variables:

DEF VAR SpecialPlace AS INT INIT -1 NO-UNDO.

...

IF SomeRecord.Place EQ SpecialPlace THEN ..

We've used preprocessors

&SCOPED SpecialPlace -1

...

IF SomeRecord.Place EQ {&SpecialPlace} THEN ..

We've used includes

IF SomeRecord.Place EQ {SpecialPlace} THEN ..

(shudder)

For a little while now (10.1C) we're using static properties to perform the same function

CLASS constants.Places#:

     DEF STATIC PROPERTY SpecialPlace AS INT NO-UNDO INIT -1 GET .

END CLASS.

/* foo.p */

USING constants.*.

IF SomeRecord.Place EQ Places#:SpecialPlace THEN ..

What is the view of you all on this ? I confess that I like it - as it gives me all the power of strong typing (?) i.e. if you misspell the constant you can't compile with all the advantages of a single location for the definitions without any of the drawbacks of preprocessor or includes.

Or have I hust made Dr Hursh shudder ?

All Replies

Posted by Admin on 09-Jun-2009 14:42

What is the view of you all on this ? I confess that I like it - as it gives me all the power of strong typing (?) i.e. if you misspell the constant you can't compile with all the advantages of a single location for the definitions without any of the drawbacks of preprocessor or includes.

I'm using the same concept using statics - just using CHARACTER values. It's easier to display in a message box.

When we'll have true enum types in the ABL it will be easy to replace.

By the way: It does not give real strong typing. The compiler will allow variable assignments and comparison of values from different "pseudo-enum" classes. So it saves you from misspelling, not guaranteeing to use only the right set of values for a parameter.

But it allows to be used in a REAL case statement

Posted by Peter Judge on 09-Jun-2009 14:44

I do something similar, for similar reasons. Architect is where this really shines with the context assistance. And if you use this approach, you're in good shape if and when the OOABL gets native enums.

I always use public static integer vars. If I need to use a string representation, I overload the ToString() method.

class OpenEdge.System.SortDirectionEnum :
   
    define public static variable None          as integer no-undo init -1.
    define public static variable Default       as integer no-undo init 0.
    define public static variable Ascending     as integer no-undo init 1.
    define public static variable Descending    as integer no-undo init 2.
       
    constructor static SortDirectionEnum():
        SortDirectionEnum:Default = SortDirectionEnum:Ascending.       
    end constructor.
   
    method static public character ToString (piSortDirection as integer):
        define variable cName as character no-undo.
       
        case piSortDirection:
            when SortDirectionEnum:None then 'None'.
            when SortDirectionEnum:Ascending then 'Ascending'.
            when SortDirectionEnum:Descending then 'Descending'.
        end case.                   
       
        return cName.
    end method.       
           
end class.

You can also use this approach for flag-type enums. This will allow you to set a flag with value of Enable + View, and be able to determine which you've set.

class OpenEdge.System.ActionStateEnum inherits FlagsEnum  :

    define public static variable Enable  as int no-undo init 1.
    define public static variable Disable as int no-undo init 2.
    define public static variable View    as int no-undo init 4.
    define public static variable Hide    as int no-undo init 8.

end class.

class OpenEdge.Base.System.FlagsEnum   :
   
    method static public logical IsA (piArg1 as int, piIsA as int):
        define variable iPos as integer no-undo.
        define variable iVal as integer no-undo.
               
        do iPos = 1 to 32 while iVal eq 0:
            iVal = get-bits(piIsA, iPos, 1).
        end.
                                      
        return (get-bits(piArg1, iPos - 1, 1) eq iVal).
    end method.


end class.

For completeness: if you need to do comparisons between the string representation and the enum (this is more useful if you want to compare, say hBufferField:data-type with a DataTypeEnum; enum-to-enum comparison simply use the ABL 'eq' or '=' comparison operator).

    method static public logical Equals (pcDirection as char, piDirection as int):
        return SortDirectionEnum:ToString(piDirection) eq pcDirection.
    end method.      

-- peter

(and as Mike points out, since they're 'real' integers, they can be used in a 'real' case statement )

Message was edited by: Peter Judge
  Added Equals() example.

Posted by Admin on 09-Jun-2009 14:52
    
    method static public character ToString (piSortDirection as integer):
        define variable cName as character no-undo.
       
        case piSortDirection:
            when SortDirectionEnum:None then 'None'.
            when SortDirectionEnum:Ascending then 'Ascending'.
            when SortDirectionEnum:Descending then 'Descending'.
        end case.                   
       
        return cName.
    end method.       


If the language hasn't or won't change in the release you are using, you should review that method.

I doubt it will ever return something useful.

Nachricht geändert durch Mike Fechner

Posted by Admin on 09-Jun-2009 14:54

pjudge schrieb:

I always use public static integer vars. If I need to use a string representation, I overload the ToString() method

Can you tell us what advantage you see in a using a public static variable? It can be changed for the whole session because it's always read/write access.

How do you secure your class framework from someone doing:

SortDirectionEnum:Ascending = 13 .

Posted by Peter Judge on 09-Jun-2009 14:57

mikefe wrote:

    
    method static public character ToString (piSortDirection as integer):
        define variable cName as character no-undo.
       
        case piSortDirection:
            when SortDirectionEnum:None then 'None'.
            when SortDirectionEnum:Ascending then 'Ascending'.
            when SortDirectionEnum:Descending then 'Descending'.
        end case.                   
       
        return cName.
    end method.       




If the language hasn't or won't change in the release you are using, you should review that method.



I doubt it will ever retrun something useful.


Indeed. PSDN as QA .... now there's an idea.

Actually, PSDN as Code Review doesn't strike me as a terrible idea, although.

-- peter

Posted by Admin on 09-Jun-2009 15:02

Actually, PSDN as Code Review doesn't strike me as a terrible idea, although.

-- peter

Isn't the communities site the successor of the successor of possenet? Going home now - drinking a beer on the good old days :-)

Posted by Peter Judge on 09-Jun-2009 15:03

Can you tell us what advantage you see in a using a public static variable? It can be changed for the whole session because it's always read/write access.

How do you secure your class framework from someone doing:

SortDirectionEnum:Ascending = 13 .

Good point.

Read-only properties would do the trick though (which is what Julian is actually using).

    define public static property None as integer no-undo init -1 get.

I generally only like using member variables for private or protected use, and properties for public use, so this fits well with that.

Now to go refactor my 30-odd enums ... :S

-- peter

Posted by Peter Judge on 09-Jun-2009 15:35

A follow-up question to using properties for Enum members.

Generally, there's no setter on the enum members. But the Default needs to be set to something. All other members are read-only (no set at all, value is set by init in definition).

What should the access level be on this property?

(1) private set: can only be set in the static constructor. This means that to change the default, you'd have to sub-class, or change the code.

define public static property Default as integer no-undo init 0 get. private set.

(2) public set: freely settable by anyone. This is a recipe for inconsistent behaviour, but hey, it's a free world. Sorta.

define public static property Default as integer no-undo init 0 get. set.

(3) public set with 'set-once' restricting code: once it's set once, it's set for the session.

define public static property Default as integer no-undo init 0 get. 
  set (piVal as int):
     if Default eq 0 then Default = piVal.
  end set.

This requires a default set (in the absence of an override).

define public static property Default as integer no-undo init 0 
  get().
    /* or something similar ... */
    if ThisEnum:Default eq 0 then
      return ThisEnum:SomeMember.
    else
      return ThisEnum:Default.
  end get
  set (piVal as int):
     if ThisEnum:Default eq 0 then ThisEnum:Default = piVal.
  end set.

(4) protected set. I'm also thinking of making the Enum classes final so that they values cannot be overridden, so this would be moot. Is making Enum classes final a good idea?

Making Enum classes final means that they cannot be extended; this may be undesirable in some cases.

Thanks for any insights,

-- peter

Message was edited by: Peter Judge

Posted by jmls on 09-Jun-2009 18:24

pjudge wrote:

A follow-up question to using properties for Enum members.

Generally, there's no setter on the enum members. But the Default needs to be set to something. All other members are read-only (no set at all, value is set by init in definition).

What should the access level be on this property?

(1) private set: can only be set in the static constructor. This means that to change the default, you'd have to sub-class, or change the code.



define public static property Default as integer no-undo init 0 get. private set.


(2) public set: freely settable by anyone. This is a recipe for inconsistent behaviour, but hey, it's a free world. Sorta.



define public static property Default as integer no-undo init 0 get. set.


(3) public set with 'set-once' restricting code: once it's set once, it's set for the session.



define public static property Default as integer no-undo init 0 get. 
  set (piVal as int):
     if Default eq 0 then Default = piVal.
  end set.


This requires a default set (in the absence of an override).



define public static property Default as integer no-undo init 0 
  get().
    /* or something similar ... */
    if ThisEnum:Default eq 0 then
      return ThisEnum:SomeMember.
    else
      return ThisEnum:Default.
  end get
  set (piVal as int):
     if ThisEnum:Default eq 0 then ThisEnum:Default = piVal.
  end set.


(4) protected set. I'm also thinking of making the Enum classes final so that they values cannot be overridden, so this would be moot. Is making Enum classes final a good idea?



Making Enum classes final means that they cannot be extended; this may be undesirable in some cases.



Thanks for any insights,


-- peter



Message was edited by: Peter Judge


1) That's the way for me. Didn't know that you could subclass and change the values though. How would you do that ?

2) Yikes. No way man.

3) same as 2)

4) Hmmm. Maybe...

Posted by Peter Judge on 10-Jun-2009 08:59

jmls wrote:


1) That's the way for me. Didn't know that you could subclass and change the values though. How would you do that ?

2) Yikes. No way man.

3) same as 2)

4) Hmmm. Maybe...

I somehow thought you could subclass a class with the name package+name, in a different Propath location.Turns out you can't (yay).

You can create a class with the same package+name in a different Propath location, but then you'd have to re-implement the class completely.

After all my dithering about to-Final-or-not-to-Final, I decided to make the classes Final. If you want to override the Enum, create your own enum.

What are your thoughts on changing the Default on a per-app basis (assuming you're writing some common or shared classes)? So one app might have 'ascending' as the default sort direction, another might have 'descending'. Assuming you have one SortDirection Enum, how would you deal with that?

-- peter

Posted by jmls on 10-Jun-2009 09:14

pjudge wrote:

I somehow thought you could subclass a class with the name package+name, in a different Propath location.Turns out you can't (yay).

You can create a class with the same package+name in a different Propath location, but then you'd have to re-implement the class completely.

After all my dithering about to-Final-or-not-to-Final, I decided to make the classes Final. If you want to override the Enum, create your own enum.

What are your thoughts on changing the Default on a per-app basis (assuming you're writing some common or shared classes)? So one app might have 'ascending' as the default sort direction, another might have 'descending'. Assuming you have one SortDirection Enum, how would you deal with that?

-- peter

I would create a base class (foo) with all the properties but Default, and then create two new classes (classA, classB) which inherit foo, but define their own Default property which is initialised to either ascending or descending.

Posted by Thomas Mercer-Hursh on 10-Jun-2009 11:22

What is the use case of wanting to set an enum?

The only thing that comes readily to mind is an extendable set for validation, but that isn't something one would handle with an enum.

Posted by Thomas Mercer-Hursh on 10-Jun-2009 11:24

If there are alternate defaults by context, then I would make that a part of the constructor and take it from the database.

Posted by Peter Judge on 10-Jun-2009 11:32

What is the use case of wanting to set an enum?

The only thing I would want to be able set is the Default value (if there is one), which may vary by application. It would only be set once (by that criteria), since changing the Default continually makes somewhat of a mockery of the whole point of defaults.

I like Julian's approach, which could be tweaked by the use of abstracts.

-- peter

Posted by Thomas Mercer-Hursh on 10-Jun-2009 11:41

Subclassing makes sense if it is rigidly fixed in the two different contexts.  Taking it from the database makes sense if it is a user or site preference type of issue.  If it is something that is more set once and forget, then it could come from an XML configuration file, but that would mostly make sense in a context where there was no DB.

Posted by Admin on 10-Jun-2009 11:52

tamhas schrieb:

but that would mostly make sense in a context where there was no DB.

Like on almost any ABL client. As OO in the ABL is a brand new concept, I guess everybody should design for N-Tier with OO.

Posted by Thomas Mercer-Hursh on 10-Jun-2009 12:04

I might have said that ... if I ever thought much about ABL on the client ...

Posted by Admin on 10-Jun-2009 12:12

Thomas, ABL clients are out there - and they are very often not the worst choice

Posted by Thomas Mercer-Hursh on 10-Jun-2009 12:34

Of course they are out there and clearly with this new stuff one can make them pretty nice.  And, if one has already invested the license cost for an ABL license per seat, than that is sunk cost.  No question that the ABL GUI for .NET stuff can tart up an old application and make it look much better.  But, since I think mostly about architectural transformation, I wonder why someone would design a new architecture with a license per seat.

BTW, it is tiresome to have to type ABL GUI for .NET over and over and over again.  As you know, I reject Greg Higgins proposed NUI label for this as not clearly indicating this solution versus, for example, .NET clients (which I don't think deserve their own XUI acronym).  But, what about AG4.N or just AG4N?

Posted by Admin on 10-Jun-2009 13:02

tamhas schrieb:

But, since I think mostly about architectural transformation, I wonder why someone would design a new architecture with a license per seat.

I'm not a licensing expert at all - but AFAIK with the usual licensing model or POA it really does not matter what technology is used to write the client. My understanding is, that web services, .NET or Java Proxy based clients don't have any cost benefit over an ABL clients (let's say Web Client) - given that we are talking about full time users.

On the other side the fact that a single development team does need to control only  a single technology (OpenEdge) for both the business logic and the UI functionality may have an impact on the costs. Most projects where two different technologies where involved for back- and frontend required additional afford.

BTW, it is tiresome to have to type ABL GUI for .NET over and over and over again.  As you know, I reject Greg Higgins proposed NUI label for this as not clearly indicating this solution versus, for example, .NET clients (which I don't think deserve their own XUI acronym).  But, what about AG4.N or just AG4N?

Sounds all odd to me. I prefer .NET GUI - from the context if should be clear if I'm talking about .NET in the AVM or plain CLR.

PS.: Now we are really hijacking Julian's thread...

Posted by Thomas Mercer-Hursh on 10-Jun-2009 13:18

PS.: Now we are really hijacking Julian's thread..

While I'm sure he is used to it, I'll start a new one.

Posted by jmls on 10-Jun-2009 13:27

Oh sure. Great. You corrupt my thread, and then, when it's getting too "hijacked" for you all, you buggers then go off and start a new thread.

Harrumphh.

 * jmls keeps an eye out for the new thread from TMH. He's gonna hijack that one ...

This thread is closed