Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
151: The Strategy Pattern
I've spent a lot of my career cleaning up "God Classes"—those massive files where one single method contains a 500-line switch statement handling every possible edge case in the business. Usually, this happens because the original developer wanted to handle different behaviors based on a certain type, but they didn't want to over-engineer it at the time. The problem is that "not over-engineering" eventually turns into a maintenance nightmare.
That's exactly where the Strategy Pattern comes in. It allows us to define a family of algorithms, encapsulate each one, and make them interchangeable. In plain English: instead of one giant method trying to do everything, we delegate the work to a separate object that knows how to do one specific thing.
The "Quick and Dirty" way (that we'll regret)
Let's say we're building a shipping module for an e-commerce site. We need to calculate shipping costs based on the carrier the customer chooses. My first instinct—and the mistake I've made more times than I'd like to admit—is to just put it all in one place.
public class ShippingCalculator {
public double calculateCost(double weight, String carrier) {
if (carrier.equals("FedEx")) {
return weight * 1.5;
} else if (carrier.equals("UPS")) {
return weight * 1.2 + 5.0;
} else if (carrier.equals("DHL")) {
return weight * 2.0;
}
throw new IllegalArgumentException("Unknown carrier");
}
}
It works, right? Sure. But imagine we add ten more carriers. Or imagine FedEx changes their pricing model to include a fuel surcharge. Every time we touch this method, we risk breaking the logic for every other carrier. This violates the Open/Closed Principle: our class should be open for extension, but closed for modification.
Cleaning up the mess with an interface
To fix this, I'm going to pull the calculation logic out of the ShippingCalculator entirely. I'll create a "Strategy" interface. This interface doesn't care how the cost is calculated; it just knows that any shipping strategy must be able to take a weight and return a cost.
public interface ShippingStrategy {
double calculate(double weight);
}
Now, I can create concrete implementations for each carrier. This is the beauty of the pattern: if DHL changes their rates, I only have to touch the DhlStrategy class. I don't even have to look at the FedEx code.
public class FedExStrategy implements ShippingStrategy {
public double calculate(double weight) {
return weight * 1.5;
}
}
public class UpsStrategy implements ShippingStrategy {
public double calculate(double weight) {
return weight * 1.2 + 5.0;
}
}
Plugging in different carriers at runtime
Now we need to wire it back together. Instead of the ShippingCalculator deciding which logic to use via a string comparison, we'll give it a strategy object. I prefer using a constructor or a setter here so we can change the strategy on the fly without recreating the calculator.
public class ShippingCalculator {
private ShippingStrategy strategy;
// We inject the strategy here
public void setShippingStrategy(ShippingStrategy strategy) {
this.strategy = strategy;
}
public double calculate(double weight) {
if (strategy == null) {
throw new IllegalStateException("Shipping strategy not set!");
}
return strategy.calculate(weight);
}
}
Here is how you'd actually use this in your application. You decide which strategy to use at the moment the user selects their shipping method in the UI, and the calculator just executes it.
public class Main {
public static void main(String[] args) {
ShippingCalculator calc = new ShippingCalculator();
double packageWeight = 10.0;
// User chooses FedEx
calc.setShippingStrategy(new FedExStrategy());
System.out.println("FedEx Cost: " + calc.calculate(packageWeight));
// User changes their mind and chooses UPS
calc.setShippingStrategy(new UpsStrategy());
System.out.println("UPS Cost: " + calc.calculate(packageWeight));
}
}
By shifting the logic into separate classes, we've turned a rigid, fragile method into a flexible system. You can add a PostalServiceStrategy tomorrow without changing a single line of existing, tested code in your calculator.
📋 Practical Task
Build a Dynamic International Tax Calculator
You are tasked with building a tax calculation system for a global digital store. Different countries have different tax rules: some use a flat percentage (VAT), some have a flat fee regardless of price, and some are tax-exempt.
Your requirements:
- Create a
TaxStrategyinterface with a methoddouble calculateTax(double amount). - Implement three concrete strategies:
VatTaxStrategy: Calculates tax as 20% of the amount.FlatFeeTaxStrategy: Charges a flat $5.00 tax regardless of the amount.ExemptTaxStrategy: Returns 0.0 tax.
- Create a
TaxContextclass (the "Calculator") that holds a reference to aTaxStrategyand provides a method to calculate the total tax for a given amount. - In a
mainmethod, demonstrate the system by calculating tax for a $100 item using all three different strategies.
There are no comments for now.