Design Pattern 11: Adapter Pattern - Complete Guide with Real-World Stock Data Integration Examples
π Download the complete Design Pattern series code from our design_pattern repository.
π― What is the Adapter Pattern?
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by wrapping an existing class with a new interface, enabling objects with incompatible interfaces to collaborate.
Key Benefits:
- β Interface Compatibility - Make incompatible interfaces work together
- β Legacy Integration - Integrate old systems with new code
- β Third-party Integration - Work with external APIs and libraries
- β Code Reusability - Reuse existing code without modification
- β Flexibility - Support multiple interface variations
π Real-World Problem: Stock Data Integration System
Letβs design a stock data integration system with the following requirements:
System Requirements:
- Integrate existing XML-based stock system with new JSON-based analysis system
- Support multiple data formats (XML, JSON, CSV) for future expansion
- Maintain backward compatibility with existing XML system
- Enable real-time data processing with new analysis tools
- Provide unified interface for all data sources
Business Rules:
- Existing system provides stock data in XML format
- New analysis system expects JSON format
- Data transformation should be transparent to clients
- System should handle data validation and error recovery
- Performance should not be significantly impacted by transformation
ποΈ Object-Oriented Analysis (OOA)
Letβs analyze the problem and identify the core components:

Identified Forces:
- Interface Incompatibility
- Existing XML system and new JSON system have different interfaces
- Direct integration would require significant code changes
- No common interface between the two systems
- Legacy System Constraints
- Cannot modify existing XML system due to business constraints
- Need to maintain backward compatibility
- Risk of breaking existing functionality
- Integration Complexity
- Data format conversion required (XML to JSON)
- Different data structures and field mappings
- Error handling for format conversion
π‘ Adapter Pattern Solution
After analyzing the forces, we can apply the Adapter Pattern to create a compatible interface:

Adapter Pattern Components:
- Target Interface - The interface that the client expects
- Adaptee - The existing class with incompatible interface
- Adapter - The class that makes incompatible interfaces work together
- Client - Uses the target interface
Benefits:
- Seamless integration - Clients work with familiar interface
- No legacy modification - Existing code remains unchanged
- Flexible design - Support multiple adapters for different sources
- Easy testing - Mock adapters for testing
π οΈ Implementation: Stock Data Integration System

1. Target Interface (JSON Analyzer)
interface JsonAnalyzer {
fun analyzeJsonData(json: String): AnalysisResult
fun validateJsonData(json: String): ValidationResult
fun getSupportedFormats(): List<String>
}
data class AnalysisResult(
val success: Boolean,
val data: Map<String, Any>,
val timestamp: Long,
val processingTime: Long,
val errors: List<String> = emptyList()
)
data class ValidationResult(
val isValid: Boolean,
val errors: List<String> = emptyList(),
val warnings: List<String> = emptyList()
)
2. Adaptee (XML Stock Data System)
class XmlStockData {
private val stockData = mapOf(
"stocks" to listOf(
mapOf(
"symbol" to "TSLA",
"price" to "675.50",
"volume" to "12500000",
"change" to "+12.30",
"changePercent" to "+1.86%"
),
mapOf(
"symbol" to "AMZN",
"price" to "3201.65",
"volume" to "8900000",
"change" to "-15.20",
"changePercent" to "-0.47%"
),
mapOf(
"symbol" to "GOOGL",
"price" to "2850.25",
"volume" to "15600000",
"change" to "+8.75",
"changePercent" to "+0.31%"
)
)
)
fun getXmlData(): String {
return buildString {
appendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")
appendLine("<stocks>")
stockData["stocks"]?.forEach { stock ->
appendLine(" <stock>")
appendLine(" <symbol>${stock["symbol"]}</symbol>")
appendLine(" <price>${stock["price"]}</price>")
appendLine(" <volume>${stock["volume"]}</volume>")
appendLine(" <change>${stock["change"]}</change>")
appendLine(" <changePercent>${stock["changePercent"]}</changePercent>")
appendLine(" </stock>")
}
appendLine("</stocks>")
}
}
fun getStockCount(): Int = stockData["stocks"]?.size ?: 0
fun getLastUpdateTime(): Long = System.currentTimeMillis()
}
3. Adapter Implementation
class StockDataAdapter(
private val xmlStockData: XmlStockData
) : JsonAnalyzer {
override fun analyzeJsonData(json: String): AnalysisResult {
val startTime = System.currentTimeMillis()
return try {
// Parse JSON data
val jsonData = parseJson(json)
// Perform analysis
val analysisData = performAnalysis(jsonData)
val processingTime = System.currentTimeMillis() - startTime
AnalysisResult(
success = true,
data = analysisData,
timestamp = System.currentTimeMillis(),
processingTime = processingTime
)
} catch (e: Exception) {
AnalysisResult(
success = false,
data = emptyMap(),
timestamp = System.currentTimeMillis(),
processingTime = System.currentTimeMillis() - startTime,
errors = listOf("Analysis failed: ${e.message}")
)
}
}
override fun validateJsonData(json: String): ValidationResult {
return try {
parseJson(json)
ValidationResult(isValid = true)
} catch (e: Exception) {
ValidationResult(
isValid = false,
errors = listOf("Invalid JSON: ${e.message}")
)
}
}
override fun getSupportedFormats(): List<String> = listOf("JSON", "XML")
// Adapter-specific method to convert XML to JSON
fun convertAndAnalyze(): AnalysisResult {
val startTime = System.currentTimeMillis()
return try {
// Get XML data from existing system
val xmlData = xmlStockData.getXmlData()
// Convert XML to JSON
val jsonData = convertXmlToJson(xmlData)
// Analyze the converted data
val analysisData = performAnalysis(jsonData)
val processingTime = System.currentTimeMillis() - startTime
AnalysisResult(
success = true,
data = analysisData,
timestamp = System.currentTimeMillis(),
processingTime = processingTime
)
} catch (e: Exception) {
AnalysisResult(
success = false,
data = emptyMap(),
timestamp = System.currentTimeMillis(),
processingTime = System.currentTimeMillis() - startTime,
errors = listOf("Conversion failed: ${e.message}")
)
}
}
private fun convertXmlToJson(xml: String): Map<String, Any> {
// Simulate XML to JSON conversion
return mapOf(
"stocks" to listOf(
mapOf(
"symbol" to "TSLA",
"price" to 675.50,
"volume" to 12500000,
"change" to 12.30,
"changePercent" to 1.86
),
mapOf(
"symbol" to "AMZN",
"price" to 3201.65,
"volume" to 8900000,
"change" to -15.20,
"changePercent" to -0.47
),
mapOf(
"symbol" to "GOOGL",
"price" to 2850.25,
"volume" to 15600000,
"change" to 8.75,
"changePercent" to 0.31
)
),
"metadata" to mapOf(
"source" to "XML System",
"conversionTime" to System.currentTimeMillis(),
"totalStocks" to xmlStockData.getStockCount()
)
)
}
private fun parseJson(json: String): Map<String, Any> {
// Simulate JSON parsing
return mapOf("parsed" to true, "data" to json)
}
private fun performAnalysis(data: Map<String, Any>): Map<String, Any> {
val stocks = data["stocks"] as? List<Map<String, Any>> ?: emptyList()
val totalValue = stocks.sumOf {
(it["price"] as? Number)?.toDouble() ?: 0.0
}
val averagePrice = if (stocks.isNotEmpty()) totalValue / stocks.size else 0.0
val totalVolume = stocks.sumOf {
(it["volume"] as? Number)?.toLong() ?: 0L
}
return mapOf(
"summary" to mapOf(
"totalStocks" to stocks.size,
"totalValue" to totalValue,
"averagePrice" to averagePrice,
"totalVolume" to totalVolume
),
"topPerformers" to stocks
.filter { (it["changePercent"] as? Number)?.toDouble() ?: 0.0 > 0 }
.sortedByDescending { (it["changePercent"] as? Number)?.toDouble() ?: 0.0 }
.take(3),
"worstPerformers" to stocks
.filter { (it["changePercent"] as? Number)?.toDouble() ?: 0.0 < 0 }
.sortedBy { (it["changePercent"] as? Number)?.toDouble() ?: 0.0 }
.take(3),
"analysis" to mapOf(
"timestamp" to System.currentTimeMillis(),
"dataSource" to "XML Adapter"
)
)
}
}
4. Enhanced Adapter with Multiple Sources
class MultiSourceStockAdapter : JsonAnalyzer {
private val xmlAdapter = StockDataAdapter(XmlStockData())
private val csvAdapter = CsvStockDataAdapter()
private val jsonAdapter = DirectJsonAdapter()
override fun analyzeJsonData(json: String): AnalysisResult {
return jsonAdapter.analyzeJsonData(json)
}
override fun validateJsonData(json: String): ValidationResult {
return jsonAdapter.validateJsonData(json)
}
override fun getSupportedFormats(): List<String> = listOf("JSON", "XML", "CSV")
fun analyzeFromXml(): AnalysisResult = xmlAdapter.convertAndAnalyze()
fun analyzeFromCsv(csvData: String): AnalysisResult = csvAdapter.convertAndAnalyze(csvData)
fun analyzeFromJson(jsonData: String): AnalysisResult = jsonAdapter.analyzeJsonData(jsonData)
}
class CsvStockDataAdapter {
fun convertAndAnalyze(csvData: String): AnalysisResult {
// Simulate CSV to JSON conversion and analysis
return AnalysisResult(
success = true,
data = mapOf("source" to "CSV", "converted" to true),
timestamp = System.currentTimeMillis(),
processingTime = 50
)
}
}
class DirectJsonAdapter : JsonAnalyzer {
override fun analyzeJsonData(json: String): AnalysisResult {
return AnalysisResult(
success = true,
data = mapOf("source" to "JSON", "direct" to true),
timestamp = System.currentTimeMillis(),
processingTime = 10
)
}
override fun validateJsonData(json: String): ValidationResult {
return ValidationResult(isValid = true)
}
override fun getSupportedFormats(): List<String> = listOf("JSON")
}
5. Client Code
fun main() {
println("=== Stock Data Integration Demo ===\n")
// Create adapter
val adapter = StockDataAdapter(XmlStockData())
val multiAdapter = MultiSourceStockAdapter()
// Test XML to JSON conversion and analysis
println("π Analyzing stock data from XML system...")
val xmlResult = adapter.convertAndAnalyze()
if (xmlResult.success) {
println("β
XML analysis successful!")
println("π Processing time: ${xmlResult.processingTime}ms")
println("π Analysis results:")
val summary = xmlResult.data["summary"] as? Map<String, Any>
println(" β’ Total Stocks: ${summary?.get("totalStocks")}")
println(" β’ Total Value: $${summary?.get("totalValue")}")
println(" β’ Average Price: $${summary?.get("averagePrice")}")
println(" β’ Total Volume: ${summary?.get("totalVolume")}")
val topPerformers = xmlResult.data["topPerformers"] as? List<Map<String, Any>>
println(" β’ Top Performers: ${topPerformers?.map { "${it["symbol"]} (+${it["changePercent"]}%)" }}")
} else {
println("β XML analysis failed: ${xmlResult.errors}")
}
println()
// Test direct JSON analysis
println("π Analyzing direct JSON data...")
val jsonData = """
{
"stocks": [
{"symbol": "AAPL", "price": 150.25, "change": 2.50},
{"symbol": "MSFT", "price": 280.75, "change": -1.25}
]
}
""".trimIndent()
val jsonResult = adapter.analyzeJsonData(jsonData)
if (jsonResult.success) {
println("β
JSON analysis successful!")
println("π Processing time: ${jsonResult.processingTime}ms")
} else {
println("β JSON analysis failed: ${jsonResult.errors}")
}
println()
// Test multi-source adapter
println("π Testing multi-source adapter...")
println("π Supported formats: ${multiAdapter.getSupportedFormats()}")
val xmlMultiResult = multiAdapter.analyzeFromXml()
val csvMultiResult = multiAdapter.analyzeFromCsv("AAPL,150.25,2.50")
val jsonMultiResult = multiAdapter.analyzeFromJson(jsonData)
println("β
Multi-source analysis complete!")
println(" β’ XML: ${if (xmlMultiResult.success) "β
" else "β"}")
println(" β’ CSV: ${if (csvMultiResult.success) "β
" else "β"}")
println(" β’ JSON: ${if (jsonMultiResult.success) "β
" else "β"}")
println()
// Performance comparison
println("π Performance Comparison:")
println(" β’ XML Analysis: ${xmlResult.processingTime}ms")
println(" β’ JSON Analysis: ${jsonResult.processingTime}ms")
println(" β’ Direct JSON: ${jsonMultiResult.processingTime}ms")
}
Expected Output:
=== Stock Data Integration Demo ===
π Analyzing stock data from XML system...
β
XML analysis successful!
π Processing time: 45ms
π Analysis results:
β’ Total Stocks: 3
β’ Total Value: 6727.4
β’ Average Price: 2242.47
β’ Total Volume: 37000000
β’ Top Performers: [TSLA (+1.86%), GOOGL (+0.31%)]
π Analyzing direct JSON data...
β
JSON analysis successful!
π Processing time: 12ms
π Testing multi-source adapter...
π Supported formats: [JSON, XML, CSV]
β
Multi-source analysis complete!
β’ XML: β
β’ CSV: β
β’ JSON: β
π Performance Comparison:
β’ XML Analysis: 45ms
β’ JSON Analysis: 12ms
β’ Direct JSON: 10ms
π Adapter Pattern vs Alternative Approaches
Approach | Pros | Cons |
---|---|---|
Adapter Pattern | β
Interface compatibility β No legacy modification β Reusable design | β Additional layer β Potential performance overhead β Increased complexity |
Direct Modification | β
No overhead β Direct integration β Simple implementation | β Breaks existing code β High risk β Maintenance burden |
Wrapper Classes | β
Encapsulation β Clean interface | β Different purpose (encapsulation vs compatibility) |
Interface Segregation | β
Clean interfaces β Better design | β Requires system redesign β Not always feasible |
π― When to Use the Adapter Pattern
β Perfect For:
- Legacy system integration (old systems with new code)
- Third-party API integration (external services with different interfaces)
- Interface compatibility (making incompatible interfaces work together)
- Library integration (using libraries with different interfaces)
- Data format conversion (XML to JSON, CSV to XML, etc.)
β Avoid When:
- Simple interface changes (direct modification is simpler)
- Performance-critical applications (adapter overhead)
- Frequent interface changes (adapter becomes maintenance burden)
- New system design (design compatible interfaces from start)
π§ Advanced Adapter Pattern Implementations
1. Adapter with Caching
class CachedStockDataAdapter(
private val xmlStockData: XmlStockData,
private val cache: MutableMap<String, AnalysisResult> = mutableMapOf(),
private val cacheTimeout: Long = 60000 // 1 minute
) : JsonAnalyzer {
override fun analyzeJsonData(json: String): AnalysisResult {
val cacheKey = "json_${json.hashCode()}"
val cached = cache[cacheKey]
if (cached != null && !isExpired(cached.timestamp)) {
println("π Returning cached result for JSON analysis")
return cached
}
val result = performJsonAnalysis(json)
cache[cacheKey] = result
return result
}
fun convertAndAnalyze(): AnalysisResult {
val cacheKey = "xml_conversion"
val cached = cache[cacheKey]
if (cached != null && !isExpired(cached.timestamp)) {
println("π Returning cached result for XML conversion")
return cached
}
val result = performXmlConversion()
cache[cacheKey] = result
return result
}
private fun isExpired(timestamp: Long): Boolean {
return System.currentTimeMillis() - timestamp > cacheTimeout
}
private fun performJsonAnalysis(json: String): AnalysisResult {
// Implementation for JSON analysis
return AnalysisResult(
success = true,
data = mapOf("source" to "JSON", "cached" to false),
timestamp = System.currentTimeMillis(),
processingTime = 15
)
}
private fun performXmlConversion(): AnalysisResult {
// Implementation for XML conversion
return AnalysisResult(
success = true,
data = mapOf("source" to "XML", "cached" to false),
timestamp = System.currentTimeMillis(),
processingTime = 50
)
}
fun clearCache() {
cache.clear()
println("ποΈ Cache cleared")
}
fun getCacheSize(): Int = cache.size
}
2. Adapter with Error Recovery
class ResilientStockDataAdapter(
private val xmlStockData: XmlStockData,
private val maxRetries: Int = 3
) : JsonAnalyzer {
override fun analyzeJsonData(json: String): AnalysisResult {
return retryOperation("JSON Analysis") {
performJsonAnalysis(json)
}
}
fun convertAndAnalyze(): AnalysisResult {
return retryOperation("XML Conversion") {
performXmlConversion()
}
}
private fun <T> retryOperation(operationName: String, operation: () -> T): T {
var lastException: Exception? = null
for (attempt in 1..maxRetries) {
try {
return operation()
} catch (e: Exception) {
lastException = e
println("β οΈ $operationName attempt $attempt failed: ${e.message}")
if (attempt < maxRetries) {
val delay = attempt * 1000L // Exponential backoff
println("β³ Retrying in ${delay}ms...")
Thread.sleep(delay)
}
}
}
throw RuntimeException("$operationName failed after $maxRetries attempts", lastException)
}
private fun performJsonAnalysis(json: String): AnalysisResult {
// Simulate potential failure
if (Math.random() < 0.3) {
throw RuntimeException("Simulated JSON analysis failure")
}
return AnalysisResult(
success = true,
data = mapOf("source" to "JSON", "retry" to false),
timestamp = System.currentTimeMillis(),
processingTime = 20
)
}
private fun performXmlConversion(): AnalysisResult {
// Simulate potential failure
if (Math.random() < 0.2) {
throw RuntimeException("Simulated XML conversion failure")
}
return AnalysisResult(
success = true,
data = mapOf("source" to "XML", "retry" to false),
timestamp = System.currentTimeMillis(),
processingTime = 60
)
}
}
3. Adapter with Monitoring
class MonitoredStockDataAdapter(
private val adapter: JsonAnalyzer,
private val monitor: AdapterMonitor
) : JsonAnalyzer {
override fun analyzeJsonData(json: String): AnalysisResult {
val startTime = System.currentTimeMillis()
return try {
monitor.recordOperation("json_analysis", startTime)
val result = adapter.analyzeJsonData(json)
monitor.recordSuccess("json_analysis", System.currentTimeMillis() - startTime)
result
} catch (e: Exception) {
monitor.recordError("json_analysis", e.message ?: "Unknown error")
throw e
}
}
override fun validateJsonData(json: String): ValidationResult {
return adapter.validateJsonData(json)
}
override fun getSupportedFormats(): List<String> = adapter.getSupportedFormats()
}
class AdapterMonitor {
private val operations = mutableListOf<OperationRecord>()
private val errors = mutableListOf<ErrorRecord>()
fun recordOperation(type: String, startTime: Long) {
operations.add(OperationRecord(type, startTime, null, null))
println("π Started operation: $type")
}
fun recordSuccess(type: String, duration: Long) {
operations.lastOrNull { it.type == type && it.duration == null }?.let {
it.duration = duration
it.success = true
}
println("β
Operation completed: $type (${duration}ms)")
}
fun recordError(type: String, error: String) {
errors.add(ErrorRecord(type, error, System.currentTimeMillis()))
operations.lastOrNull { it.type == type && it.duration == null }?.let {
it.duration = System.currentTimeMillis() - it.startTime
it.success = false
}
println("β Operation failed: $type - $error")
}
fun getReport(): String {
val successfulOps = operations.filter { it.success == true }
val failedOps = operations.filter { it.success == false }
val avgDuration = successfulOps.mapNotNull { it.duration }.average()
return buildString {
appendLine("=== Adapter Monitor Report ===")
appendLine("Total Operations: ${operations.size}")
appendLine("Successful: ${successfulOps.size}")
appendLine("Failed: ${failedOps.size}")
appendLine("Average Duration: ${String.format("%.2f", avgDuration)}ms")
appendLine("Recent Errors:")
errors.takeLast(3).forEach { error ->
appendLine(" - ${error.type}: ${error.message}")
}
appendLine("=============================")
}
}
}
data class OperationRecord(
val type: String,
val startTime: Long,
var duration: Long?,
var success: Boolean?
)
data class ErrorRecord(
val type: String,
val message: String,
val timestamp: Long
)
π Real-World Applications
1. API Integration
- Payment gateways - Adapt different payment provider interfaces
- Social media APIs - Unify different platform interfaces
- Cloud services - Abstract cloud provider differences
- Database drivers - Standardize database access interfaces
2. Legacy System Integration
- Mainframe integration - Connect old systems with modern applications
- File format conversion - Convert between different data formats
- Protocol adaptation - Bridge different communication protocols
- Hardware interfaces - Adapt different device interfaces
3. Library and Framework Integration
- Third-party libraries - Adapt incompatible library interfaces
- Version compatibility - Bridge different API versions
- Language interoperability - Connect different programming languages
- Platform abstraction - Abstract platform-specific differences
4. Data Processing
- ETL pipelines - Transform data between different formats
- Message queues - Adapt different messaging protocols
- Stream processing - Convert between different stream formats
- Configuration management - Adapt different config formats
π Performance Considerations
Adapter Overhead
- Method delegation - Additional method calls through adapter
- Data transformation - Cost of converting between formats
- Memory usage - Adapter object memory footprint
- Caching strategies - Cache expensive transformations
Optimization Techniques
- Lazy loading - Defer expensive operations until needed
- Connection pooling - Reuse expensive connections
- Batch processing - Process multiple items together
- Async operations - Handle transformations asynchronously
π Related Design Patterns
- Bridge Pattern - For abstraction-implementation separation
- Facade Pattern - For simplifying complex interfaces
- Decorator Pattern - For adding functionality
- Proxy Pattern - For access control
π Best Practices
1. Adapter Design
- Single responsibility - Each adapter should handle one specific conversion
- Error handling - Proper error handling for conversion failures
- Performance optimization - Minimize conversion overhead
- Documentation - Clear documentation of conversion logic
2. Interface Design
- Target interface - Design clean, stable target interfaces
- Extensibility - Support future interface changes
- Compatibility - Ensure backward compatibility when possible
- Validation - Validate data during conversion
3. Implementation Guidelines
- Thread safety - Handle concurrent access properly
- Resource management - Properly manage resources and connections
- Testing strategies - Test with various data formats and edge cases
- Monitoring - Monitor adapter performance and errors
π― Conclusion
The Adapter Pattern provides a powerful way to integrate incompatible interfaces and systems. By creating a bridge between different interfaces, it enables:
- Seamless integration of legacy and new systems
- Flexible architecture that supports multiple data sources
- Maintainable code without modifying existing systems
- Reusable components for different integration scenarios
This pattern is essential for building flexible, maintainable systems that need to work with various external interfaces and legacy systems. Whether youβre integrating APIs, connecting legacy systems, or handling multiple data formats, the Adapter Pattern provides the foundation for robust system integration.
Next Steps:
- Explore the Bridge Pattern for abstraction-implementation separation
- Learn about the Facade Pattern for interface simplification
- Discover the Decorator Pattern for adding functionality
Ready to implement the Adapter Pattern in your projects? Download the complete code examples from our design_pattern repository and start building more flexible, integrable systems today!
Enjoy Reading This Article?
Here are some more articles you might like to read next: