Software Architecture Design Patterns
1. The Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful when exactly one object is needed to coordinate actions across the system. For example, in a logging service, you might want to ensure that only one instance of the logger exists to prevent conflicting log entries.
Implementation Example:
javapublic class Logger { private static Logger instance; private Logger() {} public static Logger getInstance() { if (instance == null) { instance = new Logger(); } return instance; } }
2. The Observer Pattern
The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. This is particularly useful in implementing distributed event-handling systems. For instance, in a stock market application, different components (such as charts and notifications) need to update whenever stock prices change.
Implementation Example:
javapublic interface Observer { void update(String stockPrice); } public class Stock implements Subject { private List
observers = new ArrayList<>(); private String stockPrice; public void addObserver(Observer observer) { observers.add(observer); } public void removeObserver(Observer observer) { observers.remove(observer); } public void setStockPrice(String price) { this.stockPrice = price; notifyObservers(); } private void notifyObservers() { for (Observer observer : observers) { observer.update(stockPrice); } } }
3. The Factory Method Pattern
The Factory Method pattern provides an interface for creating objects but allows subclasses to alter the type of objects that will be created. This pattern is useful when a class cannot anticipate the class of objects it must create. For example, in a graphics application, you might use the Factory Method pattern to create different types of shapes (circles, rectangles) without specifying the exact class of object that will be created.
Implementation Example:
javapublic abstract class ShapeFactory { public abstract Shape createShape(); } public class CircleFactory extends ShapeFactory { public Shape createShape() { return new Circle(); } } public class RectangleFactory extends ShapeFactory { public Shape createShape() { return new Rectangle(); } }
4. The Decorator Pattern
The Decorator pattern allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class. This is useful for adhering to the Open/Closed Principle, which states that software entities should be open for extension but closed for modification. For example, you can use decorators to add additional features to a graphical window without modifying its core functionality.
Implementation Example:
javapublic interface Window { void draw(); } public class SimpleWindow implements Window { public void draw() { System.out.println("Drawing a simple window."); } } public abstract class WindowDecorator implements Window { protected Window decoratedWindow; public WindowDecorator(Window decoratedWindow) { this.decoratedWindow = decoratedWindow; } public void draw() { decoratedWindow.draw(); } } public class ScrollableWindowDecorator extends WindowDecorator { public ScrollableWindowDecorator(Window decoratedWindow) { super(decoratedWindow); } public void draw() { decoratedWindow.draw(); setScrolling(decoratedWindow); } private void setScrolling(Window window) { System.out.println("Adding scrolling functionality."); } }
5. The Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each algorithm, and makes them interchangeable. This pattern allows the algorithm to vary independently from clients that use it. For instance, a payment processing system might use different payment strategies (credit card, PayPal) that can be selected at runtime.
Implementation Example:
javapublic interface PaymentStrategy { void pay(int amount); } public class CreditCardPayment implements PaymentStrategy { public void pay(int amount) { System.out.println("Paying " + amount + " using credit card."); } } public class PayPalPayment implements PaymentStrategy { public void pay(int amount) { System.out.println("Paying " + amount + " using PayPal."); } } public class ShoppingCart { private PaymentStrategy paymentStrategy; public void setPaymentStrategy(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } public void checkout(int amount) { paymentStrategy.pay(amount); } }
6. The Adapter Pattern
The Adapter pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces by converting the interface of a class into another interface that clients expect. For example, you might use an adapter to integrate a legacy system with a new system by making the old system's API compatible with the new system's API.
Implementation Example:
javapublic interface Target { void request(); } public class Adaptee { public void specificRequest() { System.out.println("Specific request from Adaptee."); } } public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } }
Conclusion
Understanding and implementing software architecture design patterns can greatly enhance the quality and flexibility of software systems. By applying these patterns, developers can tackle common design issues in a structured way and ensure that their solutions are maintainable and scalable. Whether you are building a new application or refactoring an existing one, these patterns offer valuable guidance and best practices for creating well-designed software systems.
Popular Comments
No Comments Yet