Applying the Service Locator Pattern for Extensible File Parsers in Spring
This article demonstrates how to replace tightly‑coupled if‑else or switch‑case logic with the Service Locator Pattern in a Spring application, allowing new file‑type parsers such as XML to be added without modifying client code, thereby adhering to the Open/Closed principle.
When a project needs to parse different file types (e.g., CSV, JSON), developers often use if else or switch case statements to select the appropriate parser, which tightly couples the client to concrete implementations. The Service Locator Pattern can decouple the client from specific parsers.
Example: File Parser
First, define an enum for content types:
public enum ContentType {
JSON,
CSV
}Define a parser interface:
public interface Parser {
List parse(Reader r);
}Implement concrete parsers:
// CSV parser
@Component
public class CSVParser implements Parser {
@Override
public List parse(Reader r) { /* ... */ }
}
// JSON parser
@Component
public class JSONParser implements Parser {
@Override
public List parse(Reader r) { /* ... */ }
}A naïve client would use a switch case to choose the parser:
@Service
public class Client {
private Parser csvParser, jsonParser;
@Autowired
public Client(Parser csvParser, Parser jsonParser) {
this.csvParser = csvParser;
this.jsonParser = jsonParser;
}
public List getAll(ContentType contentType) {
switch (contentType) {
case CSV: return csvParser.parse(reader);
case JSON: return jsonParser.parse(reader);
// ...
}
}
}This approach requires the client to be modified whenever a new type (e.g., XML) is added, violating the Open/Closed principle.
Applying the Service Locator Pattern
Define a service‑locator interface:
public interface ParserFactory {
Parser getParser(ContentType contentType);
}Configure Spring to create a ServiceLocatorFactoryBean for this interface:
@Configuration
public class ParserConfig {
@Bean("parserFactory")
public FactoryBean serviceLocatorFactoryBean() {
ServiceLocatorFactoryBean factoryBean = new ServiceLocatorFactoryBean();
// set the service‑locator interface
factoryBean.setServiceLocatorInterface(ParserFactory.class);
return factoryBean;
}
}Register each parser bean with a name that matches the enum value:
// CSV parser bean
@Component("CSV")
public class CSVParser implements Parser { /* ... */ }
// JSON parser bean
@Component("JSON")
public class JSONParser implements Parser { /* ... */ }
// XML parser bean
@Component("XML")
public class XMLParser implements Parser { /* ... */ }Update the enum to include the new type:
public enum ContentType {
JSON,
CSV,
XML
}Now the client can obtain the appropriate parser directly from the factory without any switch case :
@Service
public class Client {
private ParserFactory parserFactory;
@Autowired
public Client(ParserFactory parserFactory) {
this.parserFactory = parserFactory;
}
public List getAll(ContentType contentType) {
// directly retrieve the parser for the given type
return parserFactory.getParser(contentType).parse(reader);
}
}With this setup, adding a new parser only requires creating the bean and adding the enum constant; the client code remains unchanged, satisfying the Open/Closed principle.
In summary, the Service Locator Pattern provides an extensible way to decouple clients from concrete implementations, especially when dependency injection alone does not cover certain dynamic lookup scenarios. However, DI remains the preferred approach in most cases.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.