Quickstart
Welcome to tech interview preparation guide
leetcode Quickstart
Welcome to tech interview preparation guide
Design Patterns
SOLID Patterns
Certainly! Let’s take the e-commerce example from above and explain how the SOLID principles can be applied in detail. This will demonstrate how each principle contributes to a well-structured and maintainable system.
1. Single Responsibility Principle (SRP)
Definition: A class should have one, and only one, reason to change.
Application in E-commerce Example:
-
ShoppingCart Class: This class is responsible solely for managing items in the shopping cart. It handles adding and removing items and calculating the total price. If the way items are managed changes (for example, changing from an ArrayList to a different collection), only this class needs to be modified.
class ShoppingCart { private List<Item> items; public ShoppingCart() { this.items = new ArrayList<>(); } public void addItem(Item item) { items.add(item); } public void removeItem(Item item) { items.remove(item); } public double calculateTotal() { return items.stream().mapToDouble(Item::getPrice).sum(); } public List<Item> getItems() { return items; } }
-
EmailService Class: If you want to send confirmation emails after a purchase, you could create a separate
EmailService
class to handle that logic, which keeps the responsibilities distinct.class EmailService { public void sendConfirmationEmail(String email) { // Logic to send confirmation email } }
2. Open/Closed Principle (OCP)
Definition: Software entities should be open for extension but closed for modification.
Application in E-commerce Example:
-
PaymentProcessor Interface: By defining a
PaymentProcessor
interface, you can introduce new payment methods without changing the existing code. For instance, if you want to add aBitcoinProcessor
, you just create a new class that implementsPaymentProcessor
.interface PaymentProcessor { void processPayment(double amount); } class PayPalProcessor implements PaymentProcessor { @Override public void processPayment(double amount) { System.out.println("Processing PayPal payment of " + amount); } } class BitcoinProcessor implements PaymentProcessor { @Override public void processPayment(double amount) { System.out.println("Processing Bitcoin payment of " + amount); } }
3. Liskov Substitution Principle (LSP)
Definition: Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
Application in E-commerce Example:
-
Substitutable Payment Processors: If you have a class that uses a
PaymentProcessor
, you can replace it with any of its subclasses (likePayPalProcessor
orBitcoinProcessor
) without changing the logic of theCheckoutService
.class CheckoutService { private PaymentProcessor paymentProcessor; public CheckoutService(PaymentProcessor paymentProcessor) { this.paymentProcessor = paymentProcessor; } public void checkout(ShoppingCart cart) { double total = cart.calculateTotal(); paymentProcessor.processPayment(total); } }
4. Interface Segregation Principle (ISP)
Definition: No client should be forced to depend on methods it does not use.
Application in E-commerce Example:
-
Separation of Payment Types: Instead of having a single large interface for all payment-related functionalities, you can create smaller, focused interfaces. For instance, you might have a separate interface for processing refunds.
interface PaymentProcessor { void processPayment(double amount); } interface Refundable { void processRefund(double amount); } class PayPalProcessor implements PaymentProcessor, Refundable { @Override public void processPayment(double amount) { // PayPal payment logic } @Override public void processRefund(double amount) { // PayPal refund logic } } class CreditCardProcessor implements PaymentProcessor { @Override public void processPayment(double amount) { // Credit Card payment logic } // No refund method here, thus adhering to ISP }
5. Dependency Inversion Principle (DIP)
Definition: High-level modules should not depend on low-level modules. Both should depend on abstractions.
Application in E-commerce Example:
-
Dependence on Abstractions: Instead of
CheckoutService
depending directly on concrete classes likePayPalProcessor
, it depends on thePaymentProcessor
interface. This allows you to swap out implementations without changing theCheckoutService
.class CheckoutService { private PaymentProcessor paymentProcessor; public CheckoutService(PaymentProcessor paymentProcessor) { this.paymentProcessor = paymentProcessor; } public void checkout(ShoppingCart cart) { double total = cart.calculateTotal(); paymentProcessor.processPayment(total); } }
-
Using Dependency Injection: You can use dependency injection frameworks (like Spring) to manage the creation and injection of
PaymentProcessor
instances, making it easier to manage dependencies and configurations.
Conclusion
Applying the SOLID principles to the e-commerce system not only leads to cleaner and more organized code but also enhances maintainability, flexibility, and testability. Each class and interface has a clear responsibility, making it easier to understand and extend the system as new requirements emerge. By following these principles, developers can create robust software that can adapt to changing business needs with minimal risk of introducing bugs.
Let’s create a complete example of a Hotel Management System that demonstrates the SOLID principles. This example will cover various aspects of a hotel management system, including room management, booking, and notification services.
Example: Hotel Management System
Step 1: Define the Domain Models
We’ll start with the core models: Room
, Booking
, and Customer
.
// Room class
class Room {
private int roomNumber;
private String roomType; // e.g., "Single", "Double", "Suite"
private boolean isAvailable;
public Room(int roomNumber, String roomType) {
this.roomNumber = roomNumber;
this.roomType = roomType;
this.isAvailable = true; // By default, a room is available
}
public int getRoomNumber() {
return roomNumber;
}
public String getRoomType() {
return roomType;
}
public boolean isAvailable() {
return isAvailable;
}
public void setAvailable(boolean available) {
isAvailable = available;
}
}
// Customer class
class Customer {
private String name;
private String email;
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
Step 2: Booking Class
The Booking
class is responsible for managing room bookings.
import java.util.Date;
class Booking {
private Room room;
private Customer customer;
private Date startDate;
private Date endDate;
public Booking(Room room, Customer customer, Date startDate, Date endDate) {
this.room = room;
this.customer = customer;
this.startDate = startDate;
this.endDate = endDate;
}
public Room getRoom() {
return room;
}
public Customer getCustomer() {
return customer;
}
public Date getStartDate() {
return startDate;
}
public Date getEndDate() {
return endDate;
}
}
Step 3: Hotel Class
The Hotel
class manages rooms and bookings and follows the Single Responsibility Principle (SRP).
import java.util.ArrayList;
import java.util.List;
class Hotel {
private List<Room> rooms;
private List<Booking> bookings;
public Hotel() {
this.rooms = new ArrayList<>();
this.bookings = new ArrayList<>();
}
public void addRoom(Room room) {
rooms.add(room);
}
public List<Room> getAvailableRooms() {
List<Room> availableRooms = new ArrayList<>();
for (Room room : rooms) {
if (room.isAvailable()) {
availableRooms.add(room);
}
}
return availableRooms;
}
public void bookRoom(Room room, Customer customer, Date startDate, Date endDate) {
if (room.isAvailable()) {
room.setAvailable(false);
Booking booking = new Booking(room, customer, startDate, endDate);
bookings.add(booking);
} else {
System.out.println("Room is not available!");
}
}
public List<Booking> getBookings() {
return bookings;
}
}
Step 4: NotificationService Interface and Implementations
To adhere to the Open/Closed Principle (OCP), we will create an interface for notifications.
interface NotificationService {
void notifyCustomer(Customer customer, String message);
}
class EmailNotificationService implements NotificationService {
@Override
public void notifyCustomer(Customer customer, String message) {
System.out.println("Sending email to " + customer.getEmail() + ": " + message);
}
}
class SMSNotificationService implements NotificationService {
@Override
public void notifyCustomer(Customer customer, String message) {
System.out.println("Sending SMS to " + customer.getName() + ": " + message);
}
}
Step 5: BookingManager Class
The BookingManager
class handles bookings and notifications. It encapsulates the logic related to booking management and adheres to the SOLID principles.
class BookingManager {
private Hotel hotel;
private NotificationService notificationService;
public BookingManager(Hotel hotel, NotificationService notificationService) {
this.hotel = hotel;
this.notificationService = notificationService;
}
public void bookRoom(Room room, Customer customer, Date startDate, Date endDate) {
hotel.bookRoom(room, customer, startDate, endDate);
notificationService.notifyCustomer(customer, "Your booking for room " + room.getRoomNumber() + " is confirmed.");
}
}
Step 6: Main Application
Now, let’s put everything together in a main application to demonstrate the functionality.
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HotelManagementApplication {
public static void main(String[] args) throws ParseException {
// Create hotel
Hotel hotel = new Hotel();
// Add rooms
hotel.addRoom(new Room(101, "Single"));
hotel.addRoom(new Room(102, "Double"));
hotel.addRoom(new Room(103, "Suite"));
// Create customers
Customer alice = new Customer("Alice", "alice@example.com");
Customer bob = new Customer("Bob", "bob@example.com");
// Choose a notification service
NotificationService notificationService = new EmailNotificationService(); // or new SMSNotificationService();
// Create booking manager
BookingManager bookingManager = new BookingManager(hotel, notificationService);
// Simulate room booking
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date startDate = dateFormat.parse("2024-10-01");
Date endDate = dateFormat.parse("2024-10-05");
bookingManager.bookRoom(hotel.getAvailableRooms().get(0), alice, startDate, endDate); // Book room 101 for Alice
bookingManager.bookRoom(hotel.getAvailableRooms().get(0), bob, startDate, endDate); // Attempt to book room 101 for Bob
}
}
Summary of SOLID Principles Applied
-
Single Responsibility Principle (SRP):
Room
,Customer
, andBooking
classes each have a single responsibility.Hotel
manages room and booking logic.BookingManager
handles the booking process and notifications.
-
Open/Closed Principle (OCP):
- The
NotificationService
interface allows for new notification methods (like push notifications) to be added without modifying existing code.
- The
-
Liskov Substitution Principle (LSP):
- Any implementation of the
NotificationService
can be used interchangeably without affecting the behavior of theBookingManager
. For example, you can switch fromEmailNotificationService
toSMSNotificationService
seamlessly.
- Any implementation of the
-
Interface Segregation Principle (ISP):
- The
NotificationService
interface is focused, ensuring that clients only implement the methods they need. For instance, if we later create aPushNotificationService
, it will only implement the notification method without being burdened with unrelated methods.
- The
-
Dependency Inversion Principle (DIP):
BookingManager
depends on theNotificationService
abstraction, not on concrete implementations. This decoupling allows for easier testing and swapping of notification services.
Conclusion
This complete example illustrates the application of all five SOLID principles in a hotel management system. By following these principles, the system is modular, maintainable, and extensible. Each component is responsible for a specific function, and the design allows for easy adaptation as new requirements emerge, such as adding different types of notifications or additional features related to room management and customer services.
Let's create a complete example of an Online Payment System similar to Google Pay or PayPal that demonstrates the SOLID principles. This example will cover various aspects of an online payment system, including user management, payment processing, and notification services.
Example: Online Payment System
Step 1: Define the Domain Models
We’ll start with the core models: User
, Payment
, and Account
.
// User class
class User {
private String username;
private String email;
public User(String username, String email) {
this.username = username;
this.email = email;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
}
// Account class
class Account {
private User user;
private double balance;
public Account(User user, double initialBalance) {
this.user = user;
this.balance = initialBalance;
}
public User getUser() {
return user;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
balance += amount;
}
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
} else {
throw new IllegalArgumentException("Insufficient balance.");
}
}
}
// Payment class
class Payment {
private Account sender;
private Account receiver;
private double amount;
public Payment(Account sender, Account receiver, double amount) {
this.sender = sender;
this.receiver = receiver;
this.amount = amount;
}
public Account getSender() {
return sender;
}
public Account getReceiver() {
return receiver;
}
public double getAmount() {
return amount;
}
}
Step 2: PaymentProcessor Class
The PaymentProcessor
class is responsible for processing payments and follows the Single Responsibility Principle (SRP).
class PaymentProcessor {
public void processPayment(Payment payment) {
// Withdraw from sender's account
payment.getSender().withdraw(payment.getAmount());
// Deposit to receiver's account
payment.getReceiver().deposit(payment.getAmount());
System.out.println("Payment of $" + payment.getAmount() + " processed from " +
payment.getSender().getUser().getUsername() + " to " +
payment.getReceiver().getUser().getUsername());
}
}
Step 3: NotificationService Interface and Implementations
To adhere to the Open/Closed Principle (OCP), we will create an interface for notifications.
interface NotificationService {
void notifyUser(User user, String message);
}
class EmailNotificationService implements NotificationService {
@Override
public void notifyUser(User user, String message) {
System.out.println("Sending email to " + user.getEmail() + ": " + message);
}
}
class SMSNotificationService implements NotificationService {
@Override
public void notifyUser(User user, String message) {
System.out.println("Sending SMS to " + user.getUsername() + ": " + message);
}
}
Step 4: TransactionManager Class
The TransactionManager
class handles payment transactions and notifications. It encapsulates the logic related to transaction management.
class TransactionManager {
private PaymentProcessor paymentProcessor;
private NotificationService notificationService;
public TransactionManager(PaymentProcessor paymentProcessor, NotificationService notificationService) {
this.paymentProcessor = paymentProcessor;
this.notificationService = notificationService;
}
public void transferFunds(Account sender, Account receiver, double amount) {
Payment payment = new Payment(sender, receiver, amount);
paymentProcessor.processPayment(payment);
// Notify both sender and receiver
notificationService.notifyUser(sender.getUser(), "You sent $" + amount + " to " + receiver.getUser().getUsername());
notificationService.notifyUser(receiver.getUser(), "You received $" + amount + " from " + sender.getUser().getUsername());
}
}
Step 5: Main Application
Now, let’s put everything together in a main application to demonstrate the functionality.
public class PaymentApplication {
public static void main(String[] args) {
// Create users
User alice = new User("Alice", "alice@example.com");
User bob = new User("Bob", "bob@example.com");
// Create accounts
Account aliceAccount = new Account(alice, 1000.00);
Account bobAccount = new Account(bob, 500.00);
// Create notification service
NotificationService notificationService = new EmailNotificationService(); // or new SMSNotificationService();
// Create payment processor and transaction manager
PaymentProcessor paymentProcessor = new PaymentProcessor();
TransactionManager transactionManager = new TransactionManager(paymentProcessor, notificationService);
// Simulate fund transfer
transactionManager.transferFunds(aliceAccount, bobAccount, 200.00);
// Check balances
System.out.println("Alice's balance: $" + aliceAccount.getBalance());
System.out.println("Bob's balance: $" + bobAccount.getBalance());
// Attempt to transfer more than available balance
try {
transactionManager.transferFunds(bobAccount, aliceAccount, 600.00); // Should throw an exception
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
Summary of SOLID Principles Applied
-
Single Responsibility Principle (SRP):
User
,Account
, andPayment
classes each have a single responsibility.PaymentProcessor
is responsible for processing payments.TransactionManager
handles the transaction process and notifications.
-
Open/Closed Principle (OCP):
- The
NotificationService
interface allows for new notification methods (like push notifications) to be added without modifying existing code.
- The
-
Liskov Substitution Principle (LSP):
- Any implementation of the
NotificationService
can be used interchangeably without affecting the behavior of theTransactionManager
. For example, you can switch fromEmailNotificationService
toSMSNotificationService
seamlessly.
- Any implementation of the
-
Interface Segregation Principle (ISP):
- The
NotificationService
interface is focused, ensuring that clients only implement the methods they need. For instance, if we later create aPushNotificationService
, it will only implement the notification method without being burdened with unrelated methods.
- The
-
Dependency Inversion Principle (DIP):
TransactionManager
depends on theNotificationService
abstraction, not on concrete implementations. This decoupling allows for easier testing and swapping of notification services.
Conclusion
This complete example illustrates the application of all five SOLID principles in an online payment system. By following these principles, the system is modular, maintainable, and extensible. Each component is responsible for a specific function, and the design allows for easy adaptation as new requirements emerge, such as adding different types of notifications or additional features related to payment processing and user management.
Creating a Share Market Portal involves various functionalities, such as user management, stock trading, order management, and notifications. Below, I'll provide a structured example that adheres to the SOLID principles.
Example: Share Market Portal
This example includes the following components:
- User Management: Managing users and their portfolios.
- Stock Management: Managing stocks and their prices.
- Order Management: Placing buy/sell orders.
- Notification Services: Notifying users about order statuses and stock updates.
Step 1: Define Domain Models
User Class
class User {
private String id;
private String name;
private double balance;
public User(String id, String name, double balance) {
this.id = id;
this.name = name;
this.balance = balance;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
balance += amount;
}
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
} else {
throw new IllegalArgumentException("Insufficient balance.");
}
}
}
Stock Class
class Stock {
private String symbol;
private double price;
public Stock(String symbol, double price) {
this.symbol = symbol;
this.price = price;
}
public String getSymbol() {
return symbol;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
Step 2: Order Class
Order Class
abstract class Order {
protected User user;
protected Stock stock;
protected int quantity;
public Order(User user, Stock stock, int quantity) {
this.user = user;
this.stock = stock;
this.quantity = quantity;
}
public abstract void execute();
}
Step 3: Concrete Order Classes
BuyOrder Class
class BuyOrder extends Order {
public BuyOrder(User user, Stock stock, int quantity) {
super(user, stock, quantity);
}
@Override
public void execute() {
double totalCost = stock.getPrice() * quantity;
if (user.getBalance() >= totalCost) {
user.withdraw(totalCost);
System.out.println("Buy order executed for " + quantity + " shares of " + stock.getSymbol());
} else {
throw new IllegalArgumentException("Insufficient balance for buy order.");
}
}
}
SellOrder Class
class SellOrder extends Order {
public SellOrder(User user, Stock stock, int quantity) {
super(user, stock, quantity);
}
@Override
public void execute() {
double totalRevenue = stock.getPrice() * quantity;
user.deposit(totalRevenue);
System.out.println("Sell order executed for " + quantity + " shares of " + stock.getSymbol());
}
}
Step 4: OrderService Class
The OrderService
class handles order execution and adheres to the Single Responsibility Principle (SRP).
class OrderService {
public void processOrder(Order order) {
order.execute(); // Delegate executing to the specific order type
}
}
Step 5: NotificationService Interface and Implementations
To adhere to the Open/Closed Principle (OCP), we will create an interface for notifications.
interface NotificationService {
void notifyUser(User user, String message);
}
class EmailNotificationService implements NotificationService {
@Override
public void notifyUser(User user, String message) {
System.out.println("Sending Email Notification to " + user.getName() + ": " + message);
}
}
class SMSNotificationService implements NotificationService {
@Override
public void notifyUser(User user, String message) {
System.out.println("Sending SMS Notification to " + user.getName() + ": " + message);
}
}
Step 6: TradingManager Class
The TradingManager
class handles trading operations and notifications. It encapsulates the logic related to managing trades.
class TradingManager {
private OrderService orderService;
private NotificationService notificationService;
public TradingManager(OrderService orderService, NotificationService notificationService) {
this.orderService = orderService;
this.notificationService = notificationService;
}
public void executeTrade(User user, Stock stock, int quantity, String orderType) {
Order order;
if ("BUY".equalsIgnoreCase(orderType)) {
order = new BuyOrder(user, stock, quantity);
} else if ("SELL".equalsIgnoreCase(orderType)) {
order = new SellOrder(user, stock, quantity);
} else {
throw new IllegalArgumentException("Unsupported order type");
}
orderService.processOrder(order);
notificationService.notifyUser(user, "Your " + orderType + " order for " + quantity + " shares of " + stock.getSymbol() + " has been executed.");
}
}
Step 7: Main Application
Now, let’s put everything together in a main application to demonstrate the functionality.
public class ShareMarketPortalApplication {
public static void main(String[] args) {
// Create users
User alice = new User("1", "Alice", 10000.00);
User bob = new User("2", "Bob", 5000.00);
// Create stocks
Stock appleStock = new Stock("AAPL", 150.00);
Stock googleStock = new Stock("GOOGL", 2800.00);
// Create services
OrderService orderService = new OrderService();
NotificationService emailNotificationService = new EmailNotificationService();
NotificationService smsNotificationService = new SMSNotificationService();
// Create trading managers
TradingManager tradingManagerEmail = new TradingManager(orderService, emailNotificationService);
TradingManager tradingManagerSMS = new TradingManager(orderService, smsNotificationService);
// Simulate buying and selling stocks
tradingManagerEmail.executeTrade(alice, appleStock, 20, "BUY");
tradingManagerSMS.executeTrade(bob, googleStock, 2, "SELL");
// Print remaining balances
System.out.println("Alice's remaining balance: $" + alice.getBalance());
System.out.println("Bob's remaining balance: $" + bob.getBalance());
}
}
Summary of SOLID Principles Applied
-
Single Responsibility Principle (SRP):
User
,Stock
,Order
,BuyOrder
,SellOrder
,OrderService
, andTradingManager
classes each have a single responsibility.NotificationService
implementations are focused on sending notifications.
-
Open/Closed Principle (OCP):
- The
Order
class can be extended to support new order types (e.g., limit orders) without modifying existing code. - The
NotificationService
interface allows for new notification methods to be added without changing existing services.
- The
-
Liskov Substitution Principle (LSP):
- Any implementation of the
Order
class can be used interchangeably in theOrderService
without affecting its behavior.
- Any implementation of the
-
Interface Segregation Principle (ISP):
- The
NotificationService
interface is focused, ensuring that clients only implement the methods they need.
- The
-
Dependency Inversion Principle (DIP):
TradingManager
depends on theOrderService
andNotificationService
abstractions, not on concrete implementations. This decoupling allows for easier testing and swapping of services.
Conclusion
This complete example illustrates the application of all five SOLID principles in a share market portal. By following these principles, the system is modular, maintainable, and extensible. Each component is responsible for a specific function, and the design allows for easy adaptation as new requirements emerge, such as adding new order types, notifications, or additional features related to stock trading and user management.
Creating a complete example of a communication platform similar to Twilio using SOLID design principles involves multiple aspects, such as message handling, user management, and notification services. Below is a structured approach that adheres to the SOLID principles.
Example: Communication Platform (like Twilio)
This example includes the following components:
- User Management: Managing users and their contact information.
- Messaging: Sending and receiving messages.
- Notification Services: Different methods to notify users.
- Message Types: Different types of messages (SMS, Email, etc.).
Step 1: Define Domain Models
User Class
class User {
private String id;
private String name;
private String phoneNumber;
private String email;
public User(String id, String name, String phoneNumber, String email) {
this.id = id;
this.name = name;
this.phoneNumber = phoneNumber;
this.email = email;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
public String getEmail() {
return email;
}
}
Message Class
abstract class Message {
protected User sender;
protected User recipient;
protected String content;
public Message(User sender, User recipient, String content) {
this.sender = sender;
this.recipient = recipient;
this.content = content;
}
public abstract void send();
}
Step 2: Concrete Message Classes
SMSMessage Class
class SMSMessage extends Message {
public SMSMessage(User sender, User recipient, String content) {
super(sender, recipient, content);
}
@Override
public void send() {
// Logic to send SMS
System.out.println("Sending SMS from " + sender.getPhoneNumber() +
" to " + recipient.getPhoneNumber() + ": " + content);
}
}
EmailMessage Class
class EmailMessage extends Message {
public EmailMessage(User sender, User recipient, String content) {
super(sender, recipient, content);
}
@Override
public void send() {
// Logic to send Email
System.out.println("Sending Email from " + sender.getEmail() +
" to " + recipient.getEmail() + ": " + content);
}
}
Step 3: MessageService Class
The MessageService
class handles sending messages and adheres to the Single Responsibility Principle (SRP).
class MessageService {
public void sendMessage(Message message) {
message.send(); // Delegate sending to the specific message type
}
}
Step 4: NotificationService Interface and Implementations
To adhere to the Open/Closed Principle (OCP), we will create an interface for notifications.
interface NotificationService {
void notifyUser(User user, String message);
}
class SMSNotificationService implements NotificationService {
@Override
public void notifyUser(User user, String message) {
System.out.println("Sending SMS Notification to " + user.getPhoneNumber() + ": " + message);
}
}
class EmailNotificationService implements NotificationService {
@Override
public void notifyUser(User user, String message) {
System.out.println("Sending Email Notification to " + user.getEmail() + ": " + message);
}
}
Step 5: CommunicationManager Class
The CommunicationManager
class handles communication between users and notifications. It encapsulates the logic related to managing communications.
class CommunicationManager {
private MessageService messageService;
private NotificationService notificationService;
public CommunicationManager(MessageService messageService, NotificationService notificationService) {
this.messageService = messageService;
this.notificationService = notificationService;
}
public void sendMessage(User sender, User recipient, String content, String messageType) {
Message message;
if ("SMS".equalsIgnoreCase(messageType)) {
message = new SMSMessage(sender, recipient, content);
} else if ("EMAIL".equalsIgnoreCase(messageType)) {
message = new EmailMessage(sender, recipient, content);
} else {
throw new IllegalArgumentException("Unsupported message type");
}
messageService.sendMessage(message);
notificationService.notifyUser(recipient, "You have a new message from " + sender.getName());
}
}
Step 6: Main Application
Now, let’s put everything together in a main application to demonstrate the functionality.
public class CommunicationPlatformApplication {
public static void main(String[] args) {
// Create users
User alice = new User("1", "Alice", "1234567890", "alice@example.com");
User bob = new User("2", "Bob", "0987654321", "bob@example.com");
// Create services
MessageService messageService = new MessageService();
NotificationService smsNotificationService = new SMSNotificationService();
NotificationService emailNotificationService = new EmailNotificationService();
// Create communication managers
CommunicationManager smsCommunicationManager = new CommunicationManager(messageService, smsNotificationService);
CommunicationManager emailCommunicationManager = new CommunicationManager(messageService, emailNotificationService);
// Simulate sending messages
smsCommunicationManager.sendMessage(alice, bob, "Hello Bob! This is a test SMS.", "SMS");
emailCommunicationManager.sendMessage(alice, bob, "Hello Bob! This is a test Email.", "EMAIL");
}
}
Summary of SOLID Principles Applied
-
Single Responsibility Principle (SRP):
User
,Message
,SMSMessage
,EmailMessage
, andMessageService
classes each have a single responsibility.CommunicationManager
is responsible for handling message sending and notifications.
-
Open/Closed Principle (OCP):
- The
Message
class can be extended to support new message types (e.g., push notifications) without modifying existing code. - The
NotificationService
interface allows for new notification methods to be added without changing existing services.
- The
-
Liskov Substitution Principle (LSP):
- Any implementation of the
Message
class can be used interchangeably in theMessageService
without affecting its behavior.
- Any implementation of the
-
Interface Segregation Principle (ISP):
- The
NotificationService
interface is focused, ensuring that clients only implement the methods they need.
- The
-
Dependency Inversion Principle (DIP):
CommunicationManager
depends on theMessageService
andNotificationService
abstractions, not on concrete implementations. This decoupling allows for easier testing and swapping of services.
Conclusion
This complete example illustrates the application of all five SOLID principles in a communication platform similar to Twilio. By following these principles, the system is modular, maintainable, and extensible. Each component is responsible for a specific function, and the design allows for easy adaptation as new requirements emerge, such as adding new message types or notification methods.
Let’s create a complete example of a Chat-Based System that demonstrates the SOLID principles. In this example, we’ll cover the components of a chat application, including user management, message handling, and notification services.
Example: Chat-Based System
Step 1: Define the Domain Models
We’ll start with the core models: User
, Message
, and ChatRoom
.
// User class
class User {
private String username;
public User(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
// Message class
class Message {
private User sender;
private String content;
public Message(User sender, String content) {
this.sender = sender;
this.content = content;
}
public User getSender() {
return sender;
}
public String getContent() {
return content;
}
}
Step 2: ChatRoom Class
The ChatRoom
class is responsible for managing users and messages. It follows the Single Responsibility Principle (SRP).
import java.util.ArrayList;
import java.util.List;
class ChatRoom {
private List<User> users;
private List<Message> messages;
public ChatRoom() {
this.users = new ArrayList<>();
this.messages = new ArrayList<>();
}
public void addUser(User user) {
users.add(user);
}
public void removeUser(User user) {
users.remove(user);
}
public void sendMessage(Message message) {
messages.add(message);
// Notify users about the new message
// This can be a separate notification service
}
public List<Message> getMessages() {
return messages;
}
}
Step 3: NotificationService Interface and Implementations
To adhere to the Open/Closed Principle (OCP), we will create an interface for notifications.
interface NotificationService {
void notifyUser(User user, Message message);
}
class EmailNotificationService implements NotificationService {
@Override
public void notifyUser(User user, Message message) {
System.out.println("Sending email to " + user.getUsername() + ": " + message.getContent());
}
}
class SMSNotificationService implements NotificationService {
@Override
public void notifyUser(User user, Message message) {
System.out.println("Sending SMS to " + user.getUsername() + ": " + message.getContent());
}
}
Step 4: MessageHandler Class
The MessageHandler
will handle sending messages and notifying users. It encapsulates the logic related to message processing.
class MessageHandler {
private ChatRoom chatRoom;
private NotificationService notificationService;
public MessageHandler(ChatRoom chatRoom, NotificationService notificationService) {
this.chatRoom = chatRoom;
this.notificationService = notificationService;
}
public void handleMessage(User user, String content) {
Message message = new Message(user, content);
chatRoom.sendMessage(message);
// Notify all users about the new message
for (User recipient : chatRoom.getUsers()) {
if (!recipient.equals(user)) { // Don't notify the sender
notificationService.notifyUser(recipient, message);
}
}
}
}
Step 5: Main Application
Now, let’s put everything together in a main application to demonstrate the functionality.
public class ChatApplication {
public static void main(String[] args) {
// Create chat room
ChatRoom chatRoom = new ChatRoom();
// Create users
User alice = new User("Alice");
User bob = new User("Bob");
// Add users to chat room
chatRoom.addUser(alice);
chatRoom.addUser(bob);
// Choose a notification service
NotificationService notificationService = new EmailNotificationService(); // or new SMSNotificationService();
// Create message handler
MessageHandler messageHandler = new MessageHandler(chatRoom, notificationService);
// Simulate sending messages
messageHandler.handleMessage(alice, "Hello, Bob!");
messageHandler.handleMessage(bob, "Hi, Alice! How are you?");
}
}
Summary of SOLID Principles Applied
-
Single Responsibility Principle (SRP):
ChatRoom
manages users and messages.MessageHandler
is responsible for processing messages and notifying users.- Each class has a single responsibility.
-
Open/Closed Principle (OCP):
- The
NotificationService
interface allows for new notification methods to be added (like push notifications) without modifying existing code.
- The
-
Liskov Substitution Principle (LSP):
- Any implementation of the
NotificationService
can be used interchangeably without affecting the behavior of theMessageHandler
. For example, you can switch fromEmailNotificationService
toSMSNotificationService
seamlessly.
- Any implementation of the
-
Interface Segregation Principle (ISP):
- The
NotificationService
interface is focused, ensuring that clients only implement the methods they need. For instance, if we later create aPushNotificationService
, it will only implement the notification method without being burdened with unrelated methods.
- The
-
Dependency Inversion Principle (DIP):
MessageHandler
depends on theNotificationService
abstraction, not on concrete implementations. This decoupling allows for easier testing and swapping of notification services.
Conclusion
This complete example illustrates the application of all five SOLID principles in a chat-based system. By following these principles, the system is modular, maintainable, and extensible. Each component is responsible for a specific function, and the design allows for easy adaptation as new requirements emerge, such as adding different types of notifications or additional features to the chat functionality.