OE Wishlist: Object Oriented Programming Features

Although OpenEdge added support for objects in version 10 (circa 2005), there's definitely still rough edges over a decade later. Here's my list of Class and Object related gripes.

Table / Object Impedance Mismatch

OE started as a very table-focused language. It still is in many respects and there are a lot of things that this sort of thinking is useful for (See C#'s LINQ), but in a lot of cases, working with objects is more convenient. There is no really convenient way to do Object-Relational mapping. You also cannot do something like add methods to a temp-table "object" which would really be incredibly convenient.

Databinding for Objects

In OpenEdge, using GUI for .Net, Progress provides a utility to bind attributes of UI controls to standard OpenEdge components like temp-tables, datasets, and record buffers. Unfortunately, this glue doesn't stick to objects. .Net allows for you to bind to objects as well as relational result sets. Without the ability to databind to object properties, you can

  1. Manually write the databinding code in the control
  2. Use tables everywhere
  3. Not use databinding

Option 1 introduces a lot of boiler plate for even very simple cases. Option 2 entails writing non-object-oriented code, which often clashes with the rest of your code. Option 3 often causes duplication of data or bad versions of option 1.

I would very much be able to choose whether to use objects or tables based on the data and the operations that need to be performed on the data, rather than whether I have to write a bunch of boiler-plate code to get the data to show on the screen.

Generics

See https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/

Without generics, any data structures become much more complicated. Instead of having one class with generic logic, you now need a version of your data structure for each type, i.e. CharacterList, IntegerList, LoggerList, and it gets even more complicated with structures like maps that can include 2 different types, i.e. Map<integer, character> vs Map<character, character>.

A work around involves an include file that allows you to specify the type as a parameter. You'd then have a class file for each type needed that used the include file and passed in the type-

class CharacterList:

{List.i character}

end class.

Initialize Object Variables

When declaring a variable, you can set an initial value for primitive types, but not object types. It's inconsistent and harder to read.

def var logger as Logger no-undo.

constructor public MyClass():
    logger = new Logger().
end constructor.

vs

def var logger as Logger no-undo
    init new Logger().

You can also use the get method to do a sort of lazy-loading, but it's somewhat verbose.

def var logger as Logger no-undo
    get:
        if not valid-object(logger) then
            this-object:logger = new Logger().
        return this-object:logger.
    end get.
    set.

Main Method Support in Classes

To run an openedge program, the bare-minimum that you need is to run the OpenEdge runtime while passing it a startup procedure filename.

# DLC is a stand in for the OpenEdge install directory.
%DLC%/bin/_progres.exe -p start.r # For a CLI application
%DLC%/bin/prowin32.exe -p start.r # For a GUI application

Note that I said procedure-- a class cannot be used as an entry point for an application. In order to start up a class, you need to create a procedure that instantiates the class and runs whatever code is needed.

// start.p
StartupForm:Main().

// StartupForm.cls
method public static void Main():
...
end method.

This leads to a lot of boilerplate and work-arounds (usually by dynamically instantiating classes). Compare this to Java and C#, which both allow you to run classes that have a static Main method.

This is just one of many reasons why object oriented code feels like a second class citizen in the world of OpenEdge.