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:

  1. An interface TransactionStrategy defines the contract for all transaction types: Validate, Execute, and GetDescription.
  2. Concrete strategy classes implement the interface for specific transaction types: DepositStrategy, WithdrawalStrategy, and TransferStrategy.
  3. A TransactionProcessor class accepts any strategy and processes transactions polymorphically.
  4. A TransactionContext data structure carries the transaction data (amount, account references, metadata) through the processing pipeline.
  5. 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:

  1. It calls Validate on whatever strategy was passed in.
  2. If validation succeeds, it calls Execute.
  3. 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

  1. 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.

  2. 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?

  3. 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?

  4. 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.

  5. 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.