Converting system requirements into an object-oriented design (OOD) involves translating functional descriptions into a structured model of interacting objects. Here’s a step-by-step thought process that outlines how to approach this task:
Step 1: Identifying Entities and Attributes
Thought Process:
- Requirement Analysis: Begin by thoroughly reviewing the system requirements document. Look for nouns and noun phrases, as they often suggest potential objects or entities within the system.
- Domain Knowledge: Use your understanding of the domain to recognize common entities that are typically involved in similar systems. This might include both tangible items (e.g., 'Car', 'User') and intangible concepts (e.g., 'Order', 'Invoice').
- Abstraction: Abstract these nouns to determine whether they represent distinct objects or if they should be generalized. For instance, 'Admin User' and 'Regular User' might both be generalized into a 'User' entity.
Key Considerations:
- Relevance: Ensure the identified entities are relevant to the system’s goals.
- Granularity: Strike a balance between too granular (too many small objects) and too broad (overly generalized objects).
- Consistency: Maintain a consistent level of abstraction across all entities.
Step 2: Defining Attributes and Behaviors
Thought Process:
- Attribute Identification: Identify properties of each entity by looking for adjectives or descriptive phrases in the requirements. For example, a 'User' might have attributes like 'username', 'password', and 'email'.
- Behavior Identification: Determine what actions or operations each entity can perform, often indicated by verbs in the requirements. For instance, a 'User' might 'log in', 'register', or 'update profile'.
- Encapsulation: Group related attributes and behaviors together within the same entity. This ensures that an object encapsulates all the necessary data and functionality related to it.
Key Considerations:
- Necessity: Ensure each attribute and behavior is necessary for fulfilling the requirements.
- Encapsulation: Keep attributes private and expose behaviors (methods) to interact with those attributes.
- Clarity: Behaviors should be clearly defined and aligned with the entity’s role.
Step 3: Establishing Relationships
Thought Process:
- Functional Analysis: Examine the system functionalities to identify how entities interact. This helps in establishing relationships such as associations, dependencies, and collaborations.
-
Relationship Types: Determine the nature of these relationships:
- Association: A general connection between two entities (e.g., 'User' and 'Order').
- Aggregation/Composition: A whole-part relationship where one entity is composed of one or more other entities (e.g., 'Library' contains 'Books').
- Inheritance: A hierarchical relationship where one entity is a specialized form of another (e.g., 'AdminUser' inherits from 'User').
Key Considerations:
- Cardinality: Define the multiplicity of relationships (e.g., one-to-one, one-to-many).
- Directionality: Determine if the relationship is bidirectional or unidirectional.
- Dependence: Ensure that dependent objects are appropriately managed to avoid orphaned instances or memory leaks.
Step 4: Refining the Object Model
Thought Process:
- Validation: Cross-check the initial object model with the system requirements to ensure all functionalities are covered and the model is coherent.
- Feedback: Gather feedback from stakeholders, including developers, domain experts, and end-users to refine the model.
- Iteration: Iterate over the model to incorporate feedback and address any inconsistencies or gaps.
Key Considerations:
- Redundancy: Remove redundant entities or attributes.
- Consistency: Ensure consistency across the model, particularly in naming conventions and relationships.
- Complexity: Avoid unnecessary complexity; keep the model as simple as possible while meeting all requirements.
Potential Issues and Solutions:
- Ambiguities: Resolve ambiguities in requirements through discussions with stakeholders.
- Over-generalization: Avoid making entities too broad; ensure they are specific enough to handle distinct responsibilities.
- Under-specification: Ensure entities and relationships are sufficiently detailed to support all required functionalities.
- Scalability: Consider future changes and scalability in the design to avoid major refactoring.
By following these steps and considerations, you can systematically convert system requirements into an object-oriented design that accurately represents the problem domain and supports the desired system functionalities.
Step-by-Step Journey of an OOD Detective: Designing a Parking Lot System
Step 1: Identifying Entities and Attributes
Thought Process:
To uncover real-world entities, I closely examine the requirements. I look for nouns and key concepts, and ask myself questions such as:
- Who are the main actors?
- What objects are being manipulated or used?
- What are the tangible and intangible items mentioned?
Entities Identification:
- ParkingLot: Manages the overall structure and operations.
- ParkingFloor: Represents each level of the parking lot.
- ParkingSpot: Individual parking spaces within floors.
- Vehicle: Represents cars, motorcycles, trucks, etc.
- Customer: Users who park their vehicles.
- Ticket: Issued to customers upon entry.
- Payment: Handles payment transactions.
- ParkingAttendant: Assists customers with payment and other services.
- EntrancePanel: Issues tickets and manages entry.
- ExitPanel: Manages payment and exit processes.
- DisplayBoard: Shows available spots and messages.
- Admin: Manages the system configuration and attendants.
Attributes:
For each entity, I determine relevant attributes by considering the properties needed to fulfill the requirements.
-
ParkingLot:
name
,location
,totalCapacity
,floors
,entrancePanels
,exitPanels
,displayBoards
-
ParkingFloor:
floorNumber
,spots
,displayBoard
-
ParkingSpot:
spotId
,spotType
,isOccupied
,vehicle
-
Vehicle:
vehicleId
,vehicleType
,licensePlate
-
Customer:
customerId
,name
,contactInfo
-
Ticket:
ticketId
,issueTime
,paidTime
,payment
,vehicle
-
Payment:
paymentId
,amount
,method
,status
-
ParkingAttendant:
attendantId
,name
,shift
-
EntrancePanel:
panelId
,location
-
ExitPanel:
panelId
,location
-
DisplayBoard:
boardId
,location
,messages
-
Admin:
adminId
,name
,permissions
Step 2: Defining Attributes and Behaviors
Thought Process:
To define behaviors, I look at the actions described in the requirements and consider what each entity must do to support these actions. I ask:
- What operations does each entity need to perform?
- What interactions are described in the use cases?
Behaviors:
-
ParkingLot:
-
addFloor()
,removeFloor()
,addEntrancePanel()
,addExitPanel()
,displayAvailability()
-
-
ParkingFloor:
-
addSpot()
,removeSpot()
,findAvailableSpot()
,displayAvailableSpots()
-
-
ParkingSpot:
-
assignVehicle()
,removeVehicle()
-
-
Vehicle:
-
enterLot()
,exitLot()
-
-
Customer:
-
takeTicket()
,payTicket()
-
-
Ticket:
-
calculateFee()
,markPaid()
-
-
Payment:
-
processPayment()
,generateReceipt()
-
-
ParkingAttendant:
-
assistCustomer()
,processCashPayment()
-
-
EntrancePanel:
-
issueTicket()
,displayMessage()
-
-
ExitPanel:
-
scanTicket()
,processPayment()
-
-
DisplayBoard:
-
updateMessage()
,showAvailability()
-
-
Admin:
-
configureSystem()
,manageAttendants()
-
Step 3: Establishing Relationships
Thought Process:
I consider how entities need to interact to fulfill the system's functionalities. I translate system functionalities into interactions between entities, asking:
- How do entities collaborate to complete tasks?
- What kinds of relationships (association, aggregation, composition, inheritance) are appropriate?
Relationships:
-
ParkingLot and ParkingFloor: Composition (
ParkingLot
contains multipleParkingFloor
s). -
ParkingFloor and ParkingSpot: Composition (
ParkingFloor
contains multipleParkingSpot
s). -
ParkingSpot and Vehicle: Association (a
ParkingSpot
can be occupied by aVehicle
). -
Customer and Ticket: Association (a
Customer
has oneTicket
). -
Ticket and Payment: Association (a
Ticket
can have onePayment
). -
ParkingLot and EntrancePanel/ExitPanel: Aggregation (a
ParkingLot
has multipleEntrancePanel
s andExitPanel
s). -
ParkingFloor and DisplayBoard: Aggregation (each
ParkingFloor
has oneDisplayBoard
). -
ParkingLot and Admin: Association (an
Admin
configures theParkingLot
).
Inheritance:
-
Vehicle: Base class with derived classes
Car
,Truck
,Motorcycle
, etc. -
Payment: Base class with derived classes
CashPayment
andCardPayment
.
Step 4: Refining the Object Model
Thought Process:
I validate the initial model against the requirements, seeking feedback, and iterating to refine the model. I look for:
- Consistency with requirements.
- Redundancy or missing elements.
- Complexity and maintainability.
Validation and Refinement:
- Review Requirements: Ensure all use cases are covered.
- Feedback: Get input from stakeholders.
- Iteration: Adjust the model based on feedback.
Potential Issues and Solutions:
- Ambiguities: Clarify requirements with stakeholders.
- Over-generalization: Ensure entities are specific enough for their roles.
- Under-specification: Add necessary details to attributes and behaviors.
- Scalability: Design the system to handle future expansion (e.g., adding more floors or spot types).
Final Object Model:
The final model includes well-defined entities, attributes, behaviors, and relationships, ensuring a robust and scalable design for the parking lot management system. The model is validated against the requirements, ensuring all functionalities are supported efficiently.
Below is an implementation of the Parking Lot system. This implementation covers the key classes and their relationships based on the design. I'll include essential methods and properties to demonstrate how the system can be constructed and used.
Entities and Relationships
1. ParkingLot
using System;
using System.Collections.Generic;
public class ParkingLot
{
public string Name { get; set; }
public string Location { get; set; }
public int TotalCapacity { get; set; }
public List<ParkingFloor> Floors { get; set; }
public List<EntrancePanel> EntrancePanels { get; set; }
public List<ExitPanel> ExitPanels { get; set; }
public List<DisplayBoard> DisplayBoards { get; set; }
public ParkingLot(string name, string location, int totalCapacity)
{
Name = name;
Location = location;
TotalCapacity = totalCapacity;
Floors = new List<ParkingFloor>();
EntrancePanels = new List<EntrancePanel>();
ExitPanels = new List<ExitPanel>();
DisplayBoards = new List<DisplayBoard>();
}
public void AddFloor(ParkingFloor floor)
{
Floors.Add(floor);
}
public void RemoveFloor(ParkingFloor floor)
{
Floors.Remove(floor);
}
public void AddEntrancePanel(EntrancePanel panel)
{
EntrancePanels.Add(panel);
}
public void AddExitPanel(ExitPanel panel)
{
ExitPanels.Add(panel);
}
public void DisplayAvailability()
{
foreach (var board in DisplayBoards)
{
board.ShowAvailability();
}
}
}
2. ParkingFloor
using System.Collections.Generic;
public class ParkingFloor
{
public int FloorNumber { get; set; }
public List<ParkingSpot> Spots { get; set; }
public DisplayBoard DisplayBoard { get; set; }
public ParkingFloor(int floorNumber)
{
FloorNumber = floorNumber;
Spots = new List<ParkingSpot>();
}
public void AddSpot(ParkingSpot spot)
{
Spots.Add(spot);
}
public void RemoveSpot(ParkingSpot spot)
{
Spots.Remove(spot);
}
public ParkingSpot FindAvailableSpot(ParkingSpotType spotType)
{
foreach (var spot in Spots)
{
if (spot.SpotType == spotType && !spot.IsOccupied)
{
return spot;
}
}
return null;
}
public void DisplayAvailableSpots()
{
DisplayBoard.ShowAvailability();
}
}
3. ParkingSpot
public class ParkingSpot
{
public string SpotId { get; set; }
public ParkingSpotType SpotType { get; set; }
public bool IsOccupied { get; set; }
public Vehicle Vehicle { get; set; }
public ParkingSpot(string spotId, ParkingSpotType spotType)
{
SpotId = spotId;
SpotType = spotType;
IsOccupied = false;
Vehicle = null;
}
public void AssignVehicle(Vehicle vehicle)
{
Vehicle = vehicle;
IsOccupied = true;
}
public void RemoveVehicle()
{
Vehicle = null;
IsOccupied = false;
}
}
public enum ParkingSpotType
{
Compact,
Large,
Handicapped,
Motorcycle,
Electric
}
4. Vehicle
public abstract class Vehicle
{
public string VehicleId { get; set; }
public string LicensePlate { get; set; }
protected Vehicle(string vehicleId, string licensePlate)
{
VehicleId = vehicleId;
LicensePlate = licensePlate;
}
}
public class Car : Vehicle
{
public Car(string vehicleId, string licensePlate) : base(vehicleId, licensePlate) { }
}
public class Truck : Vehicle
{
public Truck(string vehicleId, string licensePlate) : base(vehicleId, licensePlate) { }
}
public class Motorcycle : Vehicle
{
public Motorcycle(string vehicleId, string licensePlate) : base(vehicleId, licensePlate) { }
}
5. Customer
public class Customer
{
public string CustomerId { get; set; }
public string Name { get; set; }
public string ContactInfo { get; set; }
public Customer(string customerId, string name, string contactInfo)
{
CustomerId = customerId;
Name = name;
ContactInfo = contactInfo;
}
public Ticket TakeTicket(ParkingLot lot, Vehicle vehicle)
{
var ticket = new Ticket(Guid.NewGuid().ToString(), DateTime.Now, null, null, vehicle);
// Assuming entrance logic here, simplified
return ticket;
}
public void PayTicket(Ticket ticket, Payment payment)
{
ticket.PayTicket(payment);
}
}
6. Ticket
using System;
public class Ticket
{
public string TicketId { get; set; }
public DateTime IssueTime { get; set; }
public DateTime? PaidTime { get; set; }
public Payment Payment { get; set; }
public Vehicle Vehicle { get; set; }
public Ticket(string ticketId, DateTime issueTime, DateTime? paidTime, Payment payment, Vehicle vehicle)
{
TicketId = ticketId;
IssueTime = issueTime;
PaidTime = paidTime;
Payment = payment;
Vehicle = vehicle;
}
public double CalculateFee(DateTime exitTime)
{
var totalHours = (exitTime - IssueTime).TotalHours;
// Fee calculation logic goes here
return totalHours * 3; // Simplified fee calculation
}
public void PayTicket(Payment payment)
{
Payment = payment;
PaidTime = DateTime.Now;
}
public bool IsPaid()
{
return PaidTime.HasValue;
}
}
7. Payment
public abstract class Payment
{
public string PaymentId { get; set; }
public double Amount { get; set; }
public PaymentStatus Status { get; set; }
protected Payment(string paymentId, double amount)
{
PaymentId = paymentId;
Amount = amount;
Status = PaymentStatus.Pending;
}
public abstract void ProcessPayment();
public void MarkAsCompleted()
{
Status = PaymentStatus.Completed;
}
}
public class CashPayment : Payment
{
public CashPayment(string paymentId, double amount) : base(paymentId, amount) { }
public override void ProcessPayment()
{
// Cash payment processing logic
MarkAsCompleted();
}
}
public class CardPayment : Payment
{
public string CardNumber { get; set; }
public CardPayment(string paymentId, double amount, string cardNumber) : base(paymentId, amount)
{
CardNumber = cardNumber;
}
public override void ProcessPayment()
{
// Card payment processing logic
MarkAsCompleted();
}
}
public enum PaymentStatus
{
Pending,
Completed,
Failed
}
8. ParkingAttendant
public class ParkingAttendant
{
public string AttendantId { get; set; }
public string Name { get; set; }
public string Shift { get; set; }
public ParkingAttendant(string attendantId, string name, string shift)
{
AttendantId = attendantId;
Name = name;
Shift = shift;
}
public void AssistCustomer(Customer customer)
{
// Assist customer logic
}
public void ProcessCashPayment(Ticket ticket, double amount)
{
var payment = new CashPayment(Guid.NewGuid().ToString(), amount);
payment.ProcessPayment();
ticket.PayTicket(payment);
}
}
9. EntrancePanel and ExitPanel
public class EntrancePanel
{
public string PanelId { get; set; }
public string Location { get; set; }
public EntrancePanel(string panelId, string location)
{
PanelId = panelId;
Location = location;
}
public Ticket IssueTicket(Vehicle vehicle)
{
return new Ticket(Guid.NewGuid().ToString(), DateTime.Now, null, null, vehicle);
}
public void DisplayMessage(string message)
{
Console.WriteLine(message);
}
}
public class ExitPanel
{
public string PanelId { get; set; }
public string Location { get; set; }
public ExitPanel(string panelId, string location)
{
PanelId = panelId;
Location = location;
}
public void ScanTicket(Ticket ticket)
{
if (!ticket.IsPaid())
{
double fee = ticket.CalculateFee(DateTime.Now);
Console.WriteLine($"Payment required: ${fee}");
}
else
{
Console.WriteLine("Ticket already paid.");
}
}
public void ProcessPayment(Ticket ticket, Payment payment)
{
payment.ProcessPayment();
ticket.PayTicket(payment);
}
}
10. DisplayBoard
using System.Collections.Generic;
public class DisplayBoard
{
public string BoardId { get; set; }
public string Location { get; set; }
public List<string> Messages { get; set; }
public DisplayBoard(string boardId, string location)
{
BoardId = boardId;
Location =
location;
Messages = new List<string>();
}
public void UpdateMessage(string message)
{
Messages.Add(message);
ShowAvailability();
}
public void ShowAvailability()
{
foreach (var message in Messages)
{
Console.WriteLine(message);
}
}
}
11. Admin
public class Admin
{
public string AdminId { get; set; }
public string Name { get; set; }
public string Permissions { get; set; }
public Admin(string adminId, string name, string permissions)
{
AdminId = adminId;
Name = name;
Permissions = permissions;
}
public void ConfigureSystem(ParkingLot lot)
{
// Configuration logic
}
public void ManageAttendants(List<ParkingAttendant> attendants)
{
// Management logic
}
}
Putting It All Together
Here’s a small demonstration of how these classes can be used together:
class Program
{
static void Main(string[] args)
{
// Create a parking lot
ParkingLot lot = new ParkingLot("Main Lot", "123 Main St", 500);
// Create and add floors
ParkingFloor floor1 = new ParkingFloor(1);
lot.AddFloor(floor1);
// Create and add parking spots
ParkingSpot spot1 = new ParkingSpot("1A", ParkingSpotType.Compact);
floor1.AddSpot(spot1);
// Create entrance and exit panels
EntrancePanel entrance = new EntrancePanel("Entrance1", "North Entrance");
ExitPanel exit = new ExitPanel("Exit1", "South Exit");
lot.AddEntrancePanel(entrance);
lot.AddExitPanel(exit);
// Create a customer and a vehicle
Customer customer = new Customer("C001", "John Doe", "555-1234");
Vehicle car = new Car("V001", "ABC123");
// Customer takes a ticket
Ticket ticket = entrance.IssueTicket(car);
// Customer pays the ticket at the exit
Payment payment = new CardPayment("P001", ticket.CalculateFee(DateTime.Now), "1234-5678-9012-3456");
exit.ProcessPayment(ticket, payment);
// Check the payment status
if (ticket.IsPaid())
{
Console.WriteLine("Ticket has been paid.");
}
}
}
This example demonstrates the creation of a parking lot, adding floors and spots, managing entrance and exit panels, and processing a customer's parking and payment. The design encapsulates the key requirements and interactions for a parking lot management system.
CAVEAT:
The idea of having actor classes like Customer
orchestrate the logic of other classes (like ShoppingCart
) is a concept that developers should approach with caution and consider the context of their application. Here's why:
Benefits in Courses and Books:
- Teaching Object-Oriented Principles: Courses and books often use the actor orchestration approach to illustrate core object-oriented principles like encapsulation, separation of concerns, and message passing. It can be a simplified way to introduce these concepts.
- Focus on Core Concepts: By placing logic within actor classes, these resources can focus on core OOD principles without introducing complexities like frameworks (ASP.NET Core Identity) or persistence mechanisms (databases).
Caveats for Real-World Applications:
-
Over-engineering for Simple Systems: For basic e-commerce applications, having a
Customer
class orchestrate shopping cart logic can be overkill. It might introduce unnecessary complexity. -
Potential for Duplication: As the application grows, authorization logic placed within the
Customer
class might need to be duplicated across controllers, leading to inconsistencies. -
Separation of Concerns in Web Applications: In web applications with frameworks like ASP.NET Core, it's often better to separate concerns using controllers for handling user interactions and authorization, and dedicated services for handling domain logic (like the
ShoppingCartService
).
When Actor Orchestration Might Be Appropriate:
- Complex Systems: In systems with intricate business rules and interactions between actors, modeling actors with orchestration logic can be more beneficial. It can help manage complex workflows and interactions.
- Standalone Applications: For desktop or standalone applications without a layered architecture (like ASP.NET Core MVC with separate controllers and services), placing some logic within actor classes might be reasonable.
What Junior Developers Should Keep in Mind:
- Understand the Trade-offs: The actor orchestration approach can be a valuable learning tool, but junior developers should understand its limitations and consider the complexity of the application when applying it in practice.
- Focus on Core Principles: Grasp the core object-oriented principles demonstrated through the actor orchestration approach. These principles are essential for building well-structured and maintainable applications.
- Learn Best Practices for Web Frameworks: As you learn about web development frameworks like ASP.NET Core, understand the recommended patterns for separation of concerns (controllers, services, repositories).
In Conclusion:
The idea of actor orchestration is a valuable concept to learn in OOD, but it's important to assess its suitability for your specific application. For web applications with ASP.NET Core, separating concerns using dedicated services is generally a better approach for maintainability and scalability. Keep in mind the trade-offs and learn best practices for the frameworks you'll be using in the real world.
Orchestrating vs Separated Concerns: Ordering Food with an App
Imagine you're building a mobile app for a food delivery service. Here's how the approach to handling user interaction with the order process can differ:
Scenario 1: Actor Orchestration (for Learning Purposes)
-
Customer
Class: This class would handle most of the logic. The user would interact with methods likeSearchRestaurants
,ViewMenu
, andPlaceOrder
. Inside these methods, theCustomer
class would interact with other classes likeRestaurant
andOrder
.
public class Customer {
public List<Restaurant> searchRestaurants(String cuisine) {
// Call Restaurant class to find restaurants based on cuisine
}
public void viewMenu(Restaurant restaurant) {
// Call Restaurant class to retrieve menu
}
public void placeOrder(Restaurant restaurant, List<MenuItem> items) {
Order order = new Order(this, restaurant, items);
// Call Order class to process and submit the order
}
}
Benefits (for learning):
-
Simpler Example: This approach can be easier to grasp initially as it keeps the logic centralized in the
Customer
class. - Focus on OOD Principles: It highlights concepts like encapsulation (data hidden within classes) and message passing (methods calling each other).
Drawbacks (for real-world app):
-
Over-engineering for Simple App: In a basic app, this can lead to a cluttered
Customer
class. -
Duplication Risk: As the app grows, authorization logic might need to be placed in the
Customer
class and potentially duplicated elsewhere.
Scenario 2: Separated Concerns (Recommended for Real-World App)
-
Dedicated Service Classes: Separate classes like
OrderService
andRestaurantService
handle specific functionalities. TheCustomerActivityController
in your mobile app would handle user interactions and call these services.
public class CustomerActivityController {
@Autowired
private OrderService orderService;
@Autowired
private RestaurantService restaurantService;
public List<Restaurant> searchRestaurants(String cuisine) {
return restaurantService.findRestaurants(cuisine);
}
public void viewMenu(int restaurantId) {
Restaurant restaurant = restaurantService.getRestaurant(restaurantId);
// Display menu retrieved from restaurant object
}
public void placeOrder(int restaurantId, List<MenuItem> items) {
orderService.createOrder(restaurantId, items, getLoggedInUserId());
}
}
Benefits:
- Separation of Concerns: Clearer division of responsibilities (controllers for user interaction, services for domain logic).
- Maintainability: Easier to modify or extend functionalities within dedicated services.
- Scalability: The code can be adapted for more complex features or integrations with other services.
Conclusion:
While the actor orchestration approach can be a helpful learning tool, separating concerns with dedicated services is generally a better practice for building maintainable and scalable web applications.
This example highlights the trade-offs: the first approach is simpler to understand initially, but the second approach leads to a cleaner and more scalable application structure in the long run.
Top comments (0)