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
156: The Template Method Pattern
I've noticed a recurring trend when developers first encounter the Template Method pattern: they mistake it for the Strategy pattern. On the surface, they look identical because both involve an abstract base or interface and a few concrete implementations. You might think, "I'm just using polymorphism to swap out logic; why does this need a special name?"
"It's just the Strategy Pattern with a different name"
If you treat the Template Method like the Strategy pattern, you end up duplicating the "boring" parts of your code. Let's say you're building a system to export data to different formats—CSV and PDF. In a Strategy-like approach, you'd create an Exporter interface with a single export() method. Each implementation would then handle opening the file, writing the header, writing the data, and closing the file.
// The "Strategy" approach (Wrong for this specific need)
public interface Exporter {
void export(Data data);
}
public class CsvExporter implements Exporter {
public void export(Data data) {
System.out.println("Opening CSV file...");
System.out.println("Writing CSV Header...");
System.out.println("Writing CSV Data...");
System.out.println("Closing CSV file...");
}
}
public class PdfExporter implements Exporter {
public void export(Data data) {
System.out.println("Opening PDF file...");
System.out.println("Writing PDF Header...");
System.out.println("Writing PDF Data...");
System.out.println("Closing PDF file...");
}
}
See the problem? The sequence of events—Open, Header, Data, Close—is identical in both classes. If you decide tomorrow that you need to add a "Log export start time" step, you have to hunt down every single implementation and manually insert that line. That's a maintenance nightmare waiting to happen.
Enforcing the Algorithm's Skeleton
The Template Method pattern isn't about swapping the entire algorithm; it's about fixing the skeleton of the algorithm in a base class and letting subclasses fill in the specific blanks. The base class controls the workflow, and the subclasses provide the implementation for the individual steps.
Here is how I would actually structure that exporter. Notice how the export()` method is marked as final. This is crucial. I don't want a subclass "accidentally" forgetting to close the file or skipping the header.
public abstract class DataExporter {
// This is the Template Method
public final void export(Data data) {
openFile();
writeHeader();
writeData(data);
closeFile();
}
private void openFile() {
System.out.println("Opening file stream...");
}
private void closeFile() {
System.out.println("Closing file stream...");
}
// These are the "blanks" subclasses must fill in
protected abstract void writeHeader();
protected abstract void writeData(Data data);
}
public class CsvExporter extends DataExporter {
@Override
protected void writeHeader() {
System.out.println("Writing CSV Header: ID, Name, Value");
}
@Override
protected void writeData(Data data) {
System.out.println("Writing data as comma-separated values.");
}
}
By doing this, the DataExporter owns the "how" (the process), while the CsvExporter owns the "what" (the format). I've effectively removed the duplication and guaranteed that the file is always closed, regardless of the format.
Adding Flexibility with Hooks
Sometimes, a subclass needs to do something optional. I don't want to force every single exporter to implement a method if they don't need it. This is where "hooks" come in. A hook is simply a method in the base class that has a default (usually empty) implementation. Subclasses can override it if they want to, but they aren't required to.
For example, imagine some exports need to be compressed into a ZIP file, but others don't. I'd add a hook like this to the base class:
protected void compressFile() {
// Default implementation: do nothing.
}
Then, I'd add compressFile() as a step inside the final export()` method. Most exporters will just ignore it, but my ZipPdfExporter can override it to add that specific functionality without breaking the overall template. It's a subtle but powerful way to keep your hierarchy clean.
📋 Practical Task
Build a Custom Log File Processor
You are tasked with creating a log processing system that reads files and performs different actions based on the log type (e.g., Security logs vs. Application logs). The workflow for all log processors must be: 1. Read the file. 2. Validate the log format. 3. Parse the entries. 4. Close the reader.
Your Requirements:
- Create an abstract class
LogProcessor. - Implement a
finalmethod calledprocessLog()that defines the four-step sequence. - Implement
read()`andclose()`as private methods in the base class (since they are the same for all logs). - Define
validateFormat()`andparseEntries()`as abstract methods. - Create two concrete classes:
SecurityLogProcessorandAppLogProcessorthat provide their own specific validation and parsing logic. - Add an optional "hook" method called
alertAdmin()`in the base class that does nothing by default, but is called at the end ofprocessLog(). Override this hook only in theSecurityLogProcessorto print "CRITICAL: Security breach detected!"
There are no comments for now.