Case Study 2: Transaction Processing with Strategy Pattern
Background
Pacific National Bank processes over 6 million transactions daily through its core banking platform. The transaction processing engine handles deposits, withdrawals, transfers, loan payments, fee assessments, and interest credits -- each with its own validation rules, processing logic, and audit requirements. Over the years, the engine has grown into a monolithic 35,000-line procedural program dominated by a massive EVALUATE statement with over 40 WHEN clauses, each containing hundreds of lines of inline processing logic.
Every new transaction type requires modifying this central program, retesting the entire module, and coordinating deployment across all environments. The development team estimates that adding a single new transaction type takes three weeks of development and two weeks of regression testing, not because the new logic is complex, but because the risk of unintended side effects in the monolithic code is enormous.
The architecture team has proposed refactoring the transaction engine using the Strategy pattern implemented in OO COBOL. Each transaction type becomes a separate strategy class with a common interface. The processing engine selects the appropriate strategy at runtime and delegates processing to it. This case study demonstrates the design, implementation, and benefits of this approach.
Problem Statement
Design and implement a transaction processing system using the Strategy pattern where:
- An interface
TransactionStrategydefines the contract for all transaction types:Validate,Execute, andGetDescription. - Concrete strategy classes implement the interface for specific transaction types:
DepositStrategy,WithdrawalStrategy, andTransferStrategy. - A
TransactionProcessorclass accepts any strategy and processes transactions polymorphically. - A
TransactionContextdata structure carries the transaction data (amount, account references, metadata) through the processing pipeline. - New transaction types can be added without modifying the existing processing engine.
The TransactionStrategy Interface
The interface defines the contract that all transaction strategies must fulfill. Every transaction type must be able to validate itself, execute its logic, and describe what it does.
IDENTIFICATION DIVISION.
INTERFACE-ID. TransactionStrategy.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. Validate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-VALID PIC 9.
01 LS-ERROR-MSG PIC X(60).
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-VALID.
END METHOD Validate.
IDENTIFICATION DIVISION.
METHOD-ID. Execute.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-NEW-SRC-BAL PIC S9(11)V99.
01 LS-NEW-TGT-BAL PIC S9(11)V99.
01 LS-SUCCESS PIC 9.
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-SUCCESS.
END METHOD Execute.
IDENTIFICATION DIVISION.
METHOD-ID. GetDescription.
DATA DIVISION.
LINKAGE SECTION.
01 LS-DESC PIC X(40).
PROCEDURE DIVISION RETURNING LS-DESC.
END METHOD GetDescription.
IDENTIFICATION DIVISION.
METHOD-ID. GetTransactionCode.
DATA DIVISION.
LINKAGE SECTION.
01 LS-CODE PIC X(3).
PROCEDURE DIVISION RETURNING LS-CODE.
END METHOD GetTransactionCode.
END INTERFACE TransactionStrategy.
Concrete Strategy: DepositStrategy
The deposit strategy validates that the amount is positive and executes by adding the amount to the source account balance.
IDENTIFICATION DIVISION.
CLASS-ID. DepositStrategy
IMPLEMENTS TransactionStrategy.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS DepositStrategy
INTERFACE TransactionStrategy.
IDENTIFICATION DIVISION.
FACTORY.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. New.
DATA DIVISION.
LINKAGE SECTION.
01 LS-NEW-OBJ USAGE OBJECT REFERENCE
DepositStrategy.
PROCEDURE DIVISION RETURNING LS-NEW-OBJ.
CREATE-IT.
INVOKE SELF "NEW" RETURNING LS-NEW-OBJ
.
END METHOD New.
END FACTORY.
IDENTIFICATION DIVISION.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TRANS-CODE PIC X(3) VALUE "DEP".
01 WS-DESCRIPTION PIC X(40)
VALUE "CASH/CHECK DEPOSIT".
01 WS-ERROR-MSG PIC X(60) VALUE SPACES.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. Validate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-VALID PIC 9.
01 LS-ERROR-MSG PIC X(60).
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-VALID.
DO-VALIDATE.
MOVE SPACES TO LS-ERROR-MSG
EVALUATE TRUE
WHEN LS-AMOUNT <= 0
MOVE "Deposit amount must be positive"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN LS-AMOUNT > 99999999.99
MOVE "Deposit exceeds single-transaction limit"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN OTHER
MOVE 1 TO LS-VALID
END-EVALUATE
.
END METHOD Validate.
IDENTIFICATION DIVISION.
METHOD-ID. Execute.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-NEW-SRC-BAL PIC S9(11)V99.
01 LS-NEW-TGT-BAL PIC S9(11)V99.
01 LS-SUCCESS PIC 9.
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-SUCCESS.
DO-EXECUTE.
COMPUTE LS-NEW-SRC-BAL =
LS-SOURCE-BAL + LS-AMOUNT
MOVE LS-TARGET-BAL TO LS-NEW-TGT-BAL
MOVE 1 TO LS-SUCCESS
.
END METHOD Execute.
IDENTIFICATION DIVISION.
METHOD-ID. GetDescription.
DATA DIVISION.
LINKAGE SECTION.
01 LS-DESC PIC X(40).
PROCEDURE DIVISION RETURNING LS-DESC.
GET-DESC.
MOVE WS-DESCRIPTION TO LS-DESC
.
END METHOD GetDescription.
IDENTIFICATION DIVISION.
METHOD-ID. GetTransactionCode.
DATA DIVISION.
LINKAGE SECTION.
01 LS-CODE PIC X(3).
PROCEDURE DIVISION RETURNING LS-CODE.
GET-CODE.
MOVE WS-TRANS-CODE TO LS-CODE
.
END METHOD GetTransactionCode.
END OBJECT.
END CLASS DepositStrategy.
Concrete Strategy: WithdrawalStrategy
The withdrawal strategy validates sufficient funds and executes by subtracting from the source balance.
IDENTIFICATION DIVISION.
CLASS-ID. WithdrawalStrategy
IMPLEMENTS TransactionStrategy.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS WithdrawalStrategy
INTERFACE TransactionStrategy.
IDENTIFICATION DIVISION.
FACTORY.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. New.
DATA DIVISION.
LINKAGE SECTION.
01 LS-NEW-OBJ USAGE OBJECT REFERENCE
WithdrawalStrategy.
PROCEDURE DIVISION RETURNING LS-NEW-OBJ.
CREATE-IT.
INVOKE SELF "NEW" RETURNING LS-NEW-OBJ
.
END METHOD New.
END FACTORY.
IDENTIFICATION DIVISION.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TRANS-CODE PIC X(3) VALUE "WDR".
01 WS-DESCRIPTION PIC X(40)
VALUE "CASH WITHDRAWAL".
01 WS-DAILY-LIMIT PIC 9(7)V99 VALUE 5000.00.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. Validate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-VALID PIC 9.
01 LS-ERROR-MSG PIC X(60).
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-VALID.
DO-VALIDATE.
MOVE SPACES TO LS-ERROR-MSG
EVALUATE TRUE
WHEN LS-AMOUNT <= 0
MOVE "Withdrawal amount must be positive"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN LS-AMOUNT > LS-SOURCE-BAL
MOVE "Insufficient funds for withdrawal"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN LS-AMOUNT > WS-DAILY-LIMIT
MOVE "Exceeds daily withdrawal limit"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN OTHER
MOVE 1 TO LS-VALID
END-EVALUATE
.
END METHOD Validate.
IDENTIFICATION DIVISION.
METHOD-ID. Execute.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-NEW-SRC-BAL PIC S9(11)V99.
01 LS-NEW-TGT-BAL PIC S9(11)V99.
01 LS-SUCCESS PIC 9.
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-SUCCESS.
DO-EXECUTE.
COMPUTE LS-NEW-SRC-BAL =
LS-SOURCE-BAL - LS-AMOUNT
MOVE LS-TARGET-BAL TO LS-NEW-TGT-BAL
IF LS-NEW-SRC-BAL < 0
MOVE 0 TO LS-SUCCESS
ELSE
MOVE 1 TO LS-SUCCESS
END-IF
.
END METHOD Execute.
IDENTIFICATION DIVISION.
METHOD-ID. GetDescription.
DATA DIVISION.
LINKAGE SECTION.
01 LS-DESC PIC X(40).
PROCEDURE DIVISION RETURNING LS-DESC.
GET-DESC.
MOVE WS-DESCRIPTION TO LS-DESC
.
END METHOD GetDescription.
IDENTIFICATION DIVISION.
METHOD-ID. GetTransactionCode.
DATA DIVISION.
LINKAGE SECTION.
01 LS-CODE PIC X(3).
PROCEDURE DIVISION RETURNING LS-CODE.
GET-CODE.
MOVE WS-TRANS-CODE TO LS-CODE
.
END METHOD GetTransactionCode.
END OBJECT.
END CLASS WithdrawalStrategy.
Concrete Strategy: TransferStrategy
The transfer strategy validates both accounts and moves funds between them atomically.
IDENTIFICATION DIVISION.
CLASS-ID. TransferStrategy
IMPLEMENTS TransactionStrategy.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS TransferStrategy
INTERFACE TransactionStrategy.
IDENTIFICATION DIVISION.
FACTORY.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. New.
DATA DIVISION.
LINKAGE SECTION.
01 LS-NEW-OBJ USAGE OBJECT REFERENCE
TransferStrategy.
PROCEDURE DIVISION RETURNING LS-NEW-OBJ.
CREATE-IT.
INVOKE SELF "NEW" RETURNING LS-NEW-OBJ
.
END METHOD New.
END FACTORY.
IDENTIFICATION DIVISION.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TRANS-CODE PIC X(3) VALUE "TRF".
01 WS-DESCRIPTION PIC X(40)
VALUE "ACCOUNT-TO-ACCOUNT TRANSFER".
01 WS-MAX-TRANSFER PIC 9(9)V99 VALUE 250000.00.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. Validate.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-VALID PIC 9.
01 LS-ERROR-MSG PIC X(60).
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-VALID.
DO-VALIDATE.
MOVE SPACES TO LS-ERROR-MSG
EVALUATE TRUE
WHEN LS-AMOUNT <= 0
MOVE "Transfer amount must be positive"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN LS-AMOUNT > LS-SOURCE-BAL
MOVE "Insufficient funds for transfer"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN LS-AMOUNT > WS-MAX-TRANSFER
MOVE "Exceeds maximum transfer limit"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN LS-TARGET-BAL < 0
MOVE "Target account is not valid"
TO LS-ERROR-MSG
MOVE 0 TO LS-VALID
WHEN OTHER
MOVE 1 TO LS-VALID
END-EVALUATE
.
END METHOD Validate.
IDENTIFICATION DIVISION.
METHOD-ID. Execute.
DATA DIVISION.
LINKAGE SECTION.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-NEW-SRC-BAL PIC S9(11)V99.
01 LS-NEW-TGT-BAL PIC S9(11)V99.
01 LS-SUCCESS PIC 9.
PROCEDURE DIVISION
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING LS-SUCCESS.
DO-EXECUTE.
IF LS-AMOUNT > LS-SOURCE-BAL
MOVE 0 TO LS-SUCCESS
ELSE
COMPUTE LS-NEW-SRC-BAL =
LS-SOURCE-BAL - LS-AMOUNT
COMPUTE LS-NEW-TGT-BAL =
LS-TARGET-BAL + LS-AMOUNT
MOVE 1 TO LS-SUCCESS
END-IF
.
END METHOD Execute.
IDENTIFICATION DIVISION.
METHOD-ID. GetDescription.
DATA DIVISION.
LINKAGE SECTION.
01 LS-DESC PIC X(40).
PROCEDURE DIVISION RETURNING LS-DESC.
GET-DESC.
MOVE WS-DESCRIPTION TO LS-DESC
.
END METHOD GetDescription.
IDENTIFICATION DIVISION.
METHOD-ID. GetTransactionCode.
DATA DIVISION.
LINKAGE SECTION.
01 LS-CODE PIC X(3).
PROCEDURE DIVISION RETURNING LS-CODE.
GET-CODE.
MOVE WS-TRANS-CODE TO LS-CODE
.
END METHOD GetTransactionCode.
END OBJECT.
END CLASS TransferStrategy.
The Transaction Processor
The TransactionProcessor class is the engine that orchestrates transaction processing. It accepts any strategy that implements the TransactionStrategy interface and processes transactions through a standardized pipeline: validate, execute, log.
IDENTIFICATION DIVISION.
CLASS-ID. TransactionProcessor.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS TransactionProcessor
INTERFACE TransactionStrategy.
IDENTIFICATION DIVISION.
FACTORY.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. New.
DATA DIVISION.
LINKAGE SECTION.
01 LS-NEW-OBJ USAGE OBJECT REFERENCE
TransactionProcessor.
PROCEDURE DIVISION RETURNING LS-NEW-OBJ.
CREATE-IT.
INVOKE SELF "NEW" RETURNING LS-NEW-OBJ
.
END METHOD New.
END FACTORY.
IDENTIFICATION DIVISION.
OBJECT.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-TOTAL-PROCESSED PIC 9(8) VALUE 0.
01 WS-TOTAL-APPROVED PIC 9(8) VALUE 0.
01 WS-TOTAL-DECLINED PIC 9(8) VALUE 0.
01 WS-AUDIT-LOG.
05 WS-AUDIT-ENTRY OCCURS 100 TIMES.
10 WS-AUDIT-SEQ PIC 9(8).
10 WS-AUDIT-CODE PIC X(3).
10 WS-AUDIT-AMT PIC S9(11)V99.
10 WS-AUDIT-STATUS PIC X(8).
10 WS-AUDIT-TIME PIC X(21).
01 WS-AUDIT-COUNT PIC 9(8) VALUE 0.
PROCEDURE DIVISION.
IDENTIFICATION DIVISION.
METHOD-ID. ProcessTransaction.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-IS-VALID PIC 9.
01 WS-EXEC-OK PIC 9.
01 WS-ERROR-MSG PIC X(60).
01 WS-TRANS-CODE PIC X(3).
01 WS-TRANS-DESC PIC X(40).
01 WS-NEW-SRC-BAL PIC S9(11)V99.
01 WS-NEW-TGT-BAL PIC S9(11)V99.
01 WS-FMT-AMT PIC -(11)9.99.
01 WS-TIMESTAMP PIC X(21).
LINKAGE SECTION.
01 LS-STRATEGY-REF USAGE OBJECT REFERENCE
TransactionStrategy.
01 LS-SOURCE-BAL PIC S9(11)V99.
01 LS-TARGET-BAL PIC S9(11)V99.
01 LS-AMOUNT PIC 9(11)V99.
01 LS-RESULT-CODE PIC 9.
01 LS-RESULT-MSG PIC X(80).
PROCEDURE DIVISION
USING LS-STRATEGY-REF LS-SOURCE-BAL
LS-TARGET-BAL LS-AMOUNT
RETURNING LS-RESULT-CODE.
DO-PROCESS.
ADD 1 TO WS-TOTAL-PROCESSED
INVOKE LS-STRATEGY-REF "GetTransactionCode"
RETURNING WS-TRANS-CODE
INVOKE LS-STRATEGY-REF "GetDescription"
RETURNING WS-TRANS-DESC
MOVE LS-AMOUNT TO WS-FMT-AMT
DISPLAY " Processing " WS-TRANS-DESC
" for " WS-FMT-AMT
* Step 1: Validate
INVOKE LS-STRATEGY-REF "Validate"
USING LS-SOURCE-BAL LS-TARGET-BAL LS-AMOUNT
RETURNING WS-IS-VALID
IF WS-IS-VALID = 0
ADD 1 TO WS-TOTAL-DECLINED
MOVE 0 TO LS-RESULT-CODE
DISPLAY " DECLINED: Validation failed"
PERFORM AUDIT-LOG-DECLINED
ELSE
* Step 2: Execute
INVOKE LS-STRATEGY-REF "Execute"
USING LS-SOURCE-BAL LS-TARGET-BAL
LS-AMOUNT
RETURNING WS-EXEC-OK
IF WS-EXEC-OK = 1
ADD 1 TO WS-TOTAL-APPROVED
MOVE 1 TO LS-RESULT-CODE
DISPLAY " APPROVED"
PERFORM AUDIT-LOG-APPROVED
ELSE
ADD 1 TO WS-TOTAL-DECLINED
MOVE 0 TO LS-RESULT-CODE
DISPLAY " DECLINED: Execution failed"
PERFORM AUDIT-LOG-DECLINED
END-IF
END-IF
.
AUDIT-LOG-APPROVED.
IF WS-AUDIT-COUNT < 100
ADD 1 TO WS-AUDIT-COUNT
MOVE WS-TOTAL-PROCESSED
TO WS-AUDIT-SEQ(WS-AUDIT-COUNT)
MOVE WS-TRANS-CODE
TO WS-AUDIT-CODE(WS-AUDIT-COUNT)
MOVE LS-AMOUNT
TO WS-AUDIT-AMT(WS-AUDIT-COUNT)
MOVE "APPROVED"
TO WS-AUDIT-STATUS(WS-AUDIT-COUNT)
MOVE FUNCTION CURRENT-DATE(1:21)
TO WS-AUDIT-TIME(WS-AUDIT-COUNT)
END-IF
.
AUDIT-LOG-DECLINED.
IF WS-AUDIT-COUNT < 100
ADD 1 TO WS-AUDIT-COUNT
MOVE WS-TOTAL-PROCESSED
TO WS-AUDIT-SEQ(WS-AUDIT-COUNT)
MOVE WS-TRANS-CODE
TO WS-AUDIT-CODE(WS-AUDIT-COUNT)
MOVE LS-AMOUNT
TO WS-AUDIT-AMT(WS-AUDIT-COUNT)
MOVE "DECLINED"
TO WS-AUDIT-STATUS(WS-AUDIT-COUNT)
MOVE FUNCTION CURRENT-DATE(1:21)
TO WS-AUDIT-TIME(WS-AUDIT-COUNT)
END-IF
.
END METHOD ProcessTransaction.
IDENTIFICATION DIVISION.
METHOD-ID. PrintAuditLog.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-IDX PIC 9(8).
01 WS-FMT-AMT PIC -(11)9.99.
PROCEDURE DIVISION.
DO-PRINT.
DISPLAY " "
DISPLAY "=========================================="
DISPLAY " TRANSACTION AUDIT LOG"
DISPLAY "=========================================="
DISPLAY " SEQ CODE AMOUNT"
" STATUS"
IF WS-AUDIT-COUNT = 0
DISPLAY " (No transactions recorded)"
ELSE
PERFORM VARYING WS-IDX FROM 1 BY 1
UNTIL WS-IDX > WS-AUDIT-COUNT
MOVE WS-AUDIT-AMT(WS-IDX) TO WS-FMT-AMT
DISPLAY " "
WS-AUDIT-SEQ(WS-IDX) " "
WS-AUDIT-CODE(WS-IDX) " "
WS-FMT-AMT " "
WS-AUDIT-STATUS(WS-IDX)
END-PERFORM
END-IF
DISPLAY "------------------------------------------"
DISPLAY " Total processed: " WS-TOTAL-PROCESSED
DISPLAY " Approved: " WS-TOTAL-APPROVED
DISPLAY " Declined: " WS-TOTAL-DECLINED
DISPLAY "=========================================="
.
END METHOD PrintAuditLog.
IDENTIFICATION DIVISION.
METHOD-ID. GetStatistics.
DATA DIVISION.
LINKAGE SECTION.
01 LS-PROCESSED PIC 9(8).
01 LS-APPROVED PIC 9(8).
01 LS-DECLINED PIC 9(8).
PROCEDURE DIVISION
RETURNING LS-PROCESSED.
GET-STATS.
MOVE WS-TOTAL-PROCESSED TO LS-PROCESSED
.
END METHOD GetStatistics.
END OBJECT.
END CLASS TransactionProcessor.
The Test Driver: Polymorphic Processing in Action
The test program creates multiple strategy objects and feeds transactions to the processor. The processor does not know or care which specific strategy it is using -- it works entirely through the TransactionStrategy interface.
IDENTIFICATION DIVISION.
PROGRAM-ID. TransactionDemo.
ENVIRONMENT DIVISION.
CONFIGURATION SECTION.
REPOSITORY.
CLASS TransactionProcessor
CLASS DepositStrategy
CLASS WithdrawalStrategy
CLASS TransferStrategy
INTERFACE TransactionStrategy.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 WS-PROCESSOR-REF USAGE OBJECT REFERENCE
TransactionProcessor.
01 WS-DEPOSIT-REF USAGE OBJECT REFERENCE
DepositStrategy.
01 WS-WITHDRAW-REF USAGE OBJECT REFERENCE
WithdrawalStrategy.
01 WS-TRANSFER-REF USAGE OBJECT REFERENCE
TransferStrategy.
01 WS-STRATEGY-REF USAGE OBJECT REFERENCE
TransactionStrategy.
01 WS-SOURCE-BAL PIC S9(11)V99 VALUE 10000.00.
01 WS-TARGET-BAL PIC S9(11)V99 VALUE 5000.00.
01 WS-AMOUNT PIC 9(11)V99.
01 WS-RESULT PIC 9.
01 WS-FMT-BAL PIC -(11)9.99.
PROCEDURE DIVISION.
0000-MAIN.
PERFORM 1000-INITIALIZE
PERFORM 2000-RUN-TRANSACTIONS
PERFORM 3000-PRINT-RESULTS
STOP RUN
.
1000-INITIALIZE.
INVOKE TransactionProcessor "New"
RETURNING WS-PROCESSOR-REF
INVOKE DepositStrategy "New"
RETURNING WS-DEPOSIT-REF
INVOKE WithdrawalStrategy "New"
RETURNING WS-WITHDRAW-REF
INVOKE TransferStrategy "New"
RETURNING WS-TRANSFER-REF
DISPLAY "============================================"
DISPLAY " PACIFIC NATIONAL BANK"
DISPLAY " TRANSACTION PROCESSING DEMONSTRATION"
DISPLAY "============================================"
MOVE WS-SOURCE-BAL TO WS-FMT-BAL
DISPLAY " Source account balance: " WS-FMT-BAL
MOVE WS-TARGET-BAL TO WS-FMT-BAL
DISPLAY " Target account balance: " WS-FMT-BAL
DISPLAY " "
.
2000-RUN-TRANSACTIONS.
* Transaction 1: Deposit $2,500.00
DISPLAY "--- Transaction 1 ---"
MOVE 2500.00 TO WS-AMOUNT
SET WS-STRATEGY-REF TO WS-DEPOSIT-REF
INVOKE WS-PROCESSOR-REF "ProcessTransaction"
USING WS-STRATEGY-REF
WS-SOURCE-BAL WS-TARGET-BAL
WS-AMOUNT
RETURNING WS-RESULT
IF WS-RESULT = 1
ADD WS-AMOUNT TO WS-SOURCE-BAL
END-IF
DISPLAY " "
* Transaction 2: Withdrawal $3,000.00
DISPLAY "--- Transaction 2 ---"
MOVE 3000.00 TO WS-AMOUNT
SET WS-STRATEGY-REF TO WS-WITHDRAW-REF
INVOKE WS-PROCESSOR-REF "ProcessTransaction"
USING WS-STRATEGY-REF
WS-SOURCE-BAL WS-TARGET-BAL
WS-AMOUNT
RETURNING WS-RESULT
IF WS-RESULT = 1
SUBTRACT WS-AMOUNT FROM WS-SOURCE-BAL
END-IF
DISPLAY " "
* Transaction 3: Transfer $1,500.00
DISPLAY "--- Transaction 3 ---"
MOVE 1500.00 TO WS-AMOUNT
SET WS-STRATEGY-REF TO WS-TRANSFER-REF
INVOKE WS-PROCESSOR-REF "ProcessTransaction"
USING WS-STRATEGY-REF
WS-SOURCE-BAL WS-TARGET-BAL
WS-AMOUNT
RETURNING WS-RESULT
IF WS-RESULT = 1
SUBTRACT WS-AMOUNT FROM WS-SOURCE-BAL
ADD WS-AMOUNT TO WS-TARGET-BAL
END-IF
DISPLAY " "
* Transaction 4: Overdraft withdrawal (should fail)
DISPLAY "--- Transaction 4 ---"
MOVE 50000.00 TO WS-AMOUNT
SET WS-STRATEGY-REF TO WS-WITHDRAW-REF
INVOKE WS-PROCESSOR-REF "ProcessTransaction"
USING WS-STRATEGY-REF
WS-SOURCE-BAL WS-TARGET-BAL
WS-AMOUNT
RETURNING WS-RESULT
DISPLAY " "
* Transaction 5: Zero-amount deposit (should fail)
DISPLAY "--- Transaction 5 ---"
MOVE 0.00 TO WS-AMOUNT
SET WS-STRATEGY-REF TO WS-DEPOSIT-REF
INVOKE WS-PROCESSOR-REF "ProcessTransaction"
USING WS-STRATEGY-REF
WS-SOURCE-BAL WS-TARGET-BAL
WS-AMOUNT
RETURNING WS-RESULT
DISPLAY " "
* Transaction 6: Another valid deposit
DISPLAY "--- Transaction 6 ---"
MOVE 750.00 TO WS-AMOUNT
SET WS-STRATEGY-REF TO WS-DEPOSIT-REF
INVOKE WS-PROCESSOR-REF "ProcessTransaction"
USING WS-STRATEGY-REF
WS-SOURCE-BAL WS-TARGET-BAL
WS-AMOUNT
RETURNING WS-RESULT
IF WS-RESULT = 1
ADD WS-AMOUNT TO WS-SOURCE-BAL
END-IF
DISPLAY " "
.
3000-PRINT-RESULTS.
DISPLAY "============================================"
DISPLAY " FINAL BALANCES"
DISPLAY "============================================"
MOVE WS-SOURCE-BAL TO WS-FMT-BAL
DISPLAY " Source account: " WS-FMT-BAL
MOVE WS-TARGET-BAL TO WS-FMT-BAL
DISPLAY " Target account: " WS-FMT-BAL
INVOKE WS-PROCESSOR-REF "PrintAuditLog"
.
Solution Walkthrough
How the Strategy Pattern Works Here
The traditional procedural approach uses a large EVALUATE statement that switches on transaction type codes:
* TRADITIONAL PROCEDURAL APPROACH (before refactoring)
EVALUATE WS-TRANS-TYPE
WHEN "DEP"
PERFORM 5000-VALIDATE-DEPOSIT
IF WS-VALID
PERFORM 5100-EXECUTE-DEPOSIT
END-IF
WHEN "WDR"
PERFORM 6000-VALIDATE-WITHDRAWAL
IF WS-VALID
PERFORM 6100-EXECUTE-WITHDRAWAL
END-IF
WHEN "TRF"
PERFORM 7000-VALIDATE-TRANSFER
IF WS-VALID
PERFORM 7100-EXECUTE-TRANSFER
END-IF
* ... 37 more WHEN clauses ...
END-EVALUATE
Adding a new transaction type requires modifying this central EVALUATE, adding new paragraphs, and retesting the entire program. The Strategy pattern eliminates the EVALUATE entirely. The TransactionProcessor.ProcessTransaction method works with the TransactionStrategy interface:
- It calls
Validateon whatever strategy was passed in. - If validation succeeds, it calls
Execute. - It logs the result regardless of outcome.
The processor never checks what type of transaction it is processing. It delegates all type-specific behavior to the strategy object. This is the Open/Closed Principle in action -- the processor is open for extension (new transaction types) but closed for modification (existing code does not change).
Adding a New Transaction Type
To add a LoanPaymentStrategy, a developer writes a single new class that implements TransactionStrategy. The class provides its own Validate (check loan exists, amount is correct), Execute (apply payment to principal and interest), GetDescription, and GetTransactionCode methods. No existing code changes. The processor handles it automatically.
The Role of the Interface
The TransactionStrategy interface is the contract that decouples the processor from the strategies. The processor depends only on the interface, not on any concrete strategy class. This means:
- Strategies can be developed and tested independently.
- New strategies can be deployed without recompiling the processor.
- Strategy selection can be driven by configuration data rather than hardcoded logic.
Discussion Questions
-
Strategy selection: In the test driver, the strategy is selected explicitly in code. In a production system, how would you map incoming transaction codes to the appropriate strategy class? Consider a factory method or a lookup table of strategy references.
-
State management: The strategies in this implementation are stateless -- they do not retain data between calls. What are the advantages of stateless strategies? When might a stateful strategy be appropriate?
-
Error handling: The current design returns a simple success/failure flag. How would you extend it to support detailed error reporting with error codes, messages, and recovery suggestions?
-
Testing advantages: How does the Strategy pattern make unit testing easier compared to the monolithic EVALUATE approach? Consider how you would test a single transaction type in isolation.
-
Hybrid approach: In practice, would you convert all 40 transaction types to strategies at once, or take an incremental approach? Describe a migration strategy that allows the old EVALUATE and new Strategy objects to coexist during the transition.