Object-Oriented COBOL represents one of the most significant evolutions of the COBOL language since its inception in 1959. With the COBOL 2002 standard, the language formally embraced the object-oriented paradigm, bringing classes, inheritance...
In This Chapter
- Part VIII - Modern COBOL and System Evolution
- 37.1 OO COBOL Overview
- 37.2 CLASS-ID, METHOD-ID, OBJECT, and FACTORY Paragraphs
- 37.3 Defining Classes: Instance Data and Methods
- 37.4 Object Creation and References
- 37.5 Inheritance
- 37.6 Interfaces
- 37.7 Parameterized Classes and Methods
- 37.8 OO Design Patterns in COBOL
- 37.9 Mixing OO and Procedural COBOL
- 37.10 Practical Example: Building a Banking Account Class Hierarchy
- 37.11 Compiler Support
- 37.12 When to Use OO COBOL vs Traditional Procedural Style
- Summary
- Review Questions
- Exercises
Chapter 37: Object-Oriented COBOL
Part VIII - Modern COBOL and System Evolution
Object-Oriented COBOL represents one of the most significant evolutions of the COBOL language since its inception in 1959. With the COBOL 2002 standard, the language formally embraced the object-oriented paradigm, bringing classes, inheritance, polymorphism, and interfaces to the world's most widely deployed business programming language. This chapter explores how OO concepts map onto COBOL's unique syntax and semantics, how to build class hierarchies for real-world financial applications, and when the object-oriented approach genuinely benefits COBOL development versus when traditional procedural style remains the better choice.
If you have worked through Chapter 17 on subprograms and modular design, you already understand the foundational concepts of encapsulation and separation of concerns that underpin object-oriented programming. OO COBOL extends those ideas by bundling data and the procedures that operate on that data into cohesive units called classes.
37.1 OO COBOL Overview
37.1.1 The Road to Object-Oriented COBOL
The journey toward object-oriented COBOL began in the early 1990s when the ANSI COBOL committee recognized that modern software engineering practices demanded richer abstraction mechanisms than COBOL's procedural model could provide. The result was a phased introduction of OO features:
- COBOL 1997 (Draft): Initial OO extensions proposed, including CLASS-ID and METHOD-ID paragraphs.
- COBOL 2002 (ISO/IEC 1989:2002): Full object-oriented support ratified, including classes, interfaces, inheritance, parameterized classes, and object references.
- COBOL 2014 (ISO/IEC 1989:2014): Refinements to OO features along with JSON and XML capabilities (covered in Chapter 38).
IBM incorporated OO COBOL features into its Enterprise COBOL compilers beginning with Enterprise COBOL V4 and continuing through the current V6.x releases. Micro Focus Visual COBOL also provides comprehensive OO support, including interoperability with .NET and JVM class libraries.
Important Compiler Note: GnuCOBOL (formerly OpenCOBOL) does not support OO COBOL features as of version 3.x. If you are using GnuCOBOL for learning or development, the OO examples in this chapter will not compile. You will need IBM Enterprise COBOL, Micro Focus Visual COBOL, or another compliant commercial compiler to work with object-oriented COBOL code.
37.1.2 The Class/Object Paradigm in COBOL
In traditional procedural COBOL, you organize code into divisions, sections, and paragraphs. Data is declared in the DATA DIVISION and manipulated by statements in the PROCEDURE DIVISION. Programs communicate through the CALL statement, passing data via parameters (as discussed in Chapter 17).
Object-oriented COBOL introduces several new concepts layered on top of this familiar structure:
| OO Concept | COBOL Implementation |
|---|---|
| Class | CLASS-ID paragraph defining a template for objects |
| Object | An instance of a class, created at runtime |
| Method | METHOD-ID paragraph containing executable logic |
| Instance Data | WORKING-STORAGE within the OBJECT paragraph |
| Class Data | WORKING-STORAGE within the FACTORY paragraph |
| Inheritance | INHERITS clause on CLASS-ID |
| Interface | INTERFACE-ID paragraph |
| Polymorphism | Method overriding and interface implementation |
| Encapsulation | Data hidden within objects, accessed only through methods |
| Object Reference | A data item that points to an object instance |
A COBOL class definition is itself a compilation unit, much like a program. It has its own IDENTIFICATION DIVISION, ENVIRONMENT DIVISION, DATA DIVISION, and PROCEDURE DIVISION. The key difference is that a class contains nested definitions for the FACTORY (class-level behavior) and OBJECT (instance-level behavior) components.
37.2 CLASS-ID, METHOD-ID, OBJECT, and FACTORY Paragraphs
37.2.1 The CLASS-ID Paragraph
The CLASS-ID paragraph replaces the PROGRAM-ID paragraph when you are defining a class rather than a traditional program. It specifies the class name and optionally the parent class from which it inherits.
IDENTIFICATION DIVISION.
CLASS-ID. BankAccount INHERITS Base.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount".
The REPOSITORY paragraph in the CONFIGURATION SECTION maps class names used in the source code to external class names (typically file names or system identifiers). This serves a similar purpose to import statements in Java or Python.
37.2.2 The FACTORY Paragraph
The FACTORY paragraph defines class-level data and methods. Factory data is shared among all instances of the class, analogous to static fields in Java or C++. Factory methods can be invoked on the class itself without creating an instance.
FACTORY.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NEXT-ACCOUNT-NUMBER PIC 9(10) VALUE 1000000001.
01 WS-TOTAL-ACCOUNTS PIC 9(8) VALUE 0.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. createAccount.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NEW-ACCOUNT OBJECT REFERENCE BankAccount.
LINKAGE SECTION.
01 LS-ACCOUNT-REF OBJECT REFERENCE BankAccount.
PROCEDURE DIVISION RETURNING LS-ACCOUNT-REF.
INVOKE BankAccount "NEW"
RETURNING WS-NEW-ACCOUNT
ADD 1 TO WS-TOTAL-ACCOUNTS
SET LS-ACCOUNT-REF TO WS-NEW-ACCOUNT
GOBACK.
END METHOD createAccount.
IDENTIFICATION DIVISION.
METHOD-ID. getTotalAccounts.
DATA DIVISION.
LINKAGE SECTION.
01 LS-TOTAL PIC 9(8).
PROCEDURE DIVISION RETURNING LS-TOTAL.
MOVE WS-TOTAL-ACCOUNTS TO LS-TOTAL
GOBACK.
END METHOD getTotalAccounts.
END FACTORY.
37.2.3 The OBJECT Paragraph
The OBJECT paragraph defines instance-level data and methods. Each object created from the class has its own copy of the instance data. Instance methods operate on the specific object through which they are invoked.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ACCOUNT-NUMBER PIC 9(10).
01 WS-ACCOUNT-HOLDER PIC X(50).
01 WS-BALANCE PIC S9(13)V99 VALUE 0.
01 WS-ACCOUNT-TYPE PIC X(10).
01 WS-DATE-OPENED PIC 9(8).
01 WS-IS-ACTIVE PIC 9 VALUE 1.
88 ACCOUNT-ACTIVE VALUE 1.
88 ACCOUNT-CLOSED VALUE 0.
PROCEDURE DIVISION.
* Instance methods are defined here
END OBJECT.
37.2.4 The METHOD-ID Paragraph
Methods are the OO equivalent of COBOL paragraphs or sections, but they are encapsulated within a class and have access to the object's instance data. Each method has its own IDENTIFICATION DIVISION, optional DATA DIVISION, and PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. deposit.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TIMESTAMP PIC 9(14).
LINKAGE SECTION.
01 LS-AMOUNT PIC S9(13)V99.
01 LS-STATUS PIC 9.
88 DEPOSIT-SUCCESS VALUE 0.
88 DEPOSIT-INVALID-AMOUNT VALUE 1.
88 DEPOSIT-ACCOUNT-CLOSED VALUE 2.
PROCEDURE DIVISION USING LS-AMOUNT
RETURNING LS-STATUS.
IF NOT ACCOUNT-ACTIVE
SET DEPOSIT-ACCOUNT-CLOSED TO TRUE
GOBACK
END-IF
IF LS-AMOUNT <= 0
SET DEPOSIT-INVALID-AMOUNT TO TRUE
GOBACK
END-IF
ADD LS-AMOUNT TO WS-BALANCE
SET DEPOSIT-SUCCESS TO TRUE
GOBACK.
END METHOD deposit.
37.3 Defining Classes: Instance Data and Methods
37.3.1 Complete Class Definition Structure
A complete COBOL class definition follows a specific structural pattern. Understanding this structure is essential before writing any OO COBOL code. The following diagram shows the nesting of components within a class:
CLASS-ID. ClassName
ENVIRONMENT DIVISION (class-level)
REPOSITORY (class references)
FACTORY.
DATA DIVISION
WORKING-STORAGE SECTION (class-level data)
PROCEDURE DIVISION
METHOD-ID. factoryMethod1 ... END METHOD.
METHOD-ID. factoryMethod2 ... END METHOD.
END FACTORY.
OBJECT.
DATA DIVISION
WORKING-STORAGE SECTION (instance data)
PROCEDURE DIVISION
METHOD-ID. instanceMethod1 ... END METHOD.
METHOD-ID. instanceMethod2 ... END METHOD.
END OBJECT.
END CLASS ClassName.
37.3.2 A Complete Compilable BankAccount Class
The following is a complete, compilable OO COBOL program defining a BankAccount class. This requires IBM Enterprise COBOL V4 or later.
******************************************************************
* BANKACCOUNT CLASS DEFINITION
* Requires: IBM Enterprise COBOL V4+ or Micro Focus Visual COBOL
* This class represents a basic bank account with deposit,
* withdrawal, and balance inquiry capabilities.
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. BankAccount INHERITS Base.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount".
******************************************************************
* FACTORY PARAGRAPH - Class-level data and methods
******************************************************************
FACTORY.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NEXT-ACCT-NUM PIC 9(10) VALUE 1000000001.
01 WS-TOTAL-ACCOUNTS-CREATED PIC 9(8) VALUE 0.
PROCEDURE DIVISION.
*-----------------------------------------------------------------
* Factory Method: getNextAccountNumber
* Returns the next available account number and increments
* the internal counter.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. getNextAccountNumber.
DATA DIVISION.
LINKAGE SECTION.
01 LS-ACCT-NUM PIC 9(10).
PROCEDURE DIVISION RETURNING LS-ACCT-NUM.
MOVE WS-NEXT-ACCT-NUM TO LS-ACCT-NUM
ADD 1 TO WS-NEXT-ACCT-NUM
ADD 1 TO WS-TOTAL-ACCOUNTS-CREATED
GOBACK.
END METHOD getNextAccountNumber.
*-----------------------------------------------------------------
* Factory Method: getTotalAccountsCreated
* Returns the count of all accounts ever created.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. getTotalAccountsCreated.
DATA DIVISION.
LINKAGE SECTION.
01 LS-TOTAL PIC 9(8).
PROCEDURE DIVISION RETURNING LS-TOTAL.
MOVE WS-TOTAL-ACCOUNTS-CREATED TO LS-TOTAL
GOBACK.
END METHOD getTotalAccountsCreated.
END FACTORY.
******************************************************************
* OBJECT PARAGRAPH - Instance-level data and methods
******************************************************************
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ACCOUNT-NUMBER PIC 9(10).
01 WS-HOLDER-NAME PIC X(50).
01 WS-BALANCE PIC S9(13)V99 VALUE 0.
01 WS-ACCOUNT-TYPE PIC X(10) VALUE "CHECKING".
01 WS-DATE-OPENED PIC 9(8).
01 WS-STATUS-FLAG PIC 9 VALUE 1.
88 ACCOUNT-ACTIVE VALUE 1.
88 ACCOUNT-FROZEN VALUE 2.
88 ACCOUNT-CLOSED VALUE 0.
01 WS-OVERDRAFT-LIMIT PIC S9(9)V99 VALUE 0.
01 WS-TRANSACTION-COUNT PIC 9(8) VALUE 0.
PROCEDURE DIVISION.
*-----------------------------------------------------------------
* Method: initialize
* Sets up the account with holder name, type, and assigns
* an account number from the factory.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. initialize.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ASSIGNED-ACCT-NUM PIC 9(10).
01 WS-CURRENT-DATE-DATA.
05 WS-CURRENT-DATE PIC 9(8).
05 WS-CURRENT-TIME PIC 9(8).
05 WS-DIFF-FROM-GMT PIC S9(4).
LINKAGE SECTION.
01 LS-HOLDER-NAME PIC X(50).
01 LS-ACCOUNT-TYPE PIC X(10).
01 LS-OVERDRAFT-LIMIT PIC S9(9)V99.
PROCEDURE DIVISION USING LS-HOLDER-NAME
LS-ACCOUNT-TYPE
LS-OVERDRAFT-LIMIT.
INVOKE BankAccount "getNextAccountNumber"
RETURNING WS-ASSIGNED-ACCT-NUM
MOVE WS-ASSIGNED-ACCT-NUM TO WS-ACCOUNT-NUMBER
MOVE LS-HOLDER-NAME TO WS-HOLDER-NAME
MOVE LS-ACCOUNT-TYPE TO WS-ACCOUNT-TYPE
MOVE LS-OVERDRAFT-LIMIT TO WS-OVERDRAFT-LIMIT
MOVE FUNCTION CURRENT-DATE TO WS-CURRENT-DATE-DATA
MOVE WS-CURRENT-DATE TO WS-DATE-OPENED
SET ACCOUNT-ACTIVE TO TRUE
MOVE 0 TO WS-BALANCE
MOVE 0 TO WS-TRANSACTION-COUNT
GOBACK.
END METHOD initialize.
*-----------------------------------------------------------------
* Method: deposit
* Adds funds to the account. Validates the amount is positive
* and the account is active.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. deposit.
DATA DIVISION.
LINKAGE SECTION.
01 LS-AMOUNT PIC S9(13)V99.
01 LS-RESULT-CODE PIC 9(2).
88 RESULT-SUCCESS VALUE 0.
88 RESULT-INVALID-AMOUNT VALUE 1.
88 RESULT-ACCOUNT-INACTIVE VALUE 2.
PROCEDURE DIVISION USING LS-AMOUNT
RETURNING LS-RESULT-CODE.
EVALUATE TRUE
WHEN NOT ACCOUNT-ACTIVE
SET RESULT-ACCOUNT-INACTIVE TO TRUE
WHEN LS-AMOUNT <= 0
SET RESULT-INVALID-AMOUNT TO TRUE
WHEN OTHER
ADD LS-AMOUNT TO WS-BALANCE
ADD 1 TO WS-TRANSACTION-COUNT
SET RESULT-SUCCESS TO TRUE
END-EVALUATE
GOBACK.
END METHOD deposit.
*-----------------------------------------------------------------
* Method: withdraw
* Removes funds from the account. Checks for sufficient
* balance including any overdraft allowance.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. withdraw.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-AVAILABLE-FUNDS PIC S9(13)V99.
LINKAGE SECTION.
01 LS-AMOUNT PIC S9(13)V99.
01 LS-RESULT-CODE PIC 9(2).
88 RESULT-SUCCESS VALUE 0.
88 RESULT-INVALID-AMOUNT VALUE 1.
88 RESULT-ACCOUNT-INACTIVE VALUE 2.
88 RESULT-INSUFFICIENT VALUE 3.
PROCEDURE DIVISION USING LS-AMOUNT
RETURNING LS-RESULT-CODE.
EVALUATE TRUE
WHEN NOT ACCOUNT-ACTIVE
SET RESULT-ACCOUNT-INACTIVE TO TRUE
WHEN LS-AMOUNT <= 0
SET RESULT-INVALID-AMOUNT TO TRUE
WHEN OTHER
COMPUTE WS-AVAILABLE-FUNDS =
WS-BALANCE + WS-OVERDRAFT-LIMIT
IF LS-AMOUNT > WS-AVAILABLE-FUNDS
SET RESULT-INSUFFICIENT TO TRUE
ELSE
SUBTRACT LS-AMOUNT FROM WS-BALANCE
ADD 1 TO WS-TRANSACTION-COUNT
SET RESULT-SUCCESS TO TRUE
END-IF
END-EVALUATE
GOBACK.
END METHOD withdraw.
*-----------------------------------------------------------------
* Method: getBalance
* Returns the current account balance.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. getBalance.
DATA DIVISION.
LINKAGE SECTION.
01 LS-BALANCE PIC S9(13)V99.
PROCEDURE DIVISION RETURNING LS-BALANCE.
MOVE WS-BALANCE TO LS-BALANCE
GOBACK.
END METHOD getBalance.
*-----------------------------------------------------------------
* Method: getAccountNumber
* Returns the account number.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. getAccountNumber.
DATA DIVISION.
LINKAGE SECTION.
01 LS-ACCT-NUM PIC 9(10).
PROCEDURE DIVISION RETURNING LS-ACCT-NUM.
MOVE WS-ACCOUNT-NUMBER TO LS-ACCT-NUM
GOBACK.
END METHOD getAccountNumber.
*-----------------------------------------------------------------
* Method: getAccountInfo
* Returns a formatted string with account details.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. getAccountInfo.
DATA DIVISION.
LINKAGE SECTION.
01 LS-INFO.
05 LS-INFO-ACCT-NUM PIC 9(10).
05 LS-INFO-HOLDER PIC X(50).
05 LS-INFO-TYPE PIC X(10).
05 LS-INFO-BALANCE PIC S9(13)V99.
05 LS-INFO-STATUS PIC 9.
05 LS-INFO-DATE-OPENED PIC 9(8).
05 LS-INFO-TXN-COUNT PIC 9(8).
PROCEDURE DIVISION RETURNING LS-INFO.
MOVE WS-ACCOUNT-NUMBER TO LS-INFO-ACCT-NUM
MOVE WS-HOLDER-NAME TO LS-INFO-HOLDER
MOVE WS-ACCOUNT-TYPE TO LS-INFO-TYPE
MOVE WS-BALANCE TO LS-INFO-BALANCE
MOVE WS-STATUS-FLAG TO LS-INFO-STATUS
MOVE WS-DATE-OPENED TO LS-INFO-DATE-OPENED
MOVE WS-TRANSACTION-COUNT TO LS-INFO-TXN-COUNT
GOBACK.
END METHOD getAccountInfo.
*-----------------------------------------------------------------
* Method: closeAccount
* Closes the account if the balance is zero.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. closeAccount.
DATA DIVISION.
LINKAGE SECTION.
01 LS-RESULT-CODE PIC 9(2).
88 RESULT-SUCCESS VALUE 0.
88 RESULT-BALANCE-NOT-ZERO VALUE 4.
88 RESULT-ALREADY-CLOSED VALUE 5.
PROCEDURE DIVISION RETURNING LS-RESULT-CODE.
EVALUATE TRUE
WHEN ACCOUNT-CLOSED
SET RESULT-ALREADY-CLOSED TO TRUE
WHEN WS-BALANCE NOT = 0
SET RESULT-BALANCE-NOT-ZERO TO TRUE
WHEN OTHER
SET ACCOUNT-CLOSED TO TRUE
SET RESULT-SUCCESS TO TRUE
END-EVALUATE
GOBACK.
END METHOD closeAccount.
END OBJECT.
END CLASS BankAccount.
37.3.3 Working-Storage Scope Rules
Understanding the scope of WORKING-STORAGE in OO COBOL is critical:
-
FACTORY WORKING-STORAGE: Data items declared here are shared by all instances of the class. There is exactly one copy of each factory data item, regardless of how many objects are created. This is equivalent to static fields in Java.
-
OBJECT WORKING-STORAGE: Data items declared here belong to each individual object instance. When you create a new object with
INVOKE ... "NEW", a fresh copy of all OBJECT WORKING-STORAGE items is allocated. -
METHOD WORKING-STORAGE: Data items declared within a METHOD-ID paragraph are local to that method invocation. They are allocated when the method is entered and deallocated when it returns. This is equivalent to local variables.
-
METHOD LINKAGE SECTION: Parameters passed to the method and the return value are declared here. The PROCEDURE DIVISION USING/RETURNING clause specifies which items are inputs and which are the output.
37.4 Object Creation and References
37.4.1 The INVOKE Statement
The INVOKE statement is the primary mechanism for calling methods on objects and classes. It replaces the CALL statement when working with OO COBOL code, although CALL continues to work for traditional procedural invocations.
* Creating a new object instance
INVOKE BankAccount "NEW" RETURNING WS-ACCOUNT-REF
* Calling an instance method
INVOKE WS-ACCOUNT-REF "deposit"
USING WS-DEPOSIT-AMOUNT
RETURNING WS-RESULT
* Calling a factory (class-level) method
INVOKE BankAccount "getTotalAccountsCreated"
RETURNING WS-TOTAL
The general syntax of the INVOKE statement is:
INVOKE {object-reference | class-name} "method-name"
[USING parameter-1 parameter-2 ...]
[RETURNING result-item]
The method name is specified as a literal string. While this may seem unusual compared to languages like Java where method calls use dot notation, it aligns with COBOL's tradition of using literals for external references (similar to how CALL uses string literals for program names, as discussed in Chapter 17).
37.4.2 Object References
Object references are special data items that hold pointers to object instances. They are declared using the OBJECT REFERENCE phrase:
01 WS-ACCOUNT-REF OBJECT REFERENCE BankAccount.
01 WS-GENERIC-REF OBJECT REFERENCE.
01 WS-SAVINGS-REF OBJECT REFERENCE SavingsAccount.
Key rules for object references:
- A typed object reference (e.g.,
OBJECT REFERENCE BankAccount) can hold a reference to an instance of BankAccount or any class that inherits from BankAccount. - An untyped object reference (
OBJECT REFERENCEwithout a class name) can hold a reference to any object. This is analogous to theObjecttype in Java, but it provides less compile-time type safety. - Object references can be compared using
IF ref-1 = ref-2to check whether two references point to the same object instance. - The special value
NULLrepresents a reference that points to no object. You can test for this withIF WS-ACCOUNT-REF = NULL. - You can use
SET WS-REF-1 TO WS-REF-2to copy an object reference (both will then point to the same object).
37.4.3 Object Lifecycle
Objects in OO COBOL follow a create-use-destroy lifecycle:
******************************************************************
* CLIENT PROGRAM - Demonstrating object lifecycle
* Requires: IBM Enterprise COBOL V4+
******************************************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. AccountDemo.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS BankAccount IS "BankAccount".
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ACCOUNT-1 OBJECT REFERENCE BankAccount.
01 WS-ACCOUNT-2 OBJECT REFERENCE BankAccount.
01 WS-BALANCE PIC S9(13)V99.
01 WS-RESULT PIC 9(2).
01 WS-HOLDER-NAME PIC X(50).
01 WS-ACCT-TYPE PIC X(10).
01 WS-OVERDRAFT PIC S9(9)V99.
01 WS-DEPOSIT-AMT PIC S9(13)V99.
01 WS-WITHDRAW-AMT PIC S9(13)V99.
01 WS-TOTAL-CREATED PIC 9(8).
01 WS-DISPLAY-BAL PIC -(13)9.99.
01 WS-ACCT-NUM PIC 9(10).
PROCEDURE DIVISION.
MAIN-LOGIC.
* Create first account
INVOKE BankAccount "NEW"
RETURNING WS-ACCOUNT-1
MOVE "JOHN SMITH" TO WS-HOLDER-NAME
MOVE "CHECKING" TO WS-ACCT-TYPE
MOVE 500.00 TO WS-OVERDRAFT
INVOKE WS-ACCOUNT-1 "initialize"
USING WS-HOLDER-NAME
WS-ACCT-TYPE
WS-OVERDRAFT
* Create second account
INVOKE BankAccount "NEW"
RETURNING WS-ACCOUNT-2
MOVE "JANE DOE" TO WS-HOLDER-NAME
MOVE "SAVINGS" TO WS-ACCT-TYPE
MOVE 0 TO WS-OVERDRAFT
INVOKE WS-ACCOUNT-2 "initialize"
USING WS-HOLDER-NAME
WS-ACCT-TYPE
WS-OVERDRAFT
* Perform transactions on account 1
MOVE 5000.00 TO WS-DEPOSIT-AMT
INVOKE WS-ACCOUNT-1 "deposit"
USING WS-DEPOSIT-AMT
RETURNING WS-RESULT
IF WS-RESULT = 0
DISPLAY "Deposit successful"
ELSE
DISPLAY "Deposit failed with code: " WS-RESULT
END-IF
* Withdraw from account 1
MOVE 1500.00 TO WS-WITHDRAW-AMT
INVOKE WS-ACCOUNT-1 "withdraw"
USING WS-WITHDRAW-AMT
RETURNING WS-RESULT
IF WS-RESULT = 0
DISPLAY "Withdrawal successful"
ELSE
DISPLAY "Withdrawal failed with code: " WS-RESULT
END-IF
* Check balance
INVOKE WS-ACCOUNT-1 "getBalance"
RETURNING WS-BALANCE
MOVE WS-BALANCE TO WS-DISPLAY-BAL
INVOKE WS-ACCOUNT-1 "getAccountNumber"
RETURNING WS-ACCT-NUM
DISPLAY "Account " WS-ACCT-NUM
" Balance: $" WS-DISPLAY-BAL
* Check factory data
INVOKE BankAccount "getTotalAccountsCreated"
RETURNING WS-TOTAL-CREATED
DISPLAY "Total accounts created: " WS-TOTAL-CREATED
STOP RUN.
37.5 Inheritance
37.5.1 The INHERITS Clause
Inheritance allows you to create new classes that extend existing ones, inheriting their data definitions and methods while adding new capabilities or overriding existing behavior. In COBOL, inheritance is specified using the INHERITS clause on the CLASS-ID paragraph.
IDENTIFICATION DIVISION.
CLASS-ID. SavingsAccount INHERITS BankAccount.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount"
CLASS SavingsAccount IS "SavingsAccount".
The child class (SavingsAccount) inherits all OBJECT WORKING-STORAGE data items and all methods from the parent class (BankAccount). It can then add new data items and methods or override inherited methods.
37.5.2 Method Overriding
When a child class defines a method with the same name as a method in the parent class, the child's method overrides the parent's. The OVERRIDE clause makes this explicit:
******************************************************************
* SAVINGSACCOUNT CLASS - Inherits from BankAccount
* Adds interest rate and interest calculation capability.
* Overrides withdraw to enforce minimum balance.
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. SavingsAccount INHERITS BankAccount.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount"
CLASS SavingsAccount IS "SavingsAccount".
FACTORY.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DEFAULT-INTEREST-RATE PIC 9V9(4) VALUE 0.0250.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. getDefaultInterestRate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-RATE PIC 9V9(4).
PROCEDURE DIVISION RETURNING LS-RATE.
MOVE WS-DEFAULT-INTEREST-RATE TO LS-RATE
GOBACK.
END METHOD getDefaultInterestRate.
IDENTIFICATION DIVISION.
METHOD-ID. setDefaultInterestRate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-RATE PIC 9V9(4).
PROCEDURE DIVISION USING LS-RATE.
MOVE LS-RATE TO WS-DEFAULT-INTEREST-RATE
GOBACK.
END METHOD setDefaultInterestRate.
END FACTORY.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-INTEREST-RATE PIC 9V9(4).
01 WS-MINIMUM-BALANCE PIC S9(13)V99 VALUE 100.00.
01 WS-ACCRUED-INTEREST PIC S9(13)V99 VALUE 0.
PROCEDURE DIVISION.
*-----------------------------------------------------------------
* Method: initSavings
* Extended initialization for savings-specific fields.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. initSavings.
DATA DIVISION.
LINKAGE SECTION.
01 LS-INTEREST-RATE PIC 9V9(4).
01 LS-MIN-BALANCE PIC S9(13)V99.
PROCEDURE DIVISION USING LS-INTEREST-RATE
LS-MIN-BALANCE.
MOVE LS-INTEREST-RATE TO WS-INTEREST-RATE
MOVE LS-MIN-BALANCE TO WS-MINIMUM-BALANCE
MOVE 0 TO WS-ACCRUED-INTEREST
GOBACK.
END METHOD initSavings.
*-----------------------------------------------------------------
* Method: withdraw (OVERRIDE)
* Overrides parent withdraw to enforce minimum balance
* requirement on savings accounts.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. withdraw OVERRIDE.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CURRENT-BALANCE PIC S9(13)V99.
01 WS-PROJECTED-BALANCE PIC S9(13)V99.
LINKAGE SECTION.
01 LS-AMOUNT PIC S9(13)V99.
01 LS-RESULT-CODE PIC 9(2).
88 RESULT-SUCCESS VALUE 0.
88 RESULT-INVALID-AMOUNT VALUE 1.
88 RESULT-ACCOUNT-INACTIVE VALUE 2.
88 RESULT-INSUFFICIENT VALUE 3.
88 RESULT-BELOW-MINIMUM VALUE 6.
PROCEDURE DIVISION USING LS-AMOUNT
RETURNING LS-RESULT-CODE.
* First get current balance via inherited method
INVOKE SELF "getBalance"
RETURNING WS-CURRENT-BALANCE
IF LS-AMOUNT <= 0
SET RESULT-INVALID-AMOUNT TO TRUE
GOBACK
END-IF
* Check if withdrawal would go below minimum
COMPUTE WS-PROJECTED-BALANCE =
WS-CURRENT-BALANCE - LS-AMOUNT
IF WS-PROJECTED-BALANCE < WS-MINIMUM-BALANCE
SET RESULT-BELOW-MINIMUM TO TRUE
GOBACK
END-IF
* Delegate to parent's withdraw via SUPER
INVOKE SUPER "withdraw"
USING LS-AMOUNT
RETURNING LS-RESULT-CODE
GOBACK.
END METHOD withdraw.
*-----------------------------------------------------------------
* Method: calculateInterest
* Computes interest on the current balance and adds it
* to the accrued interest total.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. calculateInterest.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CURRENT-BALANCE PIC S9(13)V99.
01 WS-INTEREST-AMOUNT PIC S9(13)V99.
LINKAGE SECTION.
01 LS-INTEREST PIC S9(13)V99.
PROCEDURE DIVISION RETURNING LS-INTEREST.
INVOKE SELF "getBalance"
RETURNING WS-CURRENT-BALANCE
COMPUTE WS-INTEREST-AMOUNT ROUNDED =
WS-CURRENT-BALANCE * WS-INTEREST-RATE / 12
ADD WS-INTEREST-AMOUNT TO WS-ACCRUED-INTEREST
MOVE WS-INTEREST-AMOUNT TO LS-INTEREST
GOBACK.
END METHOD calculateInterest.
*-----------------------------------------------------------------
* Method: applyAccruedInterest
* Deposits all accrued interest into the account balance.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. applyAccruedInterest.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DEPOSIT-RESULT PIC 9(2).
LINKAGE SECTION.
01 LS-APPLIED-AMOUNT PIC S9(13)V99.
PROCEDURE DIVISION RETURNING LS-APPLIED-AMOUNT.
MOVE WS-ACCRUED-INTEREST TO LS-APPLIED-AMOUNT
IF WS-ACCRUED-INTEREST > 0
INVOKE SELF "deposit"
USING WS-ACCRUED-INTEREST
RETURNING WS-DEPOSIT-RESULT
MOVE 0 TO WS-ACCRUED-INTEREST
END-IF
GOBACK.
END METHOD applyAccruedInterest.
END OBJECT.
END CLASS SavingsAccount.
37.5.3 SELF and SUPER References
Two special object references are available within instance methods:
-
SELF: Refers to the current object instance on which the method was invoked. Use SELF when you want to call another method on the same object. This ensures polymorphic dispatch: if the method has been overridden in a subclass, the subclass version will be called.
-
SUPER: Refers to the parent class's implementation of a method. Use SUPER when you have overridden a method but still want to invoke the parent's version as part of the child's logic. In the SavingsAccount
withdrawexample above,INVOKE SUPER "withdraw"calls BankAccount's original withdraw method after the minimum balance check passes.
37.6 Interfaces
37.6.1 INTERFACE-ID
Interfaces define a contract of methods that implementing classes must provide, without specifying the implementation. This is the COBOL equivalent of Java interfaces or C++ abstract classes with pure virtual methods.
******************************************************************
* AUDITABLE INTERFACE
* Any class implementing this interface must provide methods
* for audit trail functionality.
******************************************************************
IDENTIFICATION DIVISION.
INTERFACE-ID. Auditable.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. getAuditTrail.
DATA DIVISION.
LINKAGE SECTION.
01 LS-AUDIT-DATA PIC X(500).
PROCEDURE DIVISION RETURNING LS-AUDIT-DATA.
END METHOD getAuditTrail.
IDENTIFICATION DIVISION.
METHOD-ID. getLastModifiedDate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-MOD-DATE PIC 9(8).
PROCEDURE DIVISION RETURNING LS-MOD-DATE.
END METHOD getLastModifiedDate.
IDENTIFICATION DIVISION.
METHOD-ID. getModifiedBy.
DATA DIVISION.
LINKAGE SECTION.
01 LS-USER-ID PIC X(8).
PROCEDURE DIVISION RETURNING LS-USER-ID.
END METHOD getModifiedBy.
END INTERFACE Auditable.
37.6.2 Implementing Interfaces
A class implements an interface by listing it in the CLASS-ID paragraph and providing concrete implementations of all methods declared in the interface:
IDENTIFICATION DIVISION.
CLASS-ID. AuditableBankAccount
INHERITS BankAccount
IMPLEMENTS Auditable.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount"
CLASS AuditableBankAccount IS "AuditableBankAccount"
INTERFACE Auditable IS "Auditable".
A class can implement multiple interfaces, providing maximum flexibility:
CLASS-ID. PremiumAccount
INHERITS SavingsAccount
IMPLEMENTS Auditable Reportable Notifiable.
37.6.3 Interface-Based Polymorphism
One of the most powerful uses of interfaces is to write code that works with any class implementing a given interface, regardless of the class's position in the inheritance hierarchy:
* Process any auditable object
01 WS-AUDITABLE-REF OBJECT REFERENCE Auditable.
01 WS-AUDIT-DATA PIC X(500).
* This works with any class implementing Auditable
INVOKE WS-AUDITABLE-REF "getAuditTrail"
RETURNING WS-AUDIT-DATA
This allows you to write utility programs that process collections of auditable objects without knowing or caring whether they are bank accounts, loans, or investment portfolios, as long as they implement the Auditable interface.
37.7 Parameterized Classes and Methods
37.7.1 Generic Programming in COBOL
The COBOL 2002 standard introduced parameterized classes and methods, bringing a form of generic programming to COBOL. This feature allows you to write classes and methods that work with different data types specified at instantiation time.
Important Note: Parameterized classes are part of the COBOL 2002 standard but have limited compiler support. IBM Enterprise COBOL does not fully implement parameterized classes as defined in the standard. Micro Focus Visual COBOL provides partial support. This section describes the standard for completeness, but practical usage remains limited.
The concept behind parameterized classes can be illustrated in pseudocode:
CLASS-ID. Collection USING T.
OBJECT.
01 WS-ITEMS T OCCURS 100 TIMES.
METHOD-ID. add USING item T.
METHOD-ID. get RETURNING item T.
END OBJECT.
END CLASS.
37.7.2 Practical Alternatives
Because parameterized classes have limited compiler support, COBOL developers typically achieve similar results through other mechanisms:
-
Untyped Object References: Use
OBJECT REFERENCE(without a type qualifier) to hold references to any object type, then cast as needed. -
Interface-Based Generics: Define an interface that all items in a collection must implement, then write the collection class to work with the interface type.
-
REDEFINES-Based Type Flexibility: Use traditional COBOL REDEFINES to allow a data area to hold different types:
01 WS-GENERIC-VALUE.
05 WS-VALUE-TYPE PIC X.
88 TYPE-NUMERIC VALUE "N".
88 TYPE-ALPHANUMERIC VALUE "A".
88 TYPE-OBJECT-REF VALUE "O".
05 WS-VALUE-DATA PIC X(100).
05 WS-VALUE-NUMERIC REDEFINES WS-VALUE-DATA
PIC S9(18)V99.
05 WS-VALUE-OBJECT REDEFINES WS-VALUE-DATA
OBJECT REFERENCE.
37.8 OO Design Patterns in COBOL
Design patterns are reusable solutions to commonly occurring problems in software design. While they originated in the object-oriented programming community around languages like Smalltalk, C++, and Java, several patterns translate effectively into OO COBOL. Here we examine three patterns especially relevant to financial COBOL applications.
37.8.1 Factory Pattern
The Factory pattern provides a method for creating objects without specifying the exact class of object that will be created. This is particularly useful in banking where different account types share a common interface but have different initialization requirements.
******************************************************************
* ACCOUNTFACTORY CLASS
* Creates the appropriate account type based on input parameters.
* Encapsulates the decision logic for account creation.
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. AccountFactory INHERITS Base.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount"
CLASS SavingsAccount IS "SavingsAccount"
CLASS CheckingAccount IS "CheckingAccount"
CLASS MoneyMarket IS "MoneyMarket"
CLASS AccountFactory IS "AccountFactory".
FACTORY.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. createAccount.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-NEW-SAVINGS OBJECT REFERENCE SavingsAccount.
01 WS-NEW-CHECKING OBJECT REFERENCE CheckingAccount.
01 WS-NEW-MONEY-MKT OBJECT REFERENCE MoneyMarket.
01 WS-DEFAULT-RATE PIC 9V9(4).
01 WS-DEFAULT-MIN-BAL PIC S9(13)V99.
01 WS-ZERO-OVERDRAFT PIC S9(9)V99 VALUE 0.
01 WS-STD-OVERDRAFT PIC S9(9)V99 VALUE 500.00.
LINKAGE SECTION.
01 LS-ACCOUNT-TYPE PIC X(10).
01 LS-HOLDER-NAME PIC X(50).
01 LS-ACCOUNT-REF OBJECT REFERENCE BankAccount.
PROCEDURE DIVISION USING LS-ACCOUNT-TYPE
LS-HOLDER-NAME
RETURNING LS-ACCOUNT-REF.
EVALUATE LS-ACCOUNT-TYPE
WHEN "SAVINGS"
INVOKE SavingsAccount "NEW"
RETURNING WS-NEW-SAVINGS
INVOKE WS-NEW-SAVINGS "initialize"
USING LS-HOLDER-NAME
LS-ACCOUNT-TYPE
WS-ZERO-OVERDRAFT
MOVE 0.0250 TO WS-DEFAULT-RATE
MOVE 100.00 TO WS-DEFAULT-MIN-BAL
INVOKE WS-NEW-SAVINGS "initSavings"
USING WS-DEFAULT-RATE
WS-DEFAULT-MIN-BAL
SET LS-ACCOUNT-REF TO WS-NEW-SAVINGS
WHEN "CHECKING"
INVOKE CheckingAccount "NEW"
RETURNING WS-NEW-CHECKING
INVOKE WS-NEW-CHECKING "initialize"
USING LS-HOLDER-NAME
LS-ACCOUNT-TYPE
WS-STD-OVERDRAFT
SET LS-ACCOUNT-REF TO WS-NEW-CHECKING
WHEN "MONEYMARKET"
INVOKE MoneyMarket "NEW"
RETURNING WS-NEW-MONEY-MKT
INVOKE WS-NEW-MONEY-MKT "initialize"
USING LS-HOLDER-NAME
LS-ACCOUNT-TYPE
WS-ZERO-OVERDRAFT
MOVE 0.0375 TO WS-DEFAULT-RATE
MOVE 2500.00 TO WS-DEFAULT-MIN-BAL
INVOKE WS-NEW-MONEY-MKT "initSavings"
USING WS-DEFAULT-RATE
WS-DEFAULT-MIN-BAL
SET LS-ACCOUNT-REF TO WS-NEW-MONEY-MKT
WHEN OTHER
SET LS-ACCOUNT-REF TO NULL
END-EVALUATE
GOBACK.
END METHOD createAccount.
END FACTORY.
OBJECT.
PROCEDURE DIVISION.
END OBJECT.
END CLASS AccountFactory.
37.8.2 Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. In banking, this is ideal for implementing different fee calculation strategies, interest computation methods, or transaction validation rules.
******************************************************************
* INTERESTCALCULATOR INTERFACE
* Defines the strategy for calculating interest.
******************************************************************
IDENTIFICATION DIVISION.
INTERFACE-ID. InterestCalculator.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. calculateInterest.
DATA DIVISION.
LINKAGE SECTION.
01 LS-PRINCIPAL PIC S9(13)V99.
01 LS-RATE PIC 9V9(6).
01 LS-DAYS PIC 9(4).
01 LS-INTEREST PIC S9(13)V99.
PROCEDURE DIVISION USING LS-PRINCIPAL
LS-RATE
LS-DAYS
RETURNING LS-INTEREST.
END METHOD calculateInterest.
END INTERFACE InterestCalculator.
******************************************************************
* SIMPLEINTEREST CLASS
* Implements simple interest calculation: P * R * T
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. SimpleInterest INHERITS Base
IMPLEMENTS InterestCalculator.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS SimpleInterest IS "SimpleInterest"
INTERFACE InterestCalculator IS "InterestCalculator".
FACTORY.
PROCEDURE DIVISION.
END FACTORY.
OBJECT.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. calculateInterest.
DATA DIVISION.
LINKAGE SECTION.
01 LS-PRINCIPAL PIC S9(13)V99.
01 LS-RATE PIC 9V9(6).
01 LS-DAYS PIC 9(4).
01 LS-INTEREST PIC S9(13)V99.
PROCEDURE DIVISION USING LS-PRINCIPAL
LS-RATE
LS-DAYS
RETURNING LS-INTEREST.
COMPUTE LS-INTEREST ROUNDED =
LS-PRINCIPAL * LS-RATE * LS-DAYS / 365
GOBACK.
END METHOD calculateInterest.
END OBJECT.
END CLASS SimpleInterest.
******************************************************************
* COMPOUNDINTEREST CLASS
* Implements daily compound interest calculation.
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. CompoundInterest INHERITS Base
IMPLEMENTS InterestCalculator.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS CompoundInterest IS "CompoundInterest"
INTERFACE InterestCalculator IS "InterestCalculator".
FACTORY.
PROCEDURE DIVISION.
END FACTORY.
OBJECT.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. calculateInterest.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DAILY-RATE PIC 9V9(10).
01 WS-COMPOUND-FACTOR PIC 9(13)V9(10).
01 WS-FINAL-AMOUNT PIC S9(13)V99.
LINKAGE SECTION.
01 LS-PRINCIPAL PIC S9(13)V99.
01 LS-RATE PIC 9V9(6).
01 LS-DAYS PIC 9(4).
01 LS-INTEREST PIC S9(13)V99.
PROCEDURE DIVISION USING LS-PRINCIPAL
LS-RATE
LS-DAYS
RETURNING LS-INTEREST.
COMPUTE WS-DAILY-RATE = LS-RATE / 365
COMPUTE WS-COMPOUND-FACTOR =
(1 + WS-DAILY-RATE) ** LS-DAYS
COMPUTE WS-FINAL-AMOUNT ROUNDED =
LS-PRINCIPAL * WS-COMPOUND-FACTOR
COMPUTE LS-INTEREST ROUNDED =
WS-FINAL-AMOUNT - LS-PRINCIPAL
GOBACK.
END METHOD calculateInterest.
END OBJECT.
END CLASS CompoundInterest.
37.8.3 Observer Pattern
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified. In banking, this is useful for triggering notifications when account balances cross thresholds, when large transactions occur, or when suspicious activity is detected.
******************************************************************
* ACCOUNTOBSERVER INTERFACE
* Observers implement this interface to receive notifications
* about account events.
******************************************************************
IDENTIFICATION DIVISION.
INTERFACE-ID. AccountObserver.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. onTransaction.
DATA DIVISION.
LINKAGE SECTION.
01 LS-ACCT-NUM PIC 9(10).
01 LS-TXN-TYPE PIC X(10).
01 LS-AMOUNT PIC S9(13)V99.
01 LS-NEW-BALANCE PIC S9(13)V99.
PROCEDURE DIVISION USING LS-ACCT-NUM
LS-TXN-TYPE
LS-AMOUNT
LS-NEW-BALANCE.
END METHOD onTransaction.
END INTERFACE AccountObserver.
******************************************************************
* FRAUDDETECTOR CLASS
* Implements AccountObserver to watch for suspicious activity.
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. FraudDetector INHERITS Base
IMPLEMENTS AccountObserver.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS FraudDetector IS "FraudDetector"
INTERFACE AccountObserver IS "AccountObserver".
FACTORY.
PROCEDURE DIVISION.
END FACTORY.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-LARGE-TXN-THRESHOLD PIC S9(13)V99
VALUE 10000.00.
01 WS-ALERT-COUNT PIC 9(6) VALUE 0.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. onTransaction.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-DISPLAY-AMT PIC -(13)9.99.
LINKAGE SECTION.
01 LS-ACCT-NUM PIC 9(10).
01 LS-TXN-TYPE PIC X(10).
01 LS-AMOUNT PIC S9(13)V99.
01 LS-NEW-BALANCE PIC S9(13)V99.
PROCEDURE DIVISION USING LS-ACCT-NUM
LS-TXN-TYPE
LS-AMOUNT
LS-NEW-BALANCE.
IF LS-AMOUNT > WS-LARGE-TXN-THRESHOLD
OR LS-AMOUNT < -5000.00
ADD 1 TO WS-ALERT-COUNT
MOVE LS-AMOUNT TO WS-DISPLAY-AMT
DISPLAY "FRAUD ALERT: Account " LS-ACCT-NUM
DISPLAY " Transaction: " LS-TXN-TYPE
DISPLAY " Amount: $" WS-DISPLAY-AMT
DISPLAY " Alert #" WS-ALERT-COUNT
END-IF
GOBACK.
END METHOD onTransaction.
END OBJECT.
END CLASS FraudDetector.
To connect observers to an account, you would extend the BankAccount class to maintain a list of observers and notify them whenever a transaction occurs. The notification loop in the deposit and withdraw methods would iterate through the registered observers and invoke their onTransaction method.
37.9 Mixing OO and Procedural COBOL
37.9.1 The Reality of Migration
In practice, very few organizations rewrite their entire COBOL codebase in the object-oriented style. The existing procedural code works correctly, has been tested for decades, and rewriting it would introduce risk with little immediate business value. Instead, organizations adopt a gradual migration strategy where new functionality is written using OO COBOL while existing procedural code continues to operate alongside it.
37.9.2 Calling Procedural Programs from OO Methods
OO COBOL methods can use the traditional CALL statement to invoke existing procedural programs. This is the most common integration pattern:
IDENTIFICATION DIVISION.
METHOD-ID. validateTransaction.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TXN-RECORD.
05 WS-TXN-ACCT-NUM PIC 9(10).
05 WS-TXN-TYPE PIC X(2).
05 WS-TXN-AMOUNT PIC S9(13)V99.
05 WS-TXN-DATE PIC 9(8).
01 WS-VALIDATION-RESULT PIC X.
88 VALIDATION-PASSED VALUE "P".
88 VALIDATION-FAILED VALUE "F".
LINKAGE SECTION.
01 LS-AMOUNT PIC S9(13)V99.
01 LS-IS-VALID PIC 9.
PROCEDURE DIVISION USING LS-AMOUNT
RETURNING LS-IS-VALID.
* Call existing procedural validation program
* (See Chapter 17 for details on CALL and parameter passing)
INVOKE SELF "getAccountNumber"
RETURNING WS-TXN-ACCT-NUM
MOVE "DP" TO WS-TXN-TYPE
MOVE LS-AMOUNT TO WS-TXN-AMOUNT
MOVE FUNCTION CURRENT-DATE(1:8) TO WS-TXN-DATE
CALL "TXNVALID" USING WS-TXN-RECORD
WS-VALIDATION-RESULT
IF VALIDATION-PASSED
MOVE 1 TO LS-IS-VALID
ELSE
MOVE 0 TO LS-IS-VALID
END-IF
GOBACK.
END METHOD validateTransaction.
37.9.3 Using OO Objects from Procedural Programs
Procedural COBOL programs can create and use OO COBOL objects. The procedural program must include a REPOSITORY paragraph to reference the classes it uses:
******************************************************************
* PROCEDURAL PROGRAM USING OO CLASSES
* Demonstrates how a traditional COBOL program can leverage
* OO classes for specific operations while remaining procedural
* in overall structure.
******************************************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. DailyInterestRun.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS SavingsAccount IS "SavingsAccount"
CLASS AccountFactory IS "AccountFactory".
INPUT-OUTPUT SECTION.
FILE-CONTROL.
SELECT ACCOUNT-FILE ASSIGN TO "ACCTMAST"
ORGANIZATION IS INDEXED
ACCESS MODE IS SEQUENTIAL
RECORD KEY IS ACCT-KEY
FILE STATUS IS WS-FILE-STATUS.
DATA DIVISION.
FILE SECTION.
FD ACCOUNT-FILE.
01 ACCOUNT-RECORD.
05 ACCT-KEY PIC 9(10).
05 ACCT-HOLDER PIC X(50).
05 ACCT-TYPE PIC X(10).
05 ACCT-BALANCE PIC S9(13)V99.
05 ACCT-INT-RATE PIC 9V9(4).
05 ACCT-MIN-BAL PIC S9(13)V99.
05 ACCT-STATUS PIC 9.
WORKING-STORAGE SECTION.
01 WS-FILE-STATUS PIC XX.
01 WS-EOF-FLAG PIC 9 VALUE 0.
88 END-OF-FILE VALUE 1.
01 WS-SAVINGS-REF OBJECT REFERENCE SavingsAccount.
01 WS-INTEREST-AMT PIC S9(13)V99.
01 WS-APPLIED-AMT PIC S9(13)V99.
01 WS-HOLDER-NAME PIC X(50).
01 WS-ACCT-TYPE-WS PIC X(10).
01 WS-OVERDRAFT PIC S9(9)V99.
01 WS-INT-RATE PIC 9V9(4).
01 WS-MIN-BAL PIC S9(13)V99.
01 WS-DEPOSIT-AMT PIC S9(13)V99.
01 WS-DEP-RESULT PIC 9(2).
01 WS-ACCOUNTS-PROCESSED PIC 9(8) VALUE 0.
01 WS-TOTAL-INTEREST PIC S9(15)V99 VALUE 0.
01 WS-DISPLAY-AMT PIC -(15)9.99.
PROCEDURE DIVISION.
MAIN-LOGIC.
OPEN INPUT ACCOUNT-FILE
PERFORM READ-NEXT-ACCOUNT
PERFORM PROCESS-ACCOUNTS
UNTIL END-OF-FILE
CLOSE ACCOUNT-FILE
DISPLAY "Daily Interest Run Complete"
DISPLAY "Accounts Processed: " WS-ACCOUNTS-PROCESSED
MOVE WS-TOTAL-INTEREST TO WS-DISPLAY-AMT
DISPLAY "Total Interest Calculated: $" WS-DISPLAY-AMT
STOP RUN.
READ-NEXT-ACCOUNT.
READ ACCOUNT-FILE
AT END SET END-OF-FILE TO TRUE
END-READ.
PROCESS-ACCOUNTS.
IF ACCT-TYPE = "SAVINGS" OR "MONEYMARKET"
AND ACCT-STATUS = 1
* Create a temporary OO object to calculate interest
INVOKE SavingsAccount "NEW"
RETURNING WS-SAVINGS-REF
MOVE ACCT-HOLDER TO WS-HOLDER-NAME
MOVE ACCT-TYPE TO WS-ACCT-TYPE-WS
MOVE 0 TO WS-OVERDRAFT
INVOKE WS-SAVINGS-REF "initialize"
USING WS-HOLDER-NAME
WS-ACCT-TYPE-WS
WS-OVERDRAFT
MOVE ACCT-INT-RATE TO WS-INT-RATE
MOVE ACCT-MIN-BAL TO WS-MIN-BAL
INVOKE WS-SAVINGS-REF "initSavings"
USING WS-INT-RATE
WS-MIN-BAL
* Deposit current balance into the object
MOVE ACCT-BALANCE TO WS-DEPOSIT-AMT
INVOKE WS-SAVINGS-REF "deposit"
USING WS-DEPOSIT-AMT
RETURNING WS-DEP-RESULT
* Calculate and apply interest using OO methods
INVOKE WS-SAVINGS-REF "calculateInterest"
RETURNING WS-INTEREST-AMT
ADD WS-INTEREST-AMT TO WS-TOTAL-INTEREST
ADD 1 TO WS-ACCOUNTS-PROCESSED
END-IF
PERFORM READ-NEXT-ACCOUNT.
37.9.4 Gradual Migration Strategies
Here is a practical roadmap for introducing OO COBOL into an existing procedural codebase:
Phase 1 - Wrapper Classes: Create OO classes that wrap existing procedural programs. The class methods internally use CALL to invoke the existing programs but present an object-oriented interface to new code.
Phase 2 - New Features as Classes: Write all new business functionality as OO COBOL classes. Existing programs continue to run unchanged, but new modules benefit from encapsulation, inheritance, and polymorphism.
Phase 3 - Shared Services: Identify common cross-cutting concerns (logging, validation, error handling) and implement them as reusable OO classes or interfaces. Gradually refactor existing programs to use these shared services.
Phase 4 - Domain Model: Over time, build a domain model of classes that represents the core business entities (accounts, customers, transactions). This model becomes the authoritative representation of business logic, with the legacy programs delegating to it.
This phased approach minimizes risk while steadily modernizing the codebase. The key principle is that procedural and OO COBOL can coexist peacefully within the same application.
37.10 Practical Example: Building a Banking Account Class Hierarchy
37.10.1 Design Overview
Let us build a comprehensive class hierarchy for a banking application. The hierarchy models different types of accounts with shared base functionality and specialized behavior:
BankAccount (base class)
|-- CheckingAccount
| |-- PremiumCheckingAccount
|-- SavingsAccount
| |-- MoneyMarketAccount
| |-- CertificateOfDeposit
|-- LoanAccount
Interfaces:
- InterestBearing - for accounts that earn or charge interest
- FeeChargeable - for accounts that incur fees
- Auditable - for accounts requiring audit trails
37.10.2 CheckingAccount Class
******************************************************************
* CHECKINGACCOUNT CLASS
* Extends BankAccount with check-writing capability,
* monthly fees, and free transaction limits.
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. CheckingAccount INHERITS BankAccount.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount"
CLASS CheckingAccount IS "CheckingAccount".
FACTORY.
PROCEDURE DIVISION.
END FACTORY.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MONTHLY-FEE PIC S9(5)V99 VALUE 12.00.
01 WS-FREE-TXN-LIMIT PIC 9(4) VALUE 50.
01 WS-PER-TXN-FEE PIC S9(3)V99 VALUE 0.50.
01 WS-MONTHLY-TXN-COUNT PIC 9(6) VALUE 0.
01 WS-NEXT-CHECK-NUMBER PIC 9(6) VALUE 1001.
01 WS-CHECK-REGISTER.
05 WS-CHECK-ENTRY OCCURS 100 TIMES.
10 WS-CHK-NUMBER PIC 9(6).
10 WS-CHK-PAYEE PIC X(50).
10 WS-CHK-AMOUNT PIC S9(13)V99.
10 WS-CHK-DATE PIC 9(8).
10 WS-CHK-STATUS PIC X.
88 CHECK-CLEARED VALUE "C".
88 CHECK-PENDING VALUE "P".
88 CHECK-VOIDED VALUE "V".
01 WS-CHECK-REG-COUNT PIC 9(3) VALUE 0.
PROCEDURE DIVISION.
*-----------------------------------------------------------------
* Method: initChecking
* Initializes checking-specific attributes.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. initChecking.
DATA DIVISION.
LINKAGE SECTION.
01 LS-MONTHLY-FEE PIC S9(5)V99.
01 LS-FREE-TXN-LIMIT PIC 9(4).
PROCEDURE DIVISION USING LS-MONTHLY-FEE
LS-FREE-TXN-LIMIT.
MOVE LS-MONTHLY-FEE TO WS-MONTHLY-FEE
MOVE LS-FREE-TXN-LIMIT TO WS-FREE-TXN-LIMIT
MOVE 0 TO WS-MONTHLY-TXN-COUNT
MOVE 0 TO WS-CHECK-REG-COUNT
GOBACK.
END METHOD initChecking.
*-----------------------------------------------------------------
* Method: writeCheck
* Writes a check against the account balance.
* Records the check in the internal register.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. writeCheck.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-WITHDRAW-RESULT PIC 9(2).
01 WS-CHECK-IDX PIC 9(3).
01 WS-CURRENT-DATE-DATA.
05 WS-TODAY PIC 9(8).
05 FILLER PIC X(13).
LINKAGE SECTION.
01 LS-PAYEE PIC X(50).
01 LS-AMOUNT PIC S9(13)V99.
01 LS-CHECK-NUM PIC 9(6).
01 LS-RESULT PIC 9(2).
88 CHECK-SUCCESS VALUE 0.
88 CHECK-NSF VALUE 3.
88 CHECK-REGISTER-FULL VALUE 7.
PROCEDURE DIVISION USING LS-PAYEE
LS-AMOUNT
RETURNING LS-CHECK-NUM
LS-RESULT.
IF WS-CHECK-REG-COUNT >= 100
SET CHECK-REGISTER-FULL TO TRUE
MOVE 0 TO LS-CHECK-NUM
GOBACK
END-IF
* Attempt withdrawal for the check amount
INVOKE SELF "withdraw"
USING LS-AMOUNT
RETURNING WS-WITHDRAW-RESULT
IF WS-WITHDRAW-RESULT NOT = 0
SET CHECK-NSF TO TRUE
MOVE 0 TO LS-CHECK-NUM
GOBACK
END-IF
* Record check in register
ADD 1 TO WS-CHECK-REG-COUNT
MOVE WS-CHECK-REG-COUNT TO WS-CHECK-IDX
MOVE WS-NEXT-CHECK-NUMBER
TO WS-CHK-NUMBER(WS-CHECK-IDX)
MOVE LS-PAYEE TO WS-CHK-PAYEE(WS-CHECK-IDX)
MOVE LS-AMOUNT TO WS-CHK-AMOUNT(WS-CHECK-IDX)
MOVE FUNCTION CURRENT-DATE TO WS-CURRENT-DATE-DATA
MOVE WS-TODAY TO WS-CHK-DATE(WS-CHECK-IDX)
SET CHECK-PENDING IN
WS-CHK-STATUS(WS-CHECK-IDX) TO TRUE
MOVE WS-NEXT-CHECK-NUMBER TO LS-CHECK-NUM
ADD 1 TO WS-NEXT-CHECK-NUMBER
ADD 1 TO WS-MONTHLY-TXN-COUNT
SET CHECK-SUCCESS TO TRUE
GOBACK.
END METHOD writeCheck.
*-----------------------------------------------------------------
* Method: assessMonthlyFees
* Calculates and deducts monthly maintenance fee plus any
* excess transaction fees.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. assessMonthlyFees.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TOTAL-FEES PIC S9(9)V99.
01 WS-EXCESS-TXNS PIC 9(6).
01 WS-EXCESS-FEES PIC S9(9)V99.
01 WS-FEE-RESULT PIC 9(2).
LINKAGE SECTION.
01 LS-FEES-CHARGED PIC S9(9)V99.
PROCEDURE DIVISION RETURNING LS-FEES-CHARGED.
MOVE WS-MONTHLY-FEE TO WS-TOTAL-FEES
IF WS-MONTHLY-TXN-COUNT > WS-FREE-TXN-LIMIT
COMPUTE WS-EXCESS-TXNS =
WS-MONTHLY-TXN-COUNT - WS-FREE-TXN-LIMIT
COMPUTE WS-EXCESS-FEES =
WS-EXCESS-TXNS * WS-PER-TXN-FEE
ADD WS-EXCESS-FEES TO WS-TOTAL-FEES
END-IF
* Deduct fees via withdrawal
INVOKE SELF "withdraw"
USING WS-TOTAL-FEES
RETURNING WS-FEE-RESULT
MOVE WS-TOTAL-FEES TO LS-FEES-CHARGED
MOVE 0 TO WS-MONTHLY-TXN-COUNT
GOBACK.
END METHOD assessMonthlyFees.
END OBJECT.
END CLASS CheckingAccount.
37.10.3 LoanAccount Class
******************************************************************
* LOANACCOUNT CLASS
* Represents a loan with principal, interest, and payment
* schedule. Balance is negative (owed by customer).
******************************************************************
IDENTIFICATION DIVISION.
CLASS-ID. LoanAccount INHERITS BankAccount.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS Base IS "Base"
CLASS BankAccount IS "BankAccount"
CLASS LoanAccount IS "LoanAccount".
FACTORY.
PROCEDURE DIVISION.
END FACTORY.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-ORIGINAL-PRINCIPAL PIC S9(13)V99.
01 WS-INTEREST-RATE PIC 9V9(6).
01 WS-TERM-MONTHS PIC 9(4).
01 WS-MONTHLY-PAYMENT PIC S9(9)V99.
01 WS-PAYMENTS-MADE PIC 9(4) VALUE 0.
01 WS-TOTAL-INTEREST-PAID PIC S9(13)V99 VALUE 0.
01 WS-LOAN-STATUS PIC X.
88 LOAN-ACTIVE VALUE "A".
88 LOAN-PAID-OFF VALUE "P".
88 LOAN-DELINQUENT VALUE "D".
88 LOAN-DEFAULT VALUE "X".
01 WS-MISSED-PAYMENTS PIC 9(2) VALUE 0.
PROCEDURE DIVISION.
*-----------------------------------------------------------------
* Method: initLoan
* Sets up loan-specific parameters and calculates the
* monthly payment using the standard amortization formula.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. initLoan.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-MONTHLY-RATE PIC 9V9(10).
01 WS-POWER-FACTOR PIC 9(13)V9(10).
01 WS-NUMERATOR PIC 9(13)V9(10).
01 WS-DENOMINATOR PIC 9(13)V9(10).
LINKAGE SECTION.
01 LS-PRINCIPAL PIC S9(13)V99.
01 LS-ANNUAL-RATE PIC 9V9(6).
01 LS-TERM-MONTHS PIC 9(4).
PROCEDURE DIVISION USING LS-PRINCIPAL
LS-ANNUAL-RATE
LS-TERM-MONTHS.
MOVE LS-PRINCIPAL TO WS-ORIGINAL-PRINCIPAL
MOVE LS-ANNUAL-RATE TO WS-INTEREST-RATE
MOVE LS-TERM-MONTHS TO WS-TERM-MONTHS
SET LOAN-ACTIVE TO TRUE
MOVE 0 TO WS-PAYMENTS-MADE
MOVE 0 TO WS-TOTAL-INTEREST-PAID
MOVE 0 TO WS-MISSED-PAYMENTS
* Calculate monthly payment:
* M = P * [r(1+r)^n] / [(1+r)^n - 1]
* where r = monthly rate, n = total months
COMPUTE WS-MONTHLY-RATE =
WS-INTEREST-RATE / 12
COMPUTE WS-POWER-FACTOR =
(1 + WS-MONTHLY-RATE) ** WS-TERM-MONTHS
COMPUTE WS-NUMERATOR =
WS-MONTHLY-RATE * WS-POWER-FACTOR
COMPUTE WS-DENOMINATOR =
WS-POWER-FACTOR - 1
COMPUTE WS-MONTHLY-PAYMENT ROUNDED =
LS-PRINCIPAL * WS-NUMERATOR / WS-DENOMINATOR
GOBACK.
END METHOD initLoan.
*-----------------------------------------------------------------
* Method: makePayment
* Processes a loan payment, splitting it between principal
* and interest based on the current balance.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. makePayment.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CURRENT-BALANCE PIC S9(13)V99.
01 WS-INTEREST-PORTION PIC S9(9)V99.
01 WS-PRINCIPAL-PORTION PIC S9(9)V99.
01 WS-MONTHLY-RATE PIC 9V9(10).
01 WS-DEPOSIT-RESULT PIC 9(2).
LINKAGE SECTION.
01 LS-PAYMENT-AMOUNT PIC S9(9)V99.
01 LS-INTEREST-APPLIED PIC S9(9)V99.
01 LS-PRINCIPAL-APPLIED PIC S9(9)V99.
01 LS-REMAINING-BALANCE PIC S9(13)V99.
01 LS-RESULT-CODE PIC 9(2).
88 PAYMENT-SUCCESS VALUE 0.
88 PAYMENT-INVALID VALUE 1.
88 PAYMENT-LOAN-CLOSED VALUE 8.
PROCEDURE DIVISION USING LS-PAYMENT-AMOUNT
RETURNING LS-INTEREST-APPLIED
LS-PRINCIPAL-APPLIED
LS-REMAINING-BALANCE
LS-RESULT-CODE.
IF LOAN-PAID-OFF
SET PAYMENT-LOAN-CLOSED TO TRUE
GOBACK
END-IF
IF LS-PAYMENT-AMOUNT <= 0
SET PAYMENT-INVALID TO TRUE
GOBACK
END-IF
* Calculate interest and principal portions
INVOKE SELF "getBalance"
RETURNING WS-CURRENT-BALANCE
* Balance is negative for loans (owed amount)
COMPUTE WS-MONTHLY-RATE =
WS-INTEREST-RATE / 12
COMPUTE WS-INTEREST-PORTION ROUNDED =
FUNCTION ABS(WS-CURRENT-BALANCE)
* WS-MONTHLY-RATE
COMPUTE WS-PRINCIPAL-PORTION =
LS-PAYMENT-AMOUNT - WS-INTEREST-PORTION
ADD WS-INTEREST-PORTION TO WS-TOTAL-INTEREST-PAID
* Apply payment (deposit increases balance toward zero)
INVOKE SELF "deposit"
USING LS-PAYMENT-AMOUNT
RETURNING WS-DEPOSIT-RESULT
ADD 1 TO WS-PAYMENTS-MADE
MOVE 0 TO WS-MISSED-PAYMENTS
INVOKE SELF "getBalance"
RETURNING WS-CURRENT-BALANCE
IF WS-CURRENT-BALANCE >= 0
SET LOAN-PAID-OFF TO TRUE
END-IF
MOVE WS-INTEREST-PORTION TO LS-INTEREST-APPLIED
MOVE WS-PRINCIPAL-PORTION TO LS-PRINCIPAL-APPLIED
MOVE WS-CURRENT-BALANCE TO LS-REMAINING-BALANCE
SET PAYMENT-SUCCESS TO TRUE
GOBACK.
END METHOD makePayment.
*-----------------------------------------------------------------
* Method: getAmortizationInfo
* Returns current loan status information.
*-----------------------------------------------------------------
IDENTIFICATION DIVISION.
METHOD-ID. getAmortizationInfo.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-CURR-BAL PIC S9(13)V99.
LINKAGE SECTION.
01 LS-LOAN-INFO.
05 LS-ORIG-PRINCIPAL PIC S9(13)V99.
05 LS-CURR-BALANCE PIC S9(13)V99.
05 LS-MONTHLY-PMT PIC S9(9)V99.
05 LS-PAYMENTS-MADE PIC 9(4).
05 LS-PAYMENTS-REMAIN PIC 9(4).
05 LS-TOTAL-INT-PAID PIC S9(13)V99.
05 LS-STATUS PIC X.
PROCEDURE DIVISION RETURNING LS-LOAN-INFO.
MOVE WS-ORIGINAL-PRINCIPAL TO LS-ORIG-PRINCIPAL
INVOKE SELF "getBalance"
RETURNING WS-CURR-BAL
MOVE WS-CURR-BAL TO LS-CURR-BALANCE
MOVE WS-MONTHLY-PAYMENT TO LS-MONTHLY-PMT
MOVE WS-PAYMENTS-MADE TO LS-PAYMENTS-MADE
COMPUTE LS-PAYMENTS-REMAIN =
WS-TERM-MONTHS - WS-PAYMENTS-MADE
MOVE WS-TOTAL-INTEREST-PAID TO LS-TOTAL-INT-PAID
MOVE WS-LOAN-STATUS TO LS-STATUS
GOBACK.
END METHOD getAmortizationInfo.
END OBJECT.
END CLASS LoanAccount.
37.10.4 Putting It All Together: Account Portfolio Manager
The following client program demonstrates the full class hierarchy in action, managing a portfolio of different account types:
******************************************************************
* PORTFOLIO MANAGER - Client program
* Demonstrates polymorphic behavior across the account
* class hierarchy. Creates multiple account types and
* processes transactions uniformly.
* Requires: IBM Enterprise COBOL V4+
******************************************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. PortfolioManager.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS BankAccount IS "BankAccount"
CLASS CheckingAccount IS "CheckingAccount"
CLASS SavingsAccount IS "SavingsAccount"
CLASS LoanAccount IS "LoanAccount"
CLASS AccountFactory IS "AccountFactory".
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PORTFOLIO.
05 WS-ACCT-REF OBJECT REFERENCE BankAccount
OCCURS 10 TIMES.
01 WS-PORTFOLIO-SIZE PIC 9(2) VALUE 0.
01 WS-IDX PIC 9(2).
01 WS-ACCT-TYPE PIC X(10).
01 WS-HOLDER-NAME PIC X(50).
01 WS-BALANCE PIC S9(13)V99.
01 WS-DISPLAY-BAL PIC -(13)9.99.
01 WS-ACCT-NUM PIC 9(10).
01 WS-RESULT PIC 9(2).
01 WS-DEPOSIT-AMT PIC S9(13)V99.
01 WS-TOTAL-ASSETS PIC S9(15)V99 VALUE 0.
01 WS-DISPLAY-TOTAL PIC -(15)9.99.
PROCEDURE DIVISION.
MAIN-LOGIC.
PERFORM CREATE-ACCOUNTS
PERFORM PROCESS-TRANSACTIONS
PERFORM DISPLAY-PORTFOLIO-SUMMARY
STOP RUN.
CREATE-ACCOUNTS.
* Create a checking account
MOVE "CHECKING" TO WS-ACCT-TYPE
MOVE "ALICE JOHNSON" TO WS-HOLDER-NAME
INVOKE AccountFactory "createAccount"
USING WS-ACCT-TYPE WS-HOLDER-NAME
RETURNING WS-ACCT-REF(1)
MOVE 1 TO WS-PORTFOLIO-SIZE
* Create a savings account
MOVE "SAVINGS" TO WS-ACCT-TYPE
MOVE "ALICE JOHNSON" TO WS-HOLDER-NAME
INVOKE AccountFactory "createAccount"
USING WS-ACCT-TYPE WS-HOLDER-NAME
RETURNING WS-ACCT-REF(2)
MOVE 2 TO WS-PORTFOLIO-SIZE
* Create a money market account
MOVE "MONEYMARKET" TO WS-ACCT-TYPE
MOVE "ALICE JOHNSON" TO WS-HOLDER-NAME
INVOKE AccountFactory "createAccount"
USING WS-ACCT-TYPE WS-HOLDER-NAME
RETURNING WS-ACCT-REF(3)
MOVE 3 TO WS-PORTFOLIO-SIZE.
PROCESS-TRANSACTIONS.
* Deposit to checking
MOVE 5000.00 TO WS-DEPOSIT-AMT
INVOKE WS-ACCT-REF(1) "deposit"
USING WS-DEPOSIT-AMT
RETURNING WS-RESULT
* Deposit to savings
MOVE 10000.00 TO WS-DEPOSIT-AMT
INVOKE WS-ACCT-REF(2) "deposit"
USING WS-DEPOSIT-AMT
RETURNING WS-RESULT
* Deposit to money market
MOVE 25000.00 TO WS-DEPOSIT-AMT
INVOKE WS-ACCT-REF(3) "deposit"
USING WS-DEPOSIT-AMT
RETURNING WS-RESULT.
DISPLAY-PORTFOLIO-SUMMARY.
DISPLAY "============================================"
DISPLAY " PORTFOLIO SUMMARY - ALICE JOHNSON"
DISPLAY "============================================"
MOVE 0 TO WS-TOTAL-ASSETS
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-PORTFOLIO-SIZE
* Polymorphic call - works for any account type
INVOKE WS-ACCT-REF(WS-IDX) "getAccountNumber"
RETURNING WS-ACCT-NUM
INVOKE WS-ACCT-REF(WS-IDX) "getBalance"
RETURNING WS-BALANCE
MOVE WS-BALANCE TO WS-DISPLAY-BAL
DISPLAY "Account: " WS-ACCT-NUM
" Balance: $" WS-DISPLAY-BAL
ADD WS-BALANCE TO WS-TOTAL-ASSETS
END-PERFORM
DISPLAY "--------------------------------------------"
MOVE WS-TOTAL-ASSETS TO WS-DISPLAY-TOTAL
DISPLAY "Total Portfolio Value: $" WS-DISPLAY-TOTAL
DISPLAY "============================================".
37.11 Compiler Support
37.11.1 IBM Enterprise COBOL OO Features
IBM Enterprise COBOL for z/OS provides the most comprehensive OO COBOL support among mainframe compilers. Key features and their version availability:
| Feature | Enterprise COBOL Version |
|---|---|
| CLASS-ID, METHOD-ID, OBJECT, FACTORY | V4.1+ |
| INHERITS clause | V4.1+ |
| INTERFACE-ID | V4.1+ |
| INVOKE statement | V4.1+ |
| Object references | V4.1+ |
| SELF and SUPER | V4.1+ |
| Method overriding (OVERRIDE) | V4.1+ |
| Java interoperability | V4.1+ (via OO COBOL-Java bridge) |
| Multiple interface implementation | V4.2+ |
IBM's implementation requires the Language Environment (LE) runtime and works within the z/OS environment. OO COBOL programs on z/OS can interoperate with Java through the COBOL-Java bridge, allowing COBOL objects to invoke Java methods and vice versa.
IBM Compiler Options for OO COBOL:
- THREAD - Required for OO COBOL programs that use Java interoperability
- RENT - Reentrant code, required for OO COBOL
- DLL - Dynamic link library support, needed for class loading
37.11.2 Micro Focus Visual COBOL
Micro Focus Visual COBOL provides extensive OO support that extends beyond the standard with features specific to its managed code environments:
- Full COBOL 2002 OO syntax support
- Integration with .NET Framework (COBOL classes can inherit from .NET classes)
- Integration with JVM (COBOL classes can be deployed as Java classes)
- Visual Studio and Eclipse IDE support for OO COBOL development
- Additional syntax for properties, delegates, and events
37.11.3 GnuCOBOL Limitations
GnuCOBOL (formerly OpenCOBOL), the primary open-source COBOL compiler, does not support OO COBOL features as of version 3.2. The GnuCOBOL project has discussed OO support on its roadmap, but implementation has not been prioritized because:
- The majority of GnuCOBOL's user base focuses on migrating and running legacy procedural COBOL programs.
- OO COBOL's complexity adds significant compiler development effort.
- Organizations using OO COBOL typically operate on IBM mainframes with Enterprise COBOL.
Workarounds for GnuCOBOL Users:
If you are learning on GnuCOBOL and want to explore object-oriented concepts, consider these alternatives:
- Structured Procedural Design: Use COBOL's COPY, nested programs, and subprogram calling conventions (Chapter 17) to achieve modular, well-encapsulated designs without OO syntax.
- Module-Based Encapsulation: Create separate COBOL programs for each "class," using a WORKING-STORAGE-based record to represent instance data that is passed between the programs via the CALL statement.
- External Language Bridge: Use GnuCOBOL's C interoperability to call C or C++ libraries that implement OO logic, with the COBOL program handling business data and the C layer handling object management.
Here is an example of simulating OO concepts in GnuCOBOL using procedural techniques:
******************************************************************
* SIMULATED OO PATTERN FOR GNUCOBOL
* Uses a "class module" program with operation dispatch
* to simulate object-oriented behavior.
*
* This program acts as both factory and instance manager
* for BankAccount "objects" represented as data records.
*
* Compatible with: GnuCOBOL 2.x and 3.x
******************************************************************
IDENTIFICATION DIVISION.
PROGRAM-ID. BankAccountModule.
DATA DIVISION.
WORKING-STORAGE SECTION.
* Instance storage - simulates object pool
01 WS-MAX-INSTANCES PIC 9(4) VALUE 100.
01 WS-INSTANCE-COUNT PIC 9(4) VALUE 0.
01 WS-INSTANCE-TABLE.
05 WS-INSTANCE OCCURS 100 TIMES.
10 WS-INST-ID PIC 9(4).
10 WS-INST-ACCT-NUM PIC 9(10).
10 WS-INST-HOLDER PIC X(50).
10 WS-INST-BALANCE PIC S9(13)V99.
10 WS-INST-TYPE PIC X(10).
10 WS-INST-STATUS PIC 9.
01 WS-NEXT-ACCT-NUM PIC 9(10) VALUE 1000000001.
LINKAGE SECTION.
01 LS-OPERATION PIC X(15).
01 LS-INSTANCE-ID PIC 9(4).
01 LS-PARAM-1 PIC X(50).
01 LS-PARAM-2 PIC X(10).
01 LS-AMOUNT PIC S9(13)V99.
01 LS-RESULT-CODE PIC 9(2).
01 LS-RESULT-DATA PIC X(100).
PROCEDURE DIVISION USING LS-OPERATION
LS-INSTANCE-ID
LS-PARAM-1
LS-PARAM-2
LS-AMOUNT
LS-RESULT-CODE
LS-RESULT-DATA.
MAIN-DISPATCH.
EVALUATE LS-OPERATION
WHEN "NEW"
PERFORM CREATE-INSTANCE
WHEN "INITIALIZE"
PERFORM INIT-INSTANCE
WHEN "DEPOSIT"
PERFORM DEPOSIT-FUNDS
WHEN "WITHDRAW"
PERFORM WITHDRAW-FUNDS
WHEN "GETBALANCE"
PERFORM GET-BALANCE
WHEN OTHER
MOVE 99 TO LS-RESULT-CODE
END-EVALUATE
GOBACK.
CREATE-INSTANCE.
IF WS-INSTANCE-COUNT >= WS-MAX-INSTANCES
MOVE 90 TO LS-RESULT-CODE
GOBACK
END-IF
ADD 1 TO WS-INSTANCE-COUNT
MOVE WS-INSTANCE-COUNT TO LS-INSTANCE-ID
MOVE WS-INSTANCE-COUNT TO WS-INST-ID(
WS-INSTANCE-COUNT)
MOVE 0 TO WS-INST-BALANCE(WS-INSTANCE-COUNT)
MOVE 1 TO WS-INST-STATUS(WS-INSTANCE-COUNT)
MOVE 0 TO LS-RESULT-CODE.
INIT-INSTANCE.
MOVE LS-PARAM-1 TO
WS-INST-HOLDER(LS-INSTANCE-ID)
MOVE LS-PARAM-2 TO
WS-INST-TYPE(LS-INSTANCE-ID)
MOVE WS-NEXT-ACCT-NUM TO
WS-INST-ACCT-NUM(LS-INSTANCE-ID)
ADD 1 TO WS-NEXT-ACCT-NUM
MOVE 0 TO LS-RESULT-CODE.
DEPOSIT-FUNDS.
IF LS-AMOUNT <= 0
MOVE 1 TO LS-RESULT-CODE
GOBACK
END-IF
ADD LS-AMOUNT TO
WS-INST-BALANCE(LS-INSTANCE-ID)
MOVE 0 TO LS-RESULT-CODE.
WITHDRAW-FUNDS.
IF LS-AMOUNT <= 0
MOVE 1 TO LS-RESULT-CODE
GOBACK
END-IF
IF LS-AMOUNT >
WS-INST-BALANCE(LS-INSTANCE-ID)
MOVE 3 TO LS-RESULT-CODE
GOBACK
END-IF
SUBTRACT LS-AMOUNT FROM
WS-INST-BALANCE(LS-INSTANCE-ID)
MOVE 0 TO LS-RESULT-CODE.
GET-BALANCE.
MOVE WS-INST-BALANCE(LS-INSTANCE-ID)
TO LS-AMOUNT
MOVE 0 TO LS-RESULT-CODE.
The calling program would use this module like this:
* Client program using the simulated OO module
* Compatible with: GnuCOBOL 2.x and 3.x
IDENTIFICATION DIVISION.
PROGRAM-ID. AccountClient.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-OPERATION PIC X(15).
01 WS-INST-ID PIC 9(4).
01 WS-PARAM-1 PIC X(50).
01 WS-PARAM-2 PIC X(10).
01 WS-AMOUNT PIC S9(13)V99.
01 WS-RESULT PIC 9(2).
01 WS-RESULT-DATA PIC X(100).
01 WS-DISPLAY-BAL PIC -(13)9.99.
PROCEDURE DIVISION.
MAIN-LOGIC.
* "Create" a new account object
MOVE "NEW" TO WS-OPERATION
CALL "BankAccountModule" USING
WS-OPERATION WS-INST-ID
WS-PARAM-1 WS-PARAM-2
WS-AMOUNT WS-RESULT WS-RESULT-DATA
* "Initialize" it
MOVE "INITIALIZE" TO WS-OPERATION
MOVE "JOHN SMITH" TO WS-PARAM-1
MOVE "CHECKING" TO WS-PARAM-2
CALL "BankAccountModule" USING
WS-OPERATION WS-INST-ID
WS-PARAM-1 WS-PARAM-2
WS-AMOUNT WS-RESULT WS-RESULT-DATA
* "Deposit" funds
MOVE "DEPOSIT" TO WS-OPERATION
MOVE 5000.00 TO WS-AMOUNT
CALL "BankAccountModule" USING
WS-OPERATION WS-INST-ID
WS-PARAM-1 WS-PARAM-2
WS-AMOUNT WS-RESULT WS-RESULT-DATA
* "Get balance"
MOVE "GETBALANCE" TO WS-OPERATION
CALL "BankAccountModule" USING
WS-OPERATION WS-INST-ID
WS-PARAM-1 WS-PARAM-2
WS-AMOUNT WS-RESULT WS-RESULT-DATA
MOVE WS-AMOUNT TO WS-DISPLAY-BAL
DISPLAY "Balance: $" WS-DISPLAY-BAL
STOP RUN.
37.12 When to Use OO COBOL vs Traditional Procedural Style
37.12.1 Arguments for OO COBOL
Object-oriented COBOL provides clear advantages in specific scenarios:
Complex Domain Models: When the business domain involves many interrelated entity types with specialized behavior (different account types, different product categories, different regulatory frameworks), OO COBOL's class hierarchies provide a natural way to model these relationships. The inheritance mechanism eliminates code duplication while allowing specialized behavior.
Reusable Components: OO classes are inherently more reusable than procedural programs because they encapsulate both data and behavior. A well-designed BankAccount class can be used across multiple applications without modification, while a procedural program often requires adaptation for each new context.
Team Development: On large teams, OO COBOL's encapsulation reduces the risk of unintended interactions between modules. Each class hides its implementation details behind a method interface, so changes to a class's internal logic do not affect other parts of the system as long as the method signatures remain stable.
Java/C# Interoperability: If your organization is building systems that bridge COBOL and Java or .NET, OO COBOL provides a much smoother integration path. COBOL classes can participate directly in Java class hierarchies on IBM platforms, and in .NET hierarchies on Micro Focus platforms.
37.12.2 Arguments for Procedural COBOL
Traditional procedural COBOL remains the better choice in many situations:
Existing Codebase: If you are maintaining or extending a large existing procedural codebase, introducing OO COBOL creates a mixture of paradigms that can confuse maintainers. Unless there is a compelling architectural reason, consistency with the existing style is usually more valuable than theoretical OO benefits.
Batch Processing: COBOL's traditional strength is high-volume batch processing of sequential files. These programs read records, apply business rules, and write results. The procedural style maps naturally onto this flow, and OO abstraction adds overhead without clear benefit.
Team Skills: Many COBOL programmers have decades of experience with procedural COBOL but limited exposure to OO concepts. Introducing OO COBOL without adequate training can lead to poorly designed class hierarchies that are harder to maintain than the procedural code they replaced.
Performance Sensitivity: OO COBOL introduces runtime overhead for object creation, method dispatch, and garbage collection. In performance-critical batch processing that handles millions of records, this overhead can be meaningful. Procedural COBOL with its direct data manipulation and predictable memory layout remains the fastest option.
Compiler Availability: If your environment uses GnuCOBOL or another compiler without OO support, the question is moot. Procedural COBOL is your only option, and the structured design techniques described in Chapter 17 provide adequate modularization for most applications.
37.12.3 A Balanced Approach
The most pragmatic approach for most organizations is to use procedural COBOL for the core business logic that has been proven over decades, while selectively introducing OO COBOL for new subsystems where the domain complexity justifies it. The techniques in Section 37.9 for mixing procedural and OO code enable this hybrid strategy.
Consider OO COBOL when you are building something genuinely new rather than extending something that already works. Consider procedural COBOL when you are working within an established system and consistency matters more than theoretical elegance.
Summary
Object-oriented COBOL extends the language's capabilities with classes, inheritance, interfaces, and polymorphism while preserving the familiar COBOL syntax and data description strengths. The key concepts covered in this chapter include:
- CLASS-ID defines a class compilation unit with FACTORY and OBJECT paragraphs.
- FACTORY contains class-level (shared) data and methods, analogous to static members.
- OBJECT contains instance-level data and methods, unique to each created object.
- METHOD-ID defines methods within FACTORY or OBJECT, each with its own divisions.
- INVOKE calls methods on objects or classes, replacing CALL for OO interactions.
- INHERITS establishes parent-child class relationships, enabling code reuse.
- OVERRIDE allows child classes to replace inherited method behavior.
- SELF and SUPER reference the current object and parent implementation respectively.
- INTERFACE-ID defines contracts that classes can implement for polymorphic behavior.
- Design patterns like Factory, Strategy, and Observer adapt well to COBOL's OO features.
- Mixed paradigm development lets OO and procedural COBOL coexist and interoperate.
The financial domain examples throughout this chapter demonstrate that OO COBOL is not merely a theoretical exercise but a practical tool for modeling complex business relationships. However, the decision to adopt OO COBOL should be driven by genuine architectural needs rather than fashion, and procedural COBOL remains the right choice for many workloads.
In the next chapter, we will explore how COBOL programs -- whether procedural or object-oriented -- integrate with modern APIs, web services, and microservice architectures, bridging the gap between mainframe business logic and contemporary distributed systems.
Review Questions
-
Explain the difference between FACTORY WORKING-STORAGE and OBJECT WORKING-STORAGE. When would you use each?
-
Write a CLASS-ID paragraph for a
CreditCardAccountclass that inherits fromBankAccountand implements bothInterestBearingandFeeChargeableinterfaces. -
Describe the difference between INVOKE SELF and INVOKE SUPER. Provide a scenario where using the wrong one would produce incorrect results.
-
Why does GnuCOBOL not support OO COBOL features? What procedural techniques can you use as alternatives?
-
In the Factory design pattern example, why does the
createAccountmethod return anOBJECT REFERENCE BankAccountrather than a reference to the specific account type? -
When migrating a procedural COBOL codebase to OO, why is a phased approach recommended over a complete rewrite? What risks does a complete rewrite introduce?
-
Explain how interface-based polymorphism allows a utility program to process different types of accounts without knowing their specific class.
-
Compare the memory allocation behavior of procedural COBOL WORKING-STORAGE with OO COBOL's per-instance data. How does this affect programs that process millions of records?
Exercises
Exercise 37.1: Define a CertificateOfDeposit class that inherits from SavingsAccount. It should have a maturity date, an early withdrawal penalty rate (e.g., 10% of interest earned), and a method that calculates the penalty for early withdrawal based on how many months remain until maturity.
Exercise 37.2: Implement the FeeChargeable interface with methods calculateFees, assessFees, and waiveFees. Then implement this interface in the CheckingAccount class.
Exercise 37.3: Create a TransactionLog observer class that implements AccountObserver and writes all transaction notifications to a sequential file. Each record should include the account number, transaction type, amount, new balance, and a timestamp.
Exercise 37.4: Using the GnuCOBOL-compatible procedural simulation pattern from Section 37.11.3, implement a SavingsAccountModule that adds interest calculation to the base BankAccountModule pattern. Demonstrate how procedural code can achieve method-override-like behavior through operation dispatch tables.
Exercise 37.5: Design and implement a CustomerPortfolio class that holds references to multiple BankAccount objects (of any subtype) and provides methods to calculate total assets, total liabilities (from LoanAccounts), and net worth. Demonstrate polymorphic iteration over the portfolio's accounts.