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:
- Defines a common
Accountbase class with shared attributes (account number, owner name, balance, status) and shared operations (deposit, withdraw, get balance). - Implements a
SavingsAccountsubclass that adds an interest rate attribute and overrides the monthly processing method to calculate and apply interest. - Implements a
CheckingAccountsubclass that adds an overdraft limit and monthly fee, overriding the monthly processing method to deduct the fee and check the minimum balance. - 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
-
Extensibility: If the credit union adds a new
MoneyMarketAccounttype 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? -
Encapsulation trade-offs: The
Withdrawmethod in the base class callsGetMaxWithdrawalon SELF, whichCheckingAccountoverrides. What are the risks of this approach? Could a poorly written override break the base class behavior? -
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?
-
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?
-
Testing: How would you unit-test the
ProcessMonthEndmethod for each account type? What test cases would you need to cover the boundary conditions (zero balance, negative balance after fees, minimum balance violations)?