INTERFACE statement

Defines a user-defined interface. An interface defined with this statement represents a user-defined data type that defines a set of method, property, and event prototypes for methods, properties, and events that can be implemented by one or more classes. Any class that implements the interface must support all the methods, properties, and events whose prototypes are defined in the interface or any interface from which this interface inherits member prototypes.

You cannot instantiate an interface as an object. You can only use it to define the specified interface for a class (which you can instantiate) that implements the interface.

Note: This statement is applicable only when used in a class definition (.cls) file. For more information, see the Notes section in this reference entry.

Syntax

INTERFACE interface-type-name
[ INHERITS super-interface-name [ , super-interface-name ] ... ] :
interface-body
interface-type-name
Defines the type name for a user-defined interface type. Specify an ABL interface type name as described in the Type-name syntax reference entry. You can also specify an unqualified interface name (without a package), but only if the interface name represents the full type name for the interface (that is, the interface is not defined as part of a package).

Note that you cannot specify "Progress" as the first component of the package name for any ABL user-defined interface. For example, "Progress.Inventory.IUpdateInv" is an invalid type name for a user-defined interface and results in a compiler error.The value of interface-type-name cis restricted to alphanumeric characters plus the symbols #, $, %, and _.

INHERITS super-interface-name [ , super-interface-name ]
Optionally specifies the type name of one or more other interfaces (super interfaces) from which this interface inherits member prototypes as part of its definition. Each super-interface-name you specify can be an ABL or .NET interface type name as described in the Type-name syntax reference entry. With an appropriate USING statement, you can also specify an unqualified interface name (an interface name without the ABL package or .NET namespace that completes the interface type name).

Also note that if more than one occurrence of a super interface type or member prototype appears in the interface inheritance hierarchy, it is treated as a single occurrence. However, interface-type-name (the current interface definition type name) can never appear as any super-interface-name, and can never be inherited by any interface specified by a super-interface-name. In other words, there can be no cycles (recursive inheritance) in an interface inheritance hierarchy. Otherwise, ABL raises a compiler error.

interface-body

The body of an interface definition is composed of the following types of elements:

  • Temp-table or ProDataSet object definitions used as parameters by one or more methods whose prototype is declared in this interface
  • Method prototypes for common methods implemented by one or more classes
  • Property prototypes for common properties implemented by one or more classes
  • Event prototypes for common events implemented by one or more classes

Define elements in the interface body using the following syntax:

[ { temp-table | dataset } ... ]
[ method-prototypes ]
[ property-prototypes ]
[ event-prototypes ]
END [ INTERFACE ].
temp-table | dataset

Specifies one or more temp-table or ProDataSet object definitions used as parameters by one or more methods declared in this interface. You must specify these object definitions before any method prototypes. The AVM does not allocate memory for these object definitions. You cannot specify an access mode for these object definitions. These object definitions cannot be inherited from a super interface; they are private to the interface in which they are defined.

The definition of temp-table and ProDataSet object parameters for methods defined in any classes that implement this interface must match the temp-table or ProDataSet object definitions in this interface.

For temp-table objects:

  • The temp-tables must have the same number of fields, and each field must match with respect to the data type, extent, and position. Neither the table names nor field names must match.
  • The temp-tables must have the same number of indexes, and each index component must match, including the index names. However, the index-component field names do not need to match.

For ProDataSet objects:

  • The ProDataSet objects must have the same number of member buffers, and the buffers must be in the same order. Neither the buffer names nor ProDataSet names must match.
  • The temp-tables of these buffers must match as described above.
method-prototypes

Declares one or more method prototypes in the interface. A method prototype declares a method of a class without an implementation (that is, without specifying the method's logic or the END METHOD statement).

For information on the syntax for method-prototype, see the METHOD statement reference entry for declaring method prototypes. Note the following:

  • The method access mode must be PUBLIC, either explicitly declared or defaulted.
  • The method prototype must not include the STATIC, ABSTRACT, OVERRIDE, or FINAL options.

The implementation of these method prototypes, in classes that implement this interface, must not include the STATIC option and must match these declarations with respect to:

  • Access mode, which must be PUBLIC
  • Return type
  • The number, data type, and access mode of any parameters
property-prototypes

Declares one or more property prototypes in the interface. A property prototype declares a property of a class without implementing its GET or SET accessors (that is, without specifying the property's logic). You must specify a property prototype with a PUBLIC access mode. The property prototype must include either a GET and SET accessor, or a GET accessor, or a SET accessor. Although the property declaration cannot have accessor implementations, the property when implemented in a class may include an accessor implementation.

For information on the syntax for property-prototype, see the DEFINE PROPERTY statement reference entry for declaring property prototypes. Note the following:

  • The property access mode must be PUBLIC, either explicitly declared or defaulted.
  • The property prototype must not include the STATIC, ABSTRACT, OVERRIDE, or INITIAL options.
  • Accessors must not include access modifiers.
  • Accessors must not include implementations.

The implementation of the property, in classes that implement this interface, must not include the STATIC option and must match these declarations with respect to:

  • Access mode, which must be PUBLIC
  • Name
  • Data type or type name
  • EXTENT — Its presence or absence, determinate or indeterminate, and size (if determinate)
  • Accessors — Any accessor appearing in the interface, plus the option of any additional accessor not specified in the interface, all defaulting to PUBLIC access mode
  • Presence or absence of NO-UNDO
event-prototypes

Declares one or more event prototypes in the interface. An event prototype declares a class event that the class must implement with an identical declaration, allowing the class to publish the event.

For information on the syntax for event-prototype, see the DEFINE EVENT statement reference entry for declaring class event prototypes. Note the following:

  • The event access mode must be PUBLIC, either explicitly declared or defaulted.
  • The event prototype must not include the STATIC, ABSTRACT, or OVERRIDE options.

The implementation of these event prototypes, in classes that implement this interface, must not include the STATIC option and must match these declarations with respect to:

  • Access mode, which must be PUBLIC
  • Any specified .NET delegate type or the number, data type, and access mode of any specified parameters
END [ INTERFACE ]
Specifies the end of the interface body definition. You must end the interface body definition with the END statement.

Examples

The following samples include two different class definitions that provide similar functionality, but in distinctly different ways. Each class implements the same interface and both classes define a ProDataSet data member (dsHighCustData) used to retrieve Customer and related Invoice table data for a single Customer record from the sports2000 database. However, each class selects the Customer record using a different and functionally distinct algorithm provided in its own implementation of the same interface method prototype.

Thus, each of the following sample class files implements the r-ICustObj interface type defined in the class definition file, r-ICustObj.cls and provides the following functionality:

  1. r-ICustObjImpl.cls — Defines the r-ICustObjImpl class, which retrieves data from the single Customer and related Invoice records that contain the highest balance value represented by the Customer.Balance value. This functionality is almost identical to what is provided by the r-CustObj.cls sample class file that is fully described in the Examples section of the CLASS statement reference entry. (The main differences from the r-ICustObjImpl class are that the r-CustObj class does not implement an interface type and it is defined as FINAL.)
  2. r-ICustObjImpl2.cls — Defines the r-ICustObjImpl2 class, which also retrieves data from a single Customer and related Invoice records. However, this class retrieves data for the Customer whose related Invoice records contain the highest Invoice balance represented by the sum of their Invoice.Amount values.

Following is the interface definition provided by the sample r-ICustObj.cls file.

r-ICustObj.cls

INTERFACE r-ICustObj:  

  /* Property prototypes to return basic values for the Customer
     identified with the highest balance in the database */
  DEFINE PUBLIC PROPERTY HighCustBalance AS DECIMAL NO-UNDO
    GET.
    SET.
  DEFINE PUBLIC PROPERTY HighCustNum AS INTEGER NO-UNDO
    GET.
    SET.
    
  /* Event prototype to notify about Customers with Invoices */
  DEFINE PUBLIC EVENT CustHasInvoices 
    SIGNATURE VOID ( piCustNum AS INTEGER ).

  /* Temp-tables for the ProDataSet parameter */
  DEFINE TEMP-TABLE ttCust NO-UNDO LIKE Customer.
  DEFINE TEMP-TABLE ttInv  NO-UNDO LIKE Invoice.

  /* ProDataSet parameter for passing a single Customer with the highest
     balance along with its related Invoices */
  DEFINE DATASET dsHighCustData FOR ttCust, ttInv
    DATA-RELATION FOR ttCust, ttInv 
      RELATION-FIELDS (ttCust.CustNum, ttInv.CustNum).

  /* Method prototype to get the current high Customer balance data */
  METHOD PUBLIC VOID GetHighCustomerData
    ( OUTPUT DATASET dsHighCustData BIND ).

  /* Method prototype to set (or reset) the current high Customer data */
  METHOD PUBLIC VOID SetHighCustomerData ( ).

END INTERFACE.

The implementations of the SetHighCustomerData( ) method populate the ProDataSet with selected fields from Customer and Invoice records, where the selected Customer also has related invoices. Each implementation of SetHighCustomerData( ) also sets the two implemented properties (HighCustBalance and HighCustNum) to appropriate values for the selected Customer, and publishes the implemented class event (CustHasInvoices) for each Customer record it encounters with related Invoice records. Each class defines additional data members to support its instance of the ProDataSet, and also must implement the GetHighCustomerData( ) method (according to the interface) to pass the ProDataSet as a by-reference output parameter.

Following is the interface implementation provided by the r-ICustObjImpl sample class. Note that the property implementations add initial values, but rely on default behavior for the accessors. The class also fully defines the ProDataSet to be passed as a by-reference method output parameter.

r-ICustObjImpl.cls

CLASS r-ICustObjImpl IMPLEMENTS r-ICustObj:
  
  /* Public properties to return basic values for a customer with the
     highest balance */
  DEFINE PUBLIC PROPERTY HighCustBalance AS DECIMAL INITIAL 0.0 NO-UNDO
    GET.
    SET.
  DEFINE PUBLIC PROPERTY HighCustNum AS INTEGER INITIAL ? NO-UNDO
    GET.
    SET.
    
  /* Public event to notify about Customers with Invoices */
  DEFINE PUBLIC EVENT CustHasInvoices 
    SIGNATURE VOID ( piCustNum AS INTEGER ).

  /* Private handle variable for the high customer ProDataSet */
  DEFINE PRIVATE VARIABLE hHighCustData AS HANDLE NO-UNDO.

  /* Private temp-tables for the high customer ProDataSet */
  DEFINE PRIVATE TEMP-TABLE ttCust NO-UNDO LIKE Customer.
  DEFINE PRIVATE TEMP-TABLE ttInv  NO-UNDO LIKE Invoice.

  /* Private ProDataSet for a single customer with the highest
     balance and its invoices */
  DEFINE PRIVATE DATASET dsHighCustData FOR ttCust, ttInv
    DATA-RELATION FOR ttCust, ttInv 
      RELATION-FIELDS (ttCust.CustNum, ttInv.CustNum).

  /* Private query and data sources for the ProDataSet */
  DEFINE PRIVATE QUERY qCust FOR Customer.
  DEFINE PRIVATE DATA-SOURCE srcCust FOR QUERY qCust.
  DEFINE PRIVATE DATA-SOURCE srcInv  FOR Invoice.

  /* Constructor to initialize handles and attach data sources */
  CONSTRUCTOR r-ICustObjImpl ( ):
    
    hHighCustData = DATASET dsHighCustData:HANDLE.
    BUFFER ttCust:ATTACH-DATA-SOURCE( DATA-SOURCE srcCust:HANDLE ).
    BUFFER ttInv:ATTACH-DATA-SOURCE( DATA-SOURCE srcInv:HANDLE ).
    
  END CONSTRUCTOR.

/* Public method to get the current high customer data */
  METHOD PUBLIC VOID GetHighCustomerData
    ( OUTPUT DATASET dsHighCustData BIND ):
      
  END METHOD.

  /* Public method to set (or reset) the current high customer data */
  METHOD PUBLIC VOID SetHighCustomerData ( ):
    
    hHighCustData:EMPTY-DATASET( ).
    FOR EACH Customer: /* Find Customer with highest balance */
      FIND FIRST Invoice WHERE Invoice.CustNum = Customer.CustNum NO-ERROR.
      IF AVAILABLE Invoice THEN DO:
        IF Customer.Balance > HighCustBalance THEN
          ASSIGN  HighCustBalance = Customer.Balance
                  HighCustNum     = Customer.CustNum.
        CustHasInvoices:Publish( Customer.CustNum ).
      END.
    END.
    QUERY qCust:QUERY-PREPARE("FOR EACH Customer " 
      + "WHERE Customer.CustNum = " + STRING(HighCustNum) ).
    hHighCustData:FILL( ).
    
  END METHOD.
  
END CLASS.

The bold code inside the SetHighCustomerData( ) method shows the difference in implementation from the same method implemented by the following r-ICustObjImpl2 sample class. Here, it relies on the stored value of the Customer.Balance field to determine each Customer balance and assigns the HighCustBalance and HighCustNum property values accordingly.

The following r-ICustObjProc.p sample procedure shows an application of the r-ICustObjImpl class, which responds to the CustHasInvoices class event and displays the contents of the dsHighCustCata ProDataSet. Note that it defines a reference-only instance of the ProDataSet as required by the class and its interface. (This application is identical to the r-CustObjProc.p sample procedure described in the Examples section of the CLASS statement, but which instantiates the r-CustObj sample class instead.)

r-ICustObjProc.p

DEFINE TEMP-TABLE ttCust NO-UNDO REFERENCE-ONLY LIKE Customer.
DEFINE TEMP-TABLE ttInv  NO-UNDO REFERENCE-ONLY LIKE Invoice.

DEFINE DATASET dsHighCustDataREFERENCE-ONLY FOR ttCust, ttInv
  DATA-RELATION FOR ttCust, ttInv 
    RELATION-FIELDS (ttCust.CustNum, ttInv.CustNum).
      
DEFINE VARIABLE rObj AS CLASS r-ICustObjImpl NO-UNDO.

rObj = NEW r-ICustObjImpl( ) NO-ERROR.
rObj:CustHasInvoices:Subscribe( "CustHasInvoices_Handler" ) NO-ERROR.

MESSAGE "High Customer Number:" rObj:HighCustNum SKIP 
        "High Customer Balance:" rObj:HighCustBalance VIEW-AS ALERT-BOX.

rObj:SetHighCustomerData( ) NO-ERROR.

MESSAGE "High Customer Number:" rObj:HighCustNum SKIP
        "High Customer Balance:" rObj:HighCustBalance VIEW-AS ALERT-BOX.

rObj:GetHighCustomerData( OUTPUT DATASET dsHighCustData BIND ) NO-ERROR.

CURRENT-WINDOW:WIDTH-CHARS = 90.

FOR EACH ttCust, EACH ttInv BREAK BY ttInv.CustNum: 
  DISPLAY ttCust.CustNum WHEN FIRST-OF(ttInv.CustNum)
          ttCust.Name WHEN FIRST-OF(ttInv.CustNum)
          ttCust.Balance WHEN FIRST-OF(ttInv.CustNum) 
          ttInv.InvoiceNum ttInv.Amount SKIP 
    WITH FRAME A WIDTH 90 DOWN 
         TITLE "Customer with highest stored balance" NO-ERROR.
END.

PROCEDURE CustHasInvoices_Handler:
  DEFINE INPUT PARAMETER pCustNum AS INTEGER.
    
  FIND FIRST Customer WHERE Customer.CustNum = pCustNum NO-ERROR.
  IF AVAILABLE Customer THEN 
    MESSAGE "Customer" Customer.CustNum ('"' + Customer.Name + '"')  
            "has a stored balance of" Customer.Balance 
            "and also has Invoices." 
            VIEW-AS ALERT-BOX.
      
END PROCEDURE.

Following is the interface implementation provided by the r-ICustObjImpl2 sample class. This implementation is identical to r-ICustObjImpl except for the SetHighCustomerData( ) method.

r-ICustObjImpl2.cls

CLASS r-ICustObjImpl2 IMPLEMENTS r-ICustObj:
  
  /* Public properties to return basic values for a customer with the
     highest balance */
  DEFINE PUBLIC PROPERTY HighCustBalance AS DECIMAL INITIAL 0.0 NO-UNDO
    GET.
    SET.
  DEFINE PUBLIC PROPERTY HighCustNum AS INTEGER INITIAL ? NO-UNDO
    GET.
    SET.
    
  /* Public event to notify about Customers with Invoices */
  DEFINE PUBLIC EVENT CustHasInvoices 
    SIGNATURE VOID ( piCustNum AS INTEGER ).

  /* Private handle variable for the high customer ProDataSet */
  DEFINE PRIVATE VARIABLE hHighCustData AS HANDLE NO-UNDO.

  /* Private temp-tables for the high customer ProDataSet */
  DEFINE PRIVATE TEMP-TABLE ttCust NO-UNDO LIKE Customer.
  DEFINE PRIVATE TEMP-TABLE ttInv  NO-UNDO LIKE Invoice.

  /* Private ProDataSet for a single customer with the highest
     balance and its invoices */
  DEFINE PRIVATE DATASET dsHighCustData FOR ttCust, ttInv
    DATA-RELATION FOR ttCust, ttInv 
      RELATION-FIELDS (ttCust.CustNum, ttInv.CustNum).

  /* Private query and data sources for the ProDataSet */
  DEFINE PRIVATE QUERY qCust FOR Customer.
  DEFINE PRIVATE DATA-SOURCE srcCust FOR QUERY qCust.
  DEFINE PRIVATE DATA-SOURCE srcInv  FOR Invoice.

  /* Constructor to initialize handles and attach data sources */
  CONSTRUCTOR r-ICustObjImpl2 ( ):
    
    hHighCustData = DATASET dsHighCustData:HANDLE.
    BUFFER ttCust:ATTACH-DATA-SOURCE( DATA-SOURCE srcCust:HANDLE ).
    BUFFER ttInv:ATTACH-DATA-SOURCE( DATA-SOURCE srcInv:HANDLE ).
    
  END CONSTRUCTOR.

 /* Public method to get the current high customer data */
  METHOD PUBLIC VOID GetHighCustomerData
    ( OUTPUT DATASET dsHighCustData BIND ):
      
  END METHOD.

  /* Public method to set (or reset) the current high customer data */
  METHOD PUBLIC VOID SetHighCustomerData ( ):

    DEFINE VARIABLE dBalance AS DECIMAL NO-UNDO.

    hHighCustData:EMPTY-DATASET( ).
    /* Find Customer with highest total Invoice balance */
    FOR EACH Customer:
      ASSIGN dBalance = 0.0.
      FOR EACH Invoice WHERE Invoice.CustNum = Customer.CustNum:
        ASSIGN dBalance = dBalance + Invoice.Amount.
      END.
      IF dBalance > HighCustBalance THEN
        ASSIGN  HighCustBalance = dBalance
                HighCustNum     = Customer.CustNum.
      IF dBalance > 0.0 THEN
        CustHasInvoices:Publish( Customer.CustNum ).
    END.
    QUERY qCust:QUERY-PREPARE("FOR EACH Customer " 
      + "WHERE Customer.CustNum = " + STRING(HighCustNum) ).
    hHighCustData:FILL( ).
    
  END METHOD.  

END CLASS.

The bold code inside the SetHighCustomerData( ) method shows the difference in implementation from the same method implemented by the previous r-ICustObjImpl sample class. Here, it calculates the Customer balance from the total of Invoice.Amount values in its related Invoice records and assigns the HighCustBalance and HighCustNum property values accordingly.

The following r-ICustObjProc2.p sample procedure shows an application of the r-ICustObjImpl2 class, which is very similar to the previous procedure, r-ICustObjProc.p. The differences include displaying both the stored Customer.Balance value and the Invoice.Amount total for the selected Customer record, as well as some cosmetic changes to the display. Otherwise, the application is identical.

r-ICustObjProc2.p

DEFINE TEMP-TABLE ttCust NO-UNDO REFERENCE-ONLY LIKE Customer.
DEFINE TEMP-TABLE ttInv  NO-UNDO REFERENCE-ONLY LIKE Invoice.

DEFINE DATASET dsHighCustData REFERENCE-ONLY FOR ttCust, ttInv
  DATA-RELATION FOR ttCust, ttInv 
    RELATION-FIELDS (ttCust.CustNum, ttInv.CustNum).
      
DEFINE VARIABLE rObj AS CLASS r-ICustObjImpl2 NO-UNDO.

rObj = NEW r-ICustObjImpl2( ) NO-ERROR.
rObj:CustHasInvoices:Subscribe( "CustHasInvoices_Handler" ) NO-ERROR.

MESSAGE "High Customer Number:" rObj:HighCustNum SKIP 
        "High Invoice Balance:" rObj:HighCustBalance VIEW-AS ALERT-BOX.
        
rObj:SetHighCustomerData( ) NO-ERROR.

MESSAGE "High Customer Number:" rObj:HighCustNum SKIP
        "High Invoice Balance:" rObj:HighCustBalance VIEW-AS ALERT-BOX.

rObj:GetHighCustomerData( OUTPUT DATASET dsHighCustData BIND ) NO-ERROR.

CURRENT-WINDOW:WIDTH-CHARS = 100.

FOR EACH ttCust, EACH ttInv BREAK BY ttInv.CustNum: 
  DISPLAY ttCust.CustNum WHEN FIRST-OF(ttInv.CustNum)
            COLUMN-LABEL "Customer!Number"
          ttCust.Name WHEN FIRST-OF(ttInv.CustNum)
          ttCust.Balance WHEN FIRST-OF(ttInv.CustNum) 
            COLUMN-LABEL "Stored!Balance"
          ttInv.InvoiceNum COLUMN-LABEL "Invoice!Number"
          ttInv.Amount (SUB-TOTAL BY ttInv.CustNum) SKIP 
    WITH FRAME A WIDTH 100 DOWN 
         TITLE "Customer with highest total Invoice balance" NO-ERROR.
END.

PROCEDURE CustHasInvoices_Handler:
  DEFINE INPUT PARAMETER pCustNum AS INTEGER.
    
  FIND FIRST Customer WHERE Customer.CustNum = pCustNum NO-ERROR.
  IF AVAILABLE Customer THEN 
    MESSAGE "Customer" Customer.CustNum ('"' + Customer.Name + '"')  
            "has a stored balance of" Customer.Balance 
            "and also has Invoices." 
            VIEW-AS ALERT-BOX.
      
END PROCEDURE.

Notes

See also

Class-based object reference, CLASS statement, DEFINE DATASET statement, DEFINE EVENT statement, DEFINE PROPERTY statement, DEFINE TEMP-TABLE statement, METHOD statement, Type-name syntax, USING statement