Kung Fu Code: Master Shifu Teaches Strategy Pattern to Po – the Functional Way!


 "There is no good or bad code . But how you write it… that makes all the difference.”-- Master Shifu


The sun had just touched the tips of the Valley of Peace. Birds chirped, the wind whispered tales of warriors, and Po—the Dragon Warrior—was busy trying to write some Java code. Yes, you read that right.

Master Shifu stood behind him, watching, amused and concerned.

Po (scratching his head): “Master Shifu, I’m trying to make this app where each Kung Fu move is chosen based on the enemy. But the code is… bloated. Classes everywhere. If OOP was noodles, this is a full buffet.”

Shifu (calmly sipping tea): “Ah, the classic Strategy Pattern. But there’s a better way, Po… a functional way. Let me show you the path.”

The Traditional (OOP) Strategy Pattern – Heavy Like Po’s Lunch   

Po wants to choose a fighting strategy based on his opponent.

// Strategy Interface
interface FightStrategy {
void fight();
}

// Concrete Strategies
class TigerFightStrategy implements FightStrategy {
public void fight() {
System.out.println("Attack with swift tiger strikes!");
}
}

class MonkeyFightStrategy implements FightStrategy {
public void fight() {
System.out.println("Use agile monkey flips!");
}
}

// Context
class Warrior {
private FightStrategy strategy;

public Warrior(FightStrategy strategy) {
this.strategy = strategy;
}

public void fight() {
strategy.fight();
}

public void setStrategy(FightStrategy strategy) {
this.strategy = strategy;
}
}

Usage

Warrior po = new Warrior(new TigerFightStrategy());
po.fight(); // Output: Attack with swift tiger strikes!

po.setStrategy(new MonkeyFightStrategy());
po.fight(); // Output: Use agile monkey flips!


Why This Is a Problem (and Why Po Is Annoyed)

Po: “So many files, interfaces, boilerplate! All I want is to change moves easily. This feels like trying to meditate with a noodle cart passing by!”

Indeed, OOP Strategy pattern works, but it's verboserigid, and unnecessarily class-heavy. It violates the spirit of quick Kung Fu adaptability!

Enter Functional Programming – The Way of Inner Simplicity

Shifu (nodding): “Po, what if I told you… that functions themselves can be passed around like scrolls of wisdom?”  

Po: “Whoa... like… JScrolls

Shifu: “No, Po. Java lambdas.” 

In functional programmingfunctions are first-class citizens. You don’t need classes to wrap behavior. You can pass behavior directly.

Higher-Order Functions – functions that take other functions as parameters or return them.

Po, In Java8 onwards , now we can do that easily with the help of lambda, lambda can wrap the functionality and can be pass to another method as a parameter.

Strategy Pattern – The Functional Way in Java



import java.util.function.Consumer;
class Warrior {
private Consumer<Void> strategy;

public Warrior(Consumer<Void> strategy) {
this.strategy = strategy;
}

public void fight() {
strategy.accept(null);
}

public void setStrategy(Consumer<Void> strategy) {
this.strategy = strategy;
}
}

But there’s a better, cleaner way with just lambdas and no class at all.

import java.util.function.Supplier;

public class FunctionalStrategy {

public static void main(String[] args) {
// Each strategy is just a lambda
Runnable tigerStyle = () -> System.out.println("Attack with swift tiger strikes!");
Runnable monkeyStyle = () -> System.out.println("Use agile monkey flips!");
Runnable pandaStyle = () -> System.out.println("Roll and belly-bounce!");

// Fighter is a high-order function executor
executeStrategy(tigerStyle);
executeStrategy(monkeyStyle);
executeStrategy(pandaStyle);
}

static void executeStrategy(Runnable strategy) {
strategy.run();
}
}

Shifu (with a gentle tone):

“Po, in the art of code—as in Kung Fu—not every move needs a name, nor every master a title. In our example, we summoned the ancient scroll of Runnable… a humble interface with but one method—run(). In Java8 , we called it Functional Interface.

Think of it as a silent warrior—it expects no inputs(parameters) , demands no rewards(return type), and yet, performs its duty when called.

Each fighting style—tiger, monkey, panda—was not wrapped in robes of classes, but flowed freely as lambdas.

And then, we had the executeStrategy() method…
a higher-order sensei.

It does not fight itself, Po. It simply receives the wisdom of a move—a function—and executes it when the time is right.

This… is the way of functional composition.
You do not command the move—you invite it.
You do not create many paths—you simply choose the next step.”

Benefits – As Clear As The Sacred Pool of Tears

  • No extra interfaces or classes
  •  Easily switch behaviors at runtime

  • More readable, composable, and flexible

  •  Promotes the power of behavior as data.

Real-World Example: Choosing Payment Strategy in an App

Map<String, Runnable> paymentStrategies = Map.of(
"CARD", () -> System.out.println("Processing via Credit Card"),
"UPI", () -> System.out.println("Processing via UPI"),
"CASH", () -> System.out.println("Processing via Cash")
);

String chosen = "UPI";
paymentStrategies.get(chosen).run(); // Output: Processing via UPI

Po: “This is amazing! It’s like picking dumplings from a basket, but each dumpling is a deadly move.” 

Shifu: “Exactly. The Strategy   was never about the class, Po. It was about choosing the right move at the right moment… effortlessly.” 

One move=One lambda.

The good part is this lambda only holds the move details nothing else. So any warrior can master these moves , to apply the move unnecessary he does not need to reference a bounded object which wrapped this move in a boilerplate class.

Final Words of Wisdom  

“The strength of a great developer lies not in how many patterns they know… but in how effortlessly they flow between object thinking and function weaving to craft code that adapts like water, yet strikes like steel.”-- Master Shifu, on the Tao of Design Patterns.


Coming Up in the Series

Java:Pushing runtime behaviour without Lambda

Java:Pushing runtime behaviour without Lambda



In Java8 one of the best features is Lambda Expression The Idea of Lambda expression is passing a behavior to a method.

This has been adopted from Functional programming, In Functional programming, we can pass a function  as an argument of another function or can returns a function. The function takes a function as an argument or returns a function called
Higher order function.


The benefit of this type of design is you can pass behavior and values together, so a single function can perform all types of actions on those values.

Say, I want to create the calculator so in java we have to create methods like add, subtract, multiply etc. But in the functional program paradigm, we create one method which takes another method as an argument where we define what to do.

Pseudo code
Higher order function

function void caculate (a,b,f){
    f(a,b);
}

Calling

calculate(10,20,funtion(y,z){// define and pass the method on fly

y+z;
});


In Java8 using Lambda, we can do the same if you want to know details about Lambda can consult my post  http://javaonfly.blogspot.in/2016/08/java8-lambda.html


But here we will discuss How we can achieve the same thing without Lambda. As may, many projects has not adopted java 8 due to some restrictions.

We can achieve the same by using  Interface and Anonymous class.

Step 1: Create an Interface which will provide the contract for the behavior/strategy, i.e add, subtract, multiply functionality.

So How we will achieve the same



package com.example.runtime.behaviour;

public interface IStrategy {
   
   public void operation(int operand1,int operand2);

}


Step 2: Next I will create a Calculator class where we create a method called calculate which takes two operands and the IStrategy interface as arguments.

It acts like functional programming Higher order function where we can pass method/behavior runtime.

While calling this calculate method I pass the actual implementation of IStrategy interface by using Anonymous class.

By this way, we can push behavior on runtime without Lambda Expression.


package com.example.runtime.behaviour;

public class Calculator {
   
   public void caculate(int operand1,int operand2,IStrategy runtimeBehaviour){
      runtimeBehaviour.operation(operand1, operand2);
     
   }
   
   public static void testAllOperation(){
     
      Calculator calc = new Calculator();
      calc.caculate(10, 20, new IStrategy(){

          @Override
          public void operation(int operand1, int operand2) {
              int result = operand1+operand2;
              System.out.println("ADD:: " + result);
             
          }
         
      });
     
     
      calc.caculate(10, 20, new IStrategy(){

          @Override
          public void operation(int operand1, int operand2) {
              int result = operand1*operand2;
              System.out.println("Multiply:: " + result);
             
          }
         
      });
   }
   
   public static void main(String[] args) {
      Calculator.testAllOperation();
   }

}



Look at the testAllOperationMethod here we pass two operands 10,20 and implementation of IStrategy one are for add another for multiplication.

So rather creating  4 different methods add, multiply, subtract, the division we can create one method which will support all possible operations taken on operand1 and operand2.

In Future, if any new operation like modulus will be added easily in this approach but if we use one method for one operation then you need to add a new method called modulus to Calculator class which will break Open close principle.