Case Study 1: Banking Account Class Hierarchy

Background

Northeast Federal Credit Union serves 320,000 members across four states. Their core banking system, originally written in procedural COBOL in 1992, has grown to over 400,000 lines of code. Account processing logic is scattered across dozens of programs, with each account type handled by duplicated and slightly divergent code paths. A change to the monthly fee calculation, for example, requires modifications in seven different programs -- each handling a different account type but sharing 80% of the same logic.

The maintenance team has been tasked with redesigning the account processing subsystem using Object-Oriented COBOL. The goals are clear: eliminate duplicated logic through inheritance, enable easy addition of new account types, and provide a uniform processing interface through polymorphism. The existing procedural code will be wrapped and gradually replaced using the new class hierarchy.

This case study walks through the design and implementation of an Account base class with SavingsAccount and CheckingAccount subclasses, demonstrating method overriding for interest calculation, polymorphic account processing, and the practical application of OO principles in a banking context.


Problem Statement

The credit union needs a class hierarchy that:

  1. Defines a common Account base class with shared attributes (account number, owner name, balance, status) and shared operations (deposit, withdraw, get balance).
  2. Implements a SavingsAccount subclass that adds an interest rate attribute and overrides the monthly processing method to calculate and apply interest.
  3. Implements a CheckingAccount subclass that adds an overdraft limit and monthly fee, overriding the monthly processing method to deduct the fee and check the minimum balance.
  4. Allows a processing program to iterate over a mixed collection of account types using a single base-class reference, invoking the correct monthly processing logic for each account through polymorphism.

Data Design

The base Account class defines the attributes common to all account types. The subclasses add their specialized attributes.

Account Base Class

       IDENTIFICATION DIVISION.
       CLASS-ID. Account.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           CLASS Account.

       IDENTIFICATION DIVISION.
       FACTORY.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-NEXT-ACCT-NUM  PIC 9(10) VALUE 1000000001.
       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. New.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-OWNER-NAME     PIC X(40).
       01  LS-INIT-BALANCE   PIC 9(11)V99.
       01  LS-NEW-ACCT       USAGE OBJECT REFERENCE Account.
       PROCEDURE DIVISION
           USING LS-OWNER-NAME LS-INIT-BALANCE
           RETURNING LS-NEW-ACCT.
       CREATE-ACCOUNT.
           INVOKE SELF "NEW" RETURNING LS-NEW-ACCT
           INVOKE LS-NEW-ACCT "Init"
               USING WS-NEXT-ACCT-NUM
                     LS-OWNER-NAME
                     LS-INIT-BALANCE
           ADD 1 TO WS-NEXT-ACCT-NUM
           .
       END METHOD New.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetNextAccountNumber.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-NUM            PIC 9(10).
       PROCEDURE DIVISION RETURNING LS-NUM.
       GET-NUM.
           MOVE WS-NEXT-ACCT-NUM TO LS-NUM
           .
       END METHOD GetNextAccountNumber.

       END FACTORY.

       IDENTIFICATION DIVISION.
       OBJECT.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ACCT-NUMBER    PIC 9(10).
       01  WS-OWNER-NAME     PIC X(40).
       01  WS-BALANCE        PIC S9(11)V99 VALUE 0.
       01  WS-ACCT-STATUS    PIC X(1) VALUE "A".
           88 ACCT-ACTIVE                   VALUE "A".
           88 ACCT-FROZEN                   VALUE "F".
           88 ACCT-CLOSED                   VALUE "C".
       01  WS-ACCT-TYPE-DESC PIC X(20) VALUE "BASE ACCOUNT".
       01  WS-LAST-TRANS-DATE PIC 9(8) VALUE 0.
       01  WS-MONTHLY-LOG.
           05  WS-LOG-ENTRY  OCCURS 50 TIMES.
               10  WS-LOG-TYPE   PIC X(10).
               10  WS-LOG-AMT   PIC S9(11)V99.
               10  WS-LOG-DATE  PIC 9(8).
       01  WS-LOG-COUNT      PIC 9(3) VALUE 0.
       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. Init.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-ACCT-NUM       PIC 9(10).
       01  LS-OWNER          PIC X(40).
       01  LS-BALANCE        PIC 9(11)V99.
       PROCEDURE DIVISION
           USING LS-ACCT-NUM LS-OWNER LS-BALANCE.
       DO-INIT.
           MOVE LS-ACCT-NUM TO WS-ACCT-NUMBER
           MOVE LS-OWNER    TO WS-OWNER-NAME
           MOVE LS-BALANCE  TO WS-BALANCE
           SET ACCT-ACTIVE TO TRUE
           MOVE 0 TO WS-LOG-COUNT
           .
       END METHOD Init.

       IDENTIFICATION DIVISION.
       METHOD-ID. Deposit.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-AMOUNT         PIC 9(11)V99.
       01  LS-SUCCESS        PIC 9.
       PROCEDURE DIVISION
           USING LS-AMOUNT
           RETURNING LS-SUCCESS.
       DO-DEPOSIT.
           IF NOT ACCT-ACTIVE
               MOVE 0 TO LS-SUCCESS
           ELSE IF LS-AMOUNT <= 0
               MOVE 0 TO LS-SUCCESS
           ELSE
               ADD LS-AMOUNT TO WS-BALANCE
               PERFORM LOG-TRANSACTION
               MOVE 1 TO LS-SUCCESS
           END-IF
           .
       LOG-TRANSACTION.
           IF WS-LOG-COUNT < 50
               ADD 1 TO WS-LOG-COUNT
               MOVE "DEPOSIT" TO WS-LOG-TYPE(WS-LOG-COUNT)
               MOVE LS-AMOUNT TO WS-LOG-AMT(WS-LOG-COUNT)
               MOVE FUNCTION CURRENT-DATE(1:8)
                   TO WS-LOG-DATE(WS-LOG-COUNT)
           END-IF
           .
       END METHOD Deposit.

       IDENTIFICATION DIVISION.
       METHOD-ID. Withdraw.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-MAX-ALLOWED    PIC S9(11)V99.
       LINKAGE SECTION.
       01  LS-AMOUNT         PIC 9(11)V99.
       01  LS-SUCCESS        PIC 9.
       PROCEDURE DIVISION
           USING LS-AMOUNT
           RETURNING LS-SUCCESS.
       DO-WITHDRAW.
           IF NOT ACCT-ACTIVE
               MOVE 0 TO LS-SUCCESS
           ELSE IF LS-AMOUNT <= 0
               MOVE 0 TO LS-SUCCESS
           ELSE
               INVOKE SELF "GetMaxWithdrawal"
                   RETURNING WS-MAX-ALLOWED
               IF LS-AMOUNT > WS-MAX-ALLOWED
                   MOVE 0 TO LS-SUCCESS
               ELSE
                   SUBTRACT LS-AMOUNT FROM WS-BALANCE
                   PERFORM LOG-WITHDRAWAL
                   MOVE 1 TO LS-SUCCESS
               END-IF
           END-IF
           .
       LOG-WITHDRAWAL.
           IF WS-LOG-COUNT < 50
               ADD 1 TO WS-LOG-COUNT
               MOVE "WITHDRAWAL" TO WS-LOG-TYPE(WS-LOG-COUNT)
               MOVE LS-AMOUNT TO WS-LOG-AMT(WS-LOG-COUNT)
               MOVE FUNCTION CURRENT-DATE(1:8)
                   TO WS-LOG-DATE(WS-LOG-COUNT)
           END-IF
           .
       END METHOD Withdraw.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetMaxWithdrawal.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-MAX            PIC S9(11)V99.
       PROCEDURE DIVISION RETURNING LS-MAX.
       GET-MAX.
           MOVE WS-BALANCE TO LS-MAX
           .
       END METHOD GetMaxWithdrawal.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetBalance.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-BALANCE        PIC S9(11)V99.
       PROCEDURE DIVISION RETURNING LS-BALANCE.
       GET-BAL.
           MOVE WS-BALANCE TO LS-BALANCE
           .
       END METHOD GetBalance.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetAccountNumber.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-NUM            PIC 9(10).
       PROCEDURE DIVISION RETURNING LS-NUM.
       GET-NUM.
           MOVE WS-ACCT-NUMBER TO LS-NUM
           .
       END METHOD GetAccountNumber.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetOwnerName.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-NAME           PIC X(40).
       PROCEDURE DIVISION RETURNING LS-NAME.
       GET-NAME.
           MOVE WS-OWNER-NAME TO LS-NAME
           .
       END METHOD GetOwnerName.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetAccountType.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-TYPE           PIC X(20).
       PROCEDURE DIVISION RETURNING LS-TYPE.
       GET-TYPE.
           MOVE WS-ACCT-TYPE-DESC TO LS-TYPE
           .
       END METHOD GetAccountType.

       IDENTIFICATION DIVISION.
       METHOD-ID. ProcessMonthEnd.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-SUMMARY        PIC X(80).
       PROCEDURE DIVISION RETURNING LS-SUMMARY.
       DO-MONTH-END.
           STRING "Account " WS-ACCT-NUMBER
               " - No monthly processing defined"
               DELIMITED SIZE
               INTO LS-SUMMARY
           MOVE 0 TO WS-LOG-COUNT
           .
       END METHOD ProcessMonthEnd.

       IDENTIFICATION DIVISION.
       METHOD-ID. PrintStatement.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-IDX            PIC 9(3).
       01  WS-FORMATTED-BAL  PIC -(11)9.99.
       01  WS-FORMATTED-AMT  PIC -(11)9.99.
       PROCEDURE DIVISION.
       DO-PRINT.
           MOVE WS-BALANCE TO WS-FORMATTED-BAL
           DISPLAY "========================================"
           DISPLAY "  ACCOUNT STATEMENT"
           DISPLAY "  Account: " WS-ACCT-NUMBER
           DISPLAY "  Owner:   " WS-OWNER-NAME
           DISPLAY "  Type:    " WS-ACCT-TYPE-DESC
           DISPLAY "  Balance: " WS-FORMATTED-BAL
           DISPLAY "  Status:  " WS-ACCT-STATUS
           DISPLAY "----------------------------------------"
           DISPLAY "  RECENT TRANSACTIONS:"
           IF WS-LOG-COUNT = 0
               DISPLAY "    (No transactions this period)"
           ELSE
               PERFORM VARYING WS-IDX FROM 1 BY 1
                   UNTIL WS-IDX > WS-LOG-COUNT
                   MOVE WS-LOG-AMT(WS-IDX) TO WS-FORMATTED-AMT
                   DISPLAY "    " WS-LOG-TYPE(WS-IDX)
                       "  " WS-FORMATTED-AMT
                       "  " WS-LOG-DATE(WS-IDX)
               END-PERFORM
           END-IF
           DISPLAY "========================================"
           .
       END METHOD PrintStatement.

       END OBJECT.
       END CLASS Account.

The SavingsAccount Subclass

The SavingsAccount inherits all base Account behavior and adds interest rate processing. The key override is ProcessMonthEnd, which calculates and applies monthly interest.

       IDENTIFICATION DIVISION.
       CLASS-ID. SavingsAccount INHERITS Account.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           CLASS Account
           CLASS SavingsAccount.

       IDENTIFICATION DIVISION.
       FACTORY.
       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. NewSavings.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-OWNER          PIC X(40).
       01  LS-INIT-BAL       PIC 9(11)V99.
       01  LS-INT-RATE       PIC 9V9(4).
       01  LS-MIN-BAL        PIC 9(11)V99.
       01  LS-NEW-ACCT       USAGE OBJECT REFERENCE
                              SavingsAccount.
       PROCEDURE DIVISION
           USING LS-OWNER LS-INIT-BAL LS-INT-RATE LS-MIN-BAL
           RETURNING LS-NEW-ACCT.
       CREATE-SAVINGS.
           INVOKE SELF "NEW" RETURNING LS-NEW-ACCT
           INVOKE LS-NEW-ACCT "Init"
               USING LS-OWNER LS-INIT-BAL
           INVOKE LS-NEW-ACCT "InitSavings"
               USING LS-INT-RATE LS-MIN-BAL
           .
       END METHOD NewSavings.

       END FACTORY.

       IDENTIFICATION DIVISION.
       OBJECT.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-INTEREST-RATE  PIC 9V9(4) VALUE 0.
       01  WS-MIN-BALANCE    PIC 9(11)V99 VALUE 0.
       01  WS-MONTHLY-INTEREST PIC 9(9)V99 VALUE 0.
       01  WS-ACCT-TYPE-DESC PIC X(20)
                              VALUE "SAVINGS".
       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. InitSavings.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-RATE           PIC 9V9(4).
       01  LS-MIN            PIC 9(11)V99.
       PROCEDURE DIVISION
           USING LS-RATE LS-MIN.
       DO-INIT.
           MOVE LS-RATE TO WS-INTEREST-RATE
           MOVE LS-MIN  TO WS-MIN-BALANCE
           .
       END METHOD InitSavings.

       IDENTIFICATION DIVISION.
       METHOD-ID. ProcessMonthEnd.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CURRENT-BAL    PIC S9(11)V99.
       01  WS-MONTHLY-RATE   PIC 9V9(8).
       01  WS-INTEREST-AMT   PIC 9(9)V99.
       01  WS-DEPOSIT-OK     PIC 9.
       01  WS-FMT-INT        PIC Z(8)9.99.
       LINKAGE SECTION.
       01  LS-SUMMARY        PIC X(80).
       PROCEDURE DIVISION RETURNING LS-SUMMARY.
       DO-MONTH-END.
           INVOKE SELF "GetBalance"
               RETURNING WS-CURRENT-BAL

           COMPUTE WS-MONTHLY-RATE =
               WS-INTEREST-RATE / 12
           COMPUTE WS-INTEREST-AMT ROUNDED =
               WS-CURRENT-BAL * WS-MONTHLY-RATE

           IF WS-INTEREST-AMT > 0
               INVOKE SELF "Deposit"
                   USING WS-INTEREST-AMT
                   RETURNING WS-DEPOSIT-OK
           END-IF

           MOVE WS-INTEREST-AMT TO WS-MONTHLY-INTEREST
           MOVE WS-INTEREST-AMT TO WS-FMT-INT

           EVALUATE TRUE
               WHEN WS-CURRENT-BAL < WS-MIN-BALANCE
                   STRING "SAVINGS - Interest: " WS-FMT-INT
                       " ** BELOW MINIMUM BALANCE **"
                       DELIMITED SIZE
                       INTO LS-SUMMARY
               WHEN OTHER
                   STRING "SAVINGS - Interest credited: "
                       WS-FMT-INT
                       DELIMITED SIZE
                       INTO LS-SUMMARY
           END-EVALUATE
           .
       END METHOD ProcessMonthEnd.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetInterestRate.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-RATE           PIC 9V9(4).
       PROCEDURE DIVISION RETURNING LS-RATE.
       GET-RATE.
           MOVE WS-INTEREST-RATE TO LS-RATE
           .
       END METHOD GetInterestRate.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetMonthlyInterest.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-INT            PIC 9(9)V99.
       PROCEDURE DIVISION RETURNING LS-INT.
       GET-INT.
           MOVE WS-MONTHLY-INTEREST TO LS-INT
           .
       END METHOD GetMonthlyInterest.

       END OBJECT.
       END CLASS SavingsAccount.

The CheckingAccount Subclass

The CheckingAccount adds overdraft protection and a monthly maintenance fee. Its ProcessMonthEnd override deducts the fee and checks the minimum balance.

       IDENTIFICATION DIVISION.
       CLASS-ID. CheckingAccount INHERITS Account.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           CLASS Account
           CLASS CheckingAccount.

       IDENTIFICATION DIVISION.
       FACTORY.
       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. NewChecking.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-OWNER          PIC X(40).
       01  LS-INIT-BAL       PIC 9(11)V99.
       01  LS-OVERDRAFT      PIC 9(7)V99.
       01  LS-MONTHLY-FEE    PIC 9(5)V99.
       01  LS-NEW-ACCT       USAGE OBJECT REFERENCE
                              CheckingAccount.
       PROCEDURE DIVISION
           USING LS-OWNER LS-INIT-BAL
                 LS-OVERDRAFT LS-MONTHLY-FEE
           RETURNING LS-NEW-ACCT.
       CREATE-CHECKING.
           INVOKE SELF "NEW" RETURNING LS-NEW-ACCT
           INVOKE LS-NEW-ACCT "Init"
               USING LS-OWNER LS-INIT-BAL
           INVOKE LS-NEW-ACCT "InitChecking"
               USING LS-OVERDRAFT LS-MONTHLY-FEE
           .
       END METHOD NewChecking.

       END FACTORY.

       IDENTIFICATION DIVISION.
       OBJECT.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-OVERDRAFT-LIMIT PIC 9(7)V99 VALUE 0.
       01  WS-MONTHLY-FEE    PIC 9(5)V99 VALUE 0.
       01  WS-TRANS-COUNT     PIC 9(5) VALUE 0.
       01  WS-FREE-TRANS      PIC 9(3) VALUE 30.
       01  WS-PER-TRANS-FEE   PIC 9(3)V99 VALUE 0.50.
       01  WS-ACCT-TYPE-DESC  PIC X(20)
                               VALUE "CHECKING".
       PROCEDURE DIVISION.

       IDENTIFICATION DIVISION.
       METHOD-ID. InitChecking.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-OVERDRAFT      PIC 9(7)V99.
       01  LS-FEE            PIC 9(5)V99.
       PROCEDURE DIVISION
           USING LS-OVERDRAFT LS-FEE.
       DO-INIT.
           MOVE LS-OVERDRAFT TO WS-OVERDRAFT-LIMIT
           MOVE LS-FEE       TO WS-MONTHLY-FEE
           MOVE 0            TO WS-TRANS-COUNT
           .
       END METHOD InitChecking.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetMaxWithdrawal.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CURRENT-BAL    PIC S9(11)V99.
       LINKAGE SECTION.
       01  LS-MAX            PIC S9(11)V99.
       PROCEDURE DIVISION RETURNING LS-MAX.
       GET-MAX.
           INVOKE SELF "GetBalance"
               RETURNING WS-CURRENT-BAL
           COMPUTE LS-MAX =
               WS-CURRENT-BAL + WS-OVERDRAFT-LIMIT
           .
       END METHOD GetMaxWithdrawal.

       IDENTIFICATION DIVISION.
       METHOD-ID. ProcessMonthEnd.
       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-CURRENT-BAL    PIC S9(11)V99.
       01  WS-TOTAL-FEES     PIC 9(7)V99 VALUE 0.
       01  WS-EXCESS-TRANS   PIC 9(5).
       01  WS-EXCESS-FEE     PIC 9(7)V99.
       01  WS-WITHDRAW-OK    PIC 9.
       01  WS-FMT-FEE        PIC Z(6)9.99.
       LINKAGE SECTION.
       01  LS-SUMMARY        PIC X(80).
       PROCEDURE DIVISION RETURNING LS-SUMMARY.
       DO-MONTH-END.
           MOVE WS-MONTHLY-FEE TO WS-TOTAL-FEES

           IF WS-TRANS-COUNT > WS-FREE-TRANS
               COMPUTE WS-EXCESS-TRANS =
                   WS-TRANS-COUNT - WS-FREE-TRANS
               COMPUTE WS-EXCESS-FEE =
                   WS-EXCESS-TRANS * WS-PER-TRANS-FEE
               ADD WS-EXCESS-FEE TO WS-TOTAL-FEES
           END-IF

           IF WS-TOTAL-FEES > 0
               INVOKE SELF "Withdraw"
                   USING WS-TOTAL-FEES
                   RETURNING WS-WITHDRAW-OK
           END-IF

           MOVE WS-TOTAL-FEES TO WS-FMT-FEE

           INVOKE SELF "GetBalance"
               RETURNING WS-CURRENT-BAL

           EVALUATE TRUE
               WHEN WS-CURRENT-BAL < 0
                   STRING "CHECKING - Fees: " WS-FMT-FEE
                       " ** OVERDRAWN **"
                       DELIMITED SIZE
                       INTO LS-SUMMARY
               WHEN WS-WITHDRAW-OK = 0
                   STRING "CHECKING - Fee deduction FAILED: "
                       WS-FMT-FEE
                       DELIMITED SIZE
                       INTO LS-SUMMARY
               WHEN OTHER
                   STRING "CHECKING - Monthly fees deducted: "
                       WS-FMT-FEE
                       DELIMITED SIZE
                       INTO LS-SUMMARY
           END-EVALUATE

           MOVE 0 TO WS-TRANS-COUNT
           .
       END METHOD ProcessMonthEnd.

       IDENTIFICATION DIVISION.
       METHOD-ID. GetOverdraftLimit.
       DATA DIVISION.
       LINKAGE SECTION.
       01  LS-LIMIT          PIC 9(7)V99.
       PROCEDURE DIVISION RETURNING LS-LIMIT.
       GET-LIM.
           MOVE WS-OVERDRAFT-LIMIT TO LS-LIMIT
           .
       END METHOD GetOverdraftLimit.

       END OBJECT.
       END CLASS CheckingAccount.

Solution Walkthrough: Polymorphic Month-End Processing

The power of this design becomes clear in the month-end processing program. A single loop processes all account types uniformly, yet each account receives the correct type-specific treatment.

       IDENTIFICATION DIVISION.
       PROGRAM-ID. MonthEndProcessor.

       ENVIRONMENT DIVISION.
       CONFIGURATION SECTION.
       REPOSITORY.
           CLASS Account
           CLASS SavingsAccount
           CLASS CheckingAccount.

       DATA DIVISION.
       WORKING-STORAGE SECTION.
       01  WS-ACCT-TABLE.
           05  WS-ACCT-REF    USAGE OBJECT REFERENCE Account
                               OCCURS 10 TIMES.
       01  WS-ACCT-COUNT      PIC 99 VALUE 0.
       01  WS-IDX             PIC 99.

       01  WS-SAV-REF         USAGE OBJECT REFERENCE
                               SavingsAccount.
       01  WS-CHK-REF         USAGE OBJECT REFERENCE
                               CheckingAccount.

       01  WS-SUMMARY         PIC X(80).
       01  WS-BALANCE         PIC S9(11)V99.
       01  WS-FMT-BAL         PIC -(11)9.99.
       01  WS-ACCT-NUM        PIC 9(10).
       01  WS-OWNER           PIC X(40).
       01  WS-ACCT-TYPE       PIC X(20).
       01  WS-DEP-OK          PIC 9.
       01  WS-WDR-OK          PIC 9.

       01  WS-OWNER1          PIC X(40)
                               VALUE "MARTINEZ, ELENA".
       01  WS-OWNER2          PIC X(40)
                               VALUE "CHEN, ROBERT".
       01  WS-OWNER3          PIC X(40)
                               VALUE "WASHINGTON, DIANA".
       01  WS-OWNER4          PIC X(40)
                               VALUE "PATEL, ARUN".
       01  WS-INIT-BAL1       PIC 9(11)V99 VALUE 25000.00.
       01  WS-INIT-BAL2       PIC 9(11)V99 VALUE 8500.00.
       01  WS-INIT-BAL3       PIC 9(11)V99 VALUE 3200.00.
       01  WS-INIT-BAL4       PIC 9(11)V99 VALUE 42000.00.
       01  WS-RATE1           PIC 9V9(4) VALUE 0.0350.
       01  WS-RATE2           PIC 9V9(4) VALUE 0.0425.
       01  WS-MIN-BAL         PIC 9(11)V99 VALUE 1000.00.
       01  WS-OVERDRAFT1      PIC 9(7)V99 VALUE 500.00.
       01  WS-OVERDRAFT2      PIC 9(7)V99 VALUE 2000.00.
       01  WS-FEE1            PIC 9(5)V99 VALUE 12.00.
       01  WS-FEE2            PIC 9(5)V99 VALUE 8.50.
       01  WS-DEP-AMT         PIC 9(11)V99.
       01  WS-WDR-AMT         PIC 9(11)V99.

       01  WS-TOTAL-ASSETS    PIC S9(13)V99 VALUE 0.
       01  WS-FMT-TOTAL       PIC -(13)9.99.

       PROCEDURE DIVISION.
       0000-MAIN.
           PERFORM 1000-CREATE-ACCOUNTS
           PERFORM 2000-SIMULATE-TRANSACTIONS
           PERFORM 3000-DISPLAY-PRE-PROCESSING
           PERFORM 4000-MONTH-END-PROCESSING
           PERFORM 5000-DISPLAY-POST-PROCESSING
           STOP RUN
           .

       1000-CREATE-ACCOUNTS.
      *    Create two savings accounts
           INVOKE SavingsAccount "NewSavings"
               USING WS-OWNER1 WS-INIT-BAL1
                     WS-RATE1 WS-MIN-BAL
               RETURNING WS-SAV-REF
           SET WS-ACCT-REF(1) TO WS-SAV-REF
           ADD 1 TO WS-ACCT-COUNT

           INVOKE SavingsAccount "NewSavings"
               USING WS-OWNER2 WS-INIT-BAL2
                     WS-RATE2 WS-MIN-BAL
               RETURNING WS-SAV-REF
           SET WS-ACCT-REF(2) TO WS-SAV-REF
           ADD 1 TO WS-ACCT-COUNT

      *    Create two checking accounts
           INVOKE CheckingAccount "NewChecking"
               USING WS-OWNER3 WS-INIT-BAL3
                     WS-OVERDRAFT1 WS-FEE1
               RETURNING WS-CHK-REF
           SET WS-ACCT-REF(3) TO WS-CHK-REF
           ADD 1 TO WS-ACCT-COUNT

           INVOKE CheckingAccount "NewChecking"
               USING WS-OWNER4 WS-INIT-BAL4
                     WS-OVERDRAFT2 WS-FEE2
               RETURNING WS-CHK-REF
           SET WS-ACCT-REF(4) TO WS-CHK-REF
           ADD 1 TO WS-ACCT-COUNT
           .

       2000-SIMULATE-TRANSACTIONS.
           MOVE 1500.00 TO WS-DEP-AMT
           INVOKE WS-ACCT-REF(1) "Deposit"
               USING WS-DEP-AMT
               RETURNING WS-DEP-OK

           MOVE 200.00 TO WS-WDR-AMT
           INVOKE WS-ACCT-REF(2) "Withdraw"
               USING WS-WDR-AMT
               RETURNING WS-WDR-OK

           MOVE 750.00 TO WS-DEP-AMT
           INVOKE WS-ACCT-REF(3) "Deposit"
               USING WS-DEP-AMT
               RETURNING WS-DEP-OK

           MOVE 5000.00 TO WS-WDR-AMT
           INVOKE WS-ACCT-REF(4) "Withdraw"
               USING WS-WDR-AMT
               RETURNING WS-WDR-OK
           .

       3000-DISPLAY-PRE-PROCESSING.
           DISPLAY "============================================"
           DISPLAY "  PRE-MONTH-END ACCOUNT SUMMARY"
           DISPLAY "============================================"
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-ACCT-COUNT
               INVOKE WS-ACCT-REF(WS-IDX) "GetAccountNumber"
                   RETURNING WS-ACCT-NUM
               INVOKE WS-ACCT-REF(WS-IDX) "GetOwnerName"
                   RETURNING WS-OWNER
               INVOKE WS-ACCT-REF(WS-IDX) "GetAccountType"
                   RETURNING WS-ACCT-TYPE
               INVOKE WS-ACCT-REF(WS-IDX) "GetBalance"
                   RETURNING WS-BALANCE
               MOVE WS-BALANCE TO WS-FMT-BAL
               DISPLAY "  " WS-ACCT-NUM " "
                   WS-ACCT-TYPE " " WS-FMT-BAL
           END-PERFORM
           DISPLAY " "
           .

       4000-MONTH-END-PROCESSING.
           DISPLAY "============================================"
           DISPLAY "  MONTH-END PROCESSING RESULTS"
           DISPLAY "============================================"
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-ACCT-COUNT
               INVOKE WS-ACCT-REF(WS-IDX) "ProcessMonthEnd"
                   RETURNING WS-SUMMARY
               INVOKE WS-ACCT-REF(WS-IDX) "GetAccountNumber"
                   RETURNING WS-ACCT-NUM
               DISPLAY "  " WS-ACCT-NUM ": " WS-SUMMARY
           END-PERFORM
           DISPLAY " "
           .

       5000-DISPLAY-POST-PROCESSING.
           DISPLAY "============================================"
           DISPLAY "  POST-MONTH-END ACCOUNT SUMMARY"
           DISPLAY "============================================"
           MOVE 0 TO WS-TOTAL-ASSETS
           PERFORM VARYING WS-IDX FROM 1 BY 1
               UNTIL WS-IDX > WS-ACCT-COUNT
               INVOKE WS-ACCT-REF(WS-IDX) "GetAccountNumber"
                   RETURNING WS-ACCT-NUM
               INVOKE WS-ACCT-REF(WS-IDX) "GetOwnerName"
                   RETURNING WS-OWNER
               INVOKE WS-ACCT-REF(WS-IDX) "GetAccountType"
                   RETURNING WS-ACCT-TYPE
               INVOKE WS-ACCT-REF(WS-IDX) "GetBalance"
                   RETURNING WS-BALANCE
               ADD WS-BALANCE TO WS-TOTAL-ASSETS
               MOVE WS-BALANCE TO WS-FMT-BAL
               DISPLAY "  " WS-ACCT-NUM " "
                   WS-ACCT-TYPE " " WS-FMT-BAL
           END-PERFORM
           DISPLAY "--------------------------------------------"
           MOVE WS-TOTAL-ASSETS TO WS-FMT-TOTAL
           DISPLAY "  Total Assets: " WS-FMT-TOTAL
           DISPLAY "============================================"
           .

Key Design Decisions Explained

Why Method Overriding for ProcessMonthEnd?

The base Account class defines ProcessMonthEnd as a default (no-op) method. Each subclass overrides it with type-specific logic. This is the template method pattern -- the month-end processing loop in the main program calls ProcessMonthEnd on every account without knowing the specific type. The correct behavior is selected at runtime through dynamic dispatch.

The alternative -- using EVALUATE on account type codes -- would require modifying the processing loop every time a new account type is added. With the OO approach, adding a new account type (such as MoneyMarketAccount) requires only writing the new subclass with its own ProcessMonthEnd override. The processing loop remains unchanged.

Why Override GetMaxWithdrawal in CheckingAccount?

The base Account.Withdraw method calls GetMaxWithdrawal to determine the withdrawal limit. The base implementation returns the current balance. CheckingAccount overrides GetMaxWithdrawal to return balance plus the overdraft limit. This allows the inherited Withdraw method to work correctly for both account types without being overridden itself. This is the hook method pattern -- the base class defines extension points that subclasses can customize.

Why Factory Methods Instead of Direct Construction?

Factory methods (like NewSavings and NewChecking) encapsulate the construction sequence. The caller does not need to know the steps involved in creating and initializing an account. If the initialization process changes (for example, adding audit logging or validation), only the factory method needs to be modified. Callers are insulated from construction details.


Discussion Questions

  1. Extensibility: If the credit union adds a new MoneyMarketAccount type with tiered interest rates (different rates for different balance ranges), what changes would be needed in the class hierarchy? What changes would be needed in the month-end processing program?

  2. Encapsulation trade-offs: The Withdraw method in the base class calls GetMaxWithdrawal on SELF, which CheckingAccount overrides. What are the risks of this approach? Could a poorly written override break the base class behavior?

  3. Performance: In a batch run processing 500,000 accounts, how does the INVOKE overhead compare to a procedural EVALUATE-based approach? At what scale does the maintenance benefit of OO outweigh any performance cost?

  4. Migration strategy: How would you incrementally migrate the existing procedural account processing programs to use this class hierarchy? What coexistence strategies would you employ during the transition?

  5. Testing: How would you unit-test the ProcessMonthEnd method for each account type? What test cases would you need to cover the boundary conditions (zero balance, negative balance after fees, minimum balance violations)?