Determine the different ways the system will be used. This includes main functions the system needs to perform and who will use it.
Based on the requirements and use cases, identify the main objects of the system...
1.Item
Product in the warehouse
Attributes : id, name, type(perishable/non-perishable), price, size, weight, quantity
2.Order
Customer order containing multiple items
Attributes: id, orderDate, status(cancelled, delivered, returned, undelivered), List
3.Shipment
Represent incoming or outgoing shipment
Attributes: id, type(incoming/outgoing), List
4.StorageLocation
Physical location in the warehouse where the item is stored
Attributes: id, capacity, locationType(shelf, bin etc)
5.User
Individual user uses the system
Attributes: id, username, password(hash), email, phn, role(admin, manager, worker)
6.OrderItem
Represents the item included in the order
Attributes: item, quantity, price
Determine how these objects will interact with each other to fulfill the use cases...
Design inheritance trees where applicable to promote code reuse and polymorphism. This step involves identifying common attributes and behaviors that can be abstracted into parent classes...
Consider using design patterns (e.g., Factory, Singleton, Observer, Strategy) that fit the problem...
Factory Pattern:
We can use factory design pattern for Shipment(Incoming/Outgoing) shipment. Instead of creating it via constructor we can have ShipmentFactory which generates appropriate shipment.
public class ShipmentFactory{
public static Shipment createShipment(String type){
if(type == 'incoming') {
return new IncomingShipment();
}else if(type == 'outgoing') {
return new OutgoingShipment();
}
throw new IllegalArguments("Unknown Shipment");
}
Singleton Pattern
We can use this pattern for database creation. It will ensure only one instance of DatabaseConnection got created
public class DatabaseConnection {
private static DatabaseConnection instance;
private DatabaseConnection (){
/* private constructor */
}
public static DatabaseConnection getInstance(){
if ( instance == null){
instance = new DatabaseConnection();
}
return instance;
}
public void connection(){
/* connection logic */
}
}
Observer Pattern:
The observer pattern can be useful when the stock of an item is added/finished then we need to notify the Stock Manager, Administrator
public interface StockObserver {
void update(Item item);
}
public class StockNotifier {
private List<StockObserver> observers = new ArrayList<>();
public void addObserver(StockObserver ob){
observers.add(ob);
}
public void notifyObservers(Item item){
for(StockObserver obs : observers) {
obs.update(item);
}
}
}
Strategy Design:
This pattern will be useful for optimizing the storage location of an item. Different strategies like(size based, turnover based placement) can be done based on warehouse conditions.
public interface StorageStrategy{
void optimize(Item item);
}
public void SizeStrategy implements StorageStrategy{
public void optimize(Item item){
// Logic to optimizing storage based on size
}
}
public void TurnoverStrategy implements StorageStrategy {
public void optimize(Item item){
// Logic to optimizing turnover
}
}
public class StorageOptimizer{
public StorageStrategy storageStrategy;
public StorageOptimizer(StorageStrategy ss){
this.storageStrategy = ss;
}
public void optimize(Item item){
this.storageStrategy.optimize(item);
}
}
Attributes: For each class, define the attributes (data) it will hold...
Methods: Define the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation.
public class Item{
String id;
String name;
ItemType type;
Double price;
Size size;
Double weight;
int quantity;
StorageLocation loc;
int reorderLevel;
public Item(String name,ItemType type,Double price,Size size,Double weight,int quantity,StorageLocation loc) {
// constructor
}
// Methods for all getter setter
public boolean isReorderNeeded(){
this.quantity < this.reorderLevel
}
public void updateStock(int quantity){
this.quantity = quantity;
}
}
public class Order{
String orderNumber;
String customer;
OrderStatus status;
List<OrderItem> orderItems;
public Order(String orderNumber, String customer) {
this.orderNumber = orderNumber;
this.customer = customer;
this.orderItems = new ArrayList<>();
}
// Getters and Setters
public String getOrderNumber() { return orderNumber; }
public String getCustomer() { return customer; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public void addOrderItems(OrderItem item){
this.orderItems.add(item);
}
public List<OrderItems> getOrderItem(){
return this.orderItems;
}
public double calculateCost(){
double totalCost;
for(OrderItem item: orderItems){
int quantity = item.getQuantity();
double price = item.getPrice();
totalCost += quantity*price;
}
return totalCost;
}
}
public class OrderItem{
Item item;
double price;
int quantity;
public OrderItem(Item item, int quantity, double price) {
this.item = item;
this.quantity = quantity;
this.price = price;
}
// Getters and Setters
public Item getItem() { return item; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public double getPrice() { return price; }
}
public enum OrderStatus{
RETURNED,
DELIVERED,
CANCELLED,
UNDELIVERED
}
public enum ItemType{
PERISHABLE,
NON-PERISHABLE
}
public enum Size{
SMALL,
MEDIUM,
LARGE
}
public class OrderController{
List<Order> orders;
public void createNewOrder(Order order){
this.orders.add(order);
}
public void removeOrder(Order order){
this.orders.remove(order);
}
public Order getOrderById(int orderId){
}
}
public class Shipment{
protected String shipmentId;
protected String carrier;
protected List<Item> items;
protected String status; // in-transit, delivered, etc.
public Shipment(String shipmentId, String carrier) {
this.shipmentId = shipmentId;
this.carrier = carrier;
this.items = new ArrayList<>();
}
// Abstract methods for specific shipment types
public abstract void processShipment();
}
public class IncomingShipment extends Shipment{
public void processShipment(){}
}
public class OutgoingShipment extends Shipment{
public void processShipment(){}
}
public class ShipmentController{
List<Shipment> shipments;
public void addShipment(Shipment shipment){}
public Shipment removeShipment(Shipment shipment){}
public Shipment updateShipment(Shipment shipment){}
}
Check and explain whether your design adheres to solid principles (Ask interviewer what SOLID principle is if you can not recall it.)...
Single Responsibility Principle (SRP): Each class has a specific responsibility. For example, Item manages its own stock levels, Order manages a list of OrderItems, and Shipment deals with shipment-specific details.
Open/Closed Principle (OCP): The use of design patterns such as Strategy and Factory allows the system to be extended with new functionality (e.g., new shipment types, storage strategies) without modifying existing code.
Liskov Substitution Principle (LSP): Derived classes (e.g., IncomingShipment, OutgoingShipment) can be used in place of their parent class Shipment without altering the correctness of the program.
Interface Segregation Principle (ISP): Interfaces such as StorageStrategy ensure that classes are not forced to implement methods they don’t use.
Dependency Inversion Principle (DIP): High-level modules like the StorageOptimizer class depend on abstractions (StorageStrategy), not concrete implementations.
Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...
Caching:
Event-Driven Architecture:
Order Service can publish an event when an order is placed, and the Shipping Service can subscribe to that event to trigger the shipment process.Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...
classDiagram
class Item {
-String sku
-String name
-int quantity
-StorageLocation location
-double size
-double weight
+updateStock(quantity)
}
class StorageLocation {
-String locationID
-double capacity
-double currentOccupancy
}
class Order {
-String orderNumber
-String customer
-String status
+addOrderItem(OrderItem item)
+calculateTotalCost()
}
class OrderItem {
-Item item
-int quantity
-double price
}
class Shipment {
<<abstract>>
-String shipmentId
-String carrier
-List~Item~ items
+addItem(Item item)
+processShipment()
}
class IncomingShipment {
+processShipment()
}
class OutgoingShipment {
+processShipment()
}
class User {
-String userID
-String username
-String password
+login()
}
Item --> StorageLocation
Order --> OrderItem
OrderItem --> Item
Shipment --> Item
IncomingShipment --|> Shipment
OutgoingShipment --|> Shipment
User <|-- Admin
User <|-- Worker
User <|-- Manager
Critically examine your design for any flaws or areas for future improvement...
Predictive Stock Replenishment: