- Lab
-
Libraries: If you want this lab, consider one of these libraries.
Guided: C# 14 Object-oriented Modeling
Object-oriented design is a foundational skill for building maintainable and scalable software. In this Guided Code Lab, you will model a real-world vehicle fleet management system using modern C# 14 features and object-oriented principles. You will practice designing base classes, extending behavior through inheritance, enforcing contracts with interfaces, and managing objects using polymorphism. By the end of this lab, you will be confident in translating domain requirements into clean, extensible C# models.
Lab Info
Table of Contents
-
Challenge
Introduction
In this lab, you will model a real-world system using object-oriented principles in C#.
As a developer, your responsibility is not just to write code that works. Your responsibility is to design models that:
- Represent the domain correctly
- Support future extensibility
- Encourage clean structure
- Demonstrate proper use of object-oriented principles
This lab focuses on making thoughtful modeling decisions.
Lab Scenario
You are working for a logistics company that manages a fleet of vehicles.
The company owns different types of vehicles and needs a clean, scalable way to manage them in code. Your task is to design a Vehicle Fleet Management System that models this domain accurately.
The company owns:
- Cars
- Trucks
Each vehicle has common properties such as:
- Registration number
- Manufacturer
- Year
However, there are important differences:
- Cars and trucks behave differently when they start
- Trucks require specialized maintenance
- The company also has office equipment that requires maintenance but is not a vehicle
Your goal is to design a clean object model that supports:
- Shared vehicle behavior
- Specialized vehicle behavior
- Maintenance functionality across different asset types
You will focus entirely on object-oriented modeling.
Learning Objectives
Now that you understand the lab scenario, you will begin translating the requirements into an object-oriented design.
In this lab, you will go through the below points:
- Model shared behavior using a base class
- Specialize behavior using inheritance
- Define a contract using an interface
- Implement polymorphism using both inheritance and interfaces
- Understand when to use inheritance and when to use interfaces
By the end of this lab, you will clearly understand the difference between modeling identity using inheritance and modeling capability using interfaces. >Each step contains tasks clearly marked with comments like
// Task 2.1info> If you get stuck on a task, you can view the solution in the
solutionfolder in your Filetree, or click the Task Solution link at the bottom of each task after you've attempted it. -
Challenge
Examine the Project Structure and Identify Shared Behavior
Before modifying any code, you will first understand the structure of the project and analyze the domain from a modeling perspective.
In professional development, you rarely start from scratch. Most of the time, you inherit an existing codebase. Your first responsibility is to understand what already exists before making changes.
Review the Project Structure
Observe the folder structure:
FleetManagement/ │ ├── Program.cs │ ├── Models/ │ ├── Vehicle.cs │ ├── Car.cs │ ├── Truck.cs │ └── OfficeAsset.cs │ └── Interfaces/ └── IMaintainable.cs
Program.cs
This is the entry point of the application.
It already contains basic code that:
- Creates instances of
CarandTruck - Stores them in collections
- Calls methods such as
Start()andDisplayInfo()
Models Folder
The
Modelsfolder contains the core domain entities:VehicleCarTruckOfficeAsset
These classes represent real-world business concepts, keeping domain models separate from application logic.
Interfaces Folder
The
Interfacesfolder contains contracts that classes may implement.You will modify and apply
IMaintainablelater in the lab.For now, simply note that the system has been structured to separate:
- Identity (Models)
- Capability contracts (Interfaces)
Run the Application
Before making any changes, run the application to observe its current behavior.
You can run the application in either of the following ways:
-
Click the Run button located at the bottom right of the Terminal.
-
Navigate to the
/FleetManagementdirectory, then rundotnet runin the terminal.
info>You will use either of these options throughout the lab whenever you need to run the program.
Output:
Fleet Management System Vehicle is starting... Reg: CAR123, Manufacturer: Toyota, Year: 2022 Vehicle is starting... Reg: TRK789, Manufacturer: Volvo, Year: 2020
Analyze the Base Class
Open
Models/Vehicle.cs. Review the class. Notice that it contains:- Common properties:
RegistrationNumber,Manufacturer,Year - A constructor
- A
Start()method - A
DisplayInfo()method
Now think about the domain.
The company owns cars and trucks. What do they share?
- Registration number
- Manufacturer
- Year
- The ability to start
- The ability to display information
These shared characteristics justify the
Vehiclebase class.info> Inheritance models an "is-a" relationship.
A
Caris aVehicle.
ATruckis aVehicle.
Identify What Needs Improvement
Look at the
Start()method.Does it allow specialization?
Currently, every vehicle starts the same way. But in reality:
- A car might start smoothly
- A truck might start loudly after safety checks
- An electric vehicle might start silently
Your design should support this flexibility.
In the next step, you will enable specialization in the base class.
- Creates instances of
-
Challenge
Enable Specialization in Base Class
In the previous step, you reviewed the project structure and analyzed the
Vehicleclass.Although all vehicles share the ability to start, the way they start may differ. Your current design does not support this flexibility.
The Design Limitation
Open
Models/Vehicle.csand locate theStart()method.public void Start() { Console.WriteLine("Vehicle is starting..."); }Next, you will modify the base class to properly support specialization.
Understanding the
virtualKeywordIn C#, if you want a derived class to override a method defined in a base class, that method must be marked as
virtual.public virtual void Start()The
virtualkeyword tells the runtime:info> "This method may be overridden in a derived class." ---
Run the Application
You will not notice any difference yet.
That is expected.
You have enabled specialization, but you have not implemented it yet.
In the next step, you will override this behavior in
CarandTruckand observe polymorphism in action. -
Challenge
Apply Inheritance and Override Behavior
In the previous step, you marked the
Start()method in theVehicleclass asvirtual.Now you will override this method in both
CarandTruckso that each vehicle type behaves differently when starting.This demonstrates inheritance-based polymorphism. Now that you have overridden the
Start()method inCar, apply the same approach to theTruckclass. >Run the ApplicationObserve the output.
Fleet Management System Car engine is starting smoothly... Reg: CAR123, Manufacturer: Toyota, Year: 2022 Truck is performing safety checks and starting loudly... Reg: TRK789, Manufacturer: Volvo, Year: 2020Even though both
CarandTruckare stored asVehicletypes inProgram.cs, the correct overridden method is executed at runtime.info> Executing the overridden method based on the actual object type is called Runtime Polymorphism.
In the next step, you will extend this specialization further by adding type-specific properties.
-
Challenge
Extend the Model with Type-Specific Properties
So far, you have specialized behavior by overriding the
Start()method.Now, you will extend the model further by overriding another method and reusing the base implementation.
Inheritance allows derived classes to:
- Reuse shared properties and behavior from the base class
- Extend existing behavior
- Override base methods when necessary
In this step, you will override the
DisplayInfo()method and use thebasekeyword to extend the existing implementation.
The
DisplayInfo()method in theVehicleclass prints common information such as:- Registration number
- Manufacturer
- Year
Rather than replacing this behavior entirely, you will reuse it and add a vehicle-specific message.
This demonstrates how derived classes can extend base functionality without duplicating code. Well done! The
Carclass now extends the base display behavior. Now repeat the same forTruck. >Run the application:Fleet Management System Car engine is starting smoothly... Reg: CAR123, Manufacturer: Toyota, Year: 2022 Doors: 4 Truck is performing safety checks and starting loudly... Reg: TRK789, Manufacturer: Volvo, Year: 2020 Load Capacity: 18.5 tonsObserve the output.
You should now see:
- Shared vehicle information
- Car-specific information
- Truck-specific information
This demonstrates how derived classes can:
- Extend state
- Extend behavior
- Reuse shared implementation
In the next step, you will introduce an interface to model shared capability across different types.
-
Challenge
Introduce a Behavioral Contract Using an Interface
So far, you have modeled identity using inheritance.
Inheritance works well when there is an "is-a" relationship.
Now consider a new requirement. The logistics company also owns office equipment that requires maintenance.
Examples:
- Generators
- Printers
- Servers
These are not vehicles. However, they still require maintenance.
This introduces a new modeling question:
Should office equipment inherit from
Vehicle? The answer is No.Office equipment is not a vehicle. Instead of modeling identity, you now need to model capability.
The capability is:
- Being maintainable.
This is where interfaces are appropriate.
info> An interface defines a contract that classes agree to follow.
Now you will define a contract in
IMaintainable.csthat represents maintenance behavior.
If you run the application now, you will notice that nothing has changed. That is expected.
Defining an interface does not affect program behavior until it is implemented by a class.
info> An interface defines a contract, but it does not provide implementation.
In the next step, you will implement
IMaintainableinVehicleand define a default maintenance behavior inherited by derived classes. -
Challenge
Implement the Interface in the Vehicle Hierarchy
In the previous step, you defined the
IMaintainableinterface.However, defining an interface alone does not change behavior.
Now you will apply this contract to the vehicle hierarchy. Any class that implements
IMaintainablemust provide an implementation ofPerformMaintenance(). By implementing the interface in the base class:- All derived classes automatically become maintainable.
CarandTruckinherit this maintenance behavior.- You maintain a clean hierarchy. You have added capability to the model.
In the next step, you will override maintenance behavior in
Truckand implement the interface inOfficeAssetto demonstrate interface-based polymorphism. -
Challenge
Override and Apply the Maintenance Contract Across Different Types
In the previous step, you implemented
IMaintainablein theVehicleclass. BecauseCarandTruckinherit fromVehicle, they automatically gained maintenance capability.Now you will:
- Override maintenance behavior in
Truck - Implement the same interface in a class that does not inherit from
Vehicle - Use a collection of
IMaintainableto demonstrate interface-based polymorphism Notice that the classOfficeAssetdoes not inherit fromVehicle. Now thatOfficeAssetimplementsIMaintainable, you can treat it the same way as vehicles when performing maintenance. >Run the Application
Fleet Management System Car engine is starting smoothly... Reg: CAR123, Manufacturer: Toyota, Year: 2022 Doors: 4 Truck is performing safety checks and starting loudly... Reg: TRK789, Manufacturer: Volvo, Year: 2020 Load Capacity: 18.5 tons Maintenance Operations: Performing general maintenance... Inspecting heavy-duty components of truck TRK222 Servicing office asset: Main Office GeneratorObserve the final behavior.
You have now seen two forms of polymorphism:
- Inheritance-based polymorphism using
List<Vehicle> - Interface-based polymorphism using
List<IMaintainable>
info>Inheritance models identity.
Interfaces model capability. - Override maintenance behavior in
-
Challenge
Conclusion and Next Steps
Congratulations! You have successfully completed the lab.
At this point, you have modeled a real-world system using inheritance and interfaces in C#.
You:
- Created a base class to represent shared identity
- Specialized behavior using method overriding
- Defined a contract using an interface
- Demonstrated both inheritance-based and interface-based polymorphism
You now understand that:
- Inheritance models what an object is
- Interfaces model what an object can do
Next Steps
You can extend this system by:
- Adding new vehicle types
- Creating additional interfaces
- Exploring composition as an alternative to inheritance
You have built a clean, extensible foundation.
Well done!
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.