Design Patterns and Project Standards Implementation Guide
Chain of Responsibility Pattern
This pattern decouples senders from receivers.
Benefits include: reducing coupling between sender and receiver objects, simplifying their structures, and enabling easy extension of new handlers.
Drawbacks involve debugging difficulties and potential performance issues, with risk of creating infinite loops if misused.
Common applications include interceptors and filters that process requests based on matching criteria. Interceptors use dynamic proxy implemantation while filters utilize callback functions.
Example filter implementation:
@Slf4j
@WebFilter(filterName = "requestModifier", urlPatterns = "/api/*")
public class RequestModificationFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
EnhancedRequestWrapper wrapper = new EnhancedRequestWrapper(httpRequest);
filterChain.doFilter(wrapper, servletResponse);
}
@Override
public void destroy() {
log.info("Cleaning up request modification filter!");
}
}
Strategy Pattern
Encapsulates multiple algorithms that can replace each other, eliminating traditional if-else statements.
Implementation involves packaging each algorithm into separate classes implementing a comon interface.
Benefits include good extensibility and algorithm switching capabilities.
Drawbacks require knowledge of available algorithms and can become cumbersome when managing numerous algorithm classes.
Usage example: different discount calculations for various membership tiers like standard, platinum, and gold members.
The core concept utilizes polymorphism to replace conditional statements.
Template Method Pattern (Most Common + Manager Approach)
Defines an algorithmic structure while deferring specific steps to subclasses.
An abstract class publicly defines execution templates, allowing subclasses to override methods while maintaining consistent invocation patterns.
Benefits include code reusability by implementing common methods once instead of repeating in every subclass.
Applications involve abstract classes containing shared functionality for subclasses to utilize or override.
Implementation steps:
- Define interface:
public interface ProcessingInterface {
List<ProcessingResult> execute(ProcessData data);
String getProcessType();
}
- Abstract class extending interface:
public abstract class BaseProcessor implements ProcessingInterface {
// Template method implementation
// Shared processing logic
}
- Concrete implementations (different order types):
public class AirlineProcessor extends BaseProcessor {
@Override
public List<ProcessingResult> execute(ProcessData data) {
// Airline-specific processing logic
return null;
}
@Override
public String getProcessType() {
return "AIRLINE";
}
}
public class RailwayProcessor extends BaseProcessor {
@Override
public List<ProcessingResult> execute(ProcessData data) {
// Railway-specific processing logic
return null;
}
@Override
public String getProcessType() {
return "RAILWAY";
}
}
- Register implementations during startup:
@Component("processorRegistry")
public class ProcessorRegistry {
@Autowired
private ProcessingInterface[] processors;
private Map<String, ProcessingInterface> processorMap = new HashMap<>();
@PostConstruct
public void initializeMap() {
Arrays.stream(processors)
.forEach(proc -> processorMap.put(proc.getProcessType(), proc));
}
public ProcessingInterface retrieveProcessor(String type) {
return processorMap.getOrDefault(type, processorMap.get("DEFAULT"));
}
}
This approach combines factory and template patterns.
Observer Pattern
When an object's state changes, all dependent objects receive notifications and automatic updates.
Purpose is loose coupling and weakened dependency relationships, primarily used within systems.
Publish-Subscribe Pattern
Producers generate objects and place them in queues or buffers for consumers to process.
Observer and observed maintain loose coupling, while publishers and subscribers have no direct coupling.
Observer patterns work within single applications, while publish-subscribe represents cross-application patterns like message middleware.
Adapter Pattern
Coordinates incompatible enterfaces, serving as bridges between incompatible components.
Focuses on conversion and compatibility.
Benefits include improved class reusability and flexibility.
Drawbacks create system complexity and are limited by Java's single inheritance model.
Example: InputStreamReader and OutputStreamWriter serve as adapters converting between byte and character streams.
Composite Pattern
Represents part-whole hierarchies, treating groups of similar objects as single entities.
Ensures consistent usage of individual and composite objects.
Applications include tree menus and file/directory management.
Decorator Pattern
Adds functionality without altering existing structure.
Example: BufferedInputStream adds caching capability to InputStream.
Design Principles
Single Responsibility Principle: Each class handles one specific responsibility.
Interface Segregation Principle: Decompose large interfaces into smaller granular ones to prevent change propagation.
Dependency Inversion Principle: High-level modules should not depend on low-level modules to reduce coupling.
Liskov Substitution Principle: Subtypes must be substitutable for their base types without affecting functionality.
Open-Closed Principle: Open for extension, closed for modification.
Law of Demeter: Minimize communication with unfamiliar objects, focus on own responsibilities.