In academia, OOP is often taught using “Animal” or “Car” classes. In real-world .NET projects (like ASP.NET Core APIs), OOP is about managing complexity.
BankOrder class that only allows status changes through specific methods, never direct property setters).IEmailService).SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible, and maintainable.
“A class should have one, and only one, reason to change.”
UserService class that validates user input, saves the user to the database, and sends a welcome email.UserValidator, UserRepository, and EmailService.“Software entities should be open for extension, but closed for modification.”
DiscountCalculator that uses if/else for “Student” or “Senior,” you have to change the code every time you add a new discount type.IDiscountStrategy. You add a new class MemberDiscount without ever touching the existing calculator code.“Objects of a superclass should be replaceable with objects of its subclasses without breaking the application.”
Square inheriting from Rectangle. If a method expects a Rectangle and sets Height to 10 and Width to 5, a Square will break because its sides must be equal.NotImplementedException for a base method, you are likely violating LSP.“Clients should not be forced to depend upon interfaces that they do not use.”
IMachine interface with Print(), Fax(), and Scan(). An OldPrinter class is forced to implement Fax() even if it can’t do it.IPrinter, IFax, and IScanner.“Depend upon abstractions, not concretions.”
This is the “health check” of your architecture.
| Feature | Tight Coupling (Bad) | Loose Coupling (Good) |
| Dependency | Class A creates an instance of Class B (new ClassB()). | Class A is handed an interface (IB). |
| Flexibility | Changing B requires changing A. | You can swap B for C without touching A. |
| Testing | Impossible to unit test A without B. | You can “Mock” the interface for testing. |
Code Smells are surface indications that usually correspond to a deeper problem in the system.
DI is the primary way we implement Dependency Inversion in .NET. Instead of a class “buying” its tools, they are “delivered” to it via the constructor.
Example in .NET Core:
C#
// The Interface
public interface IMessageService { void Send(string msg); }
// The Implementation
public class EmailService : IMessageService {
public void Send(string msg) => Console.WriteLine($"Email: {msg}");
}
// The Consumer (Loosely Coupled)
public class UserRegistration {
private readonly IMessageService _svc;
public UserRegistration(IMessageService svc) => _svc = svc; // DI happens here
}
In .NET, you register these in Program.cs using builder.Services.AddScoped<IMessageService, EmailService>();.
Imagine a Payment Processing System.
IPaymentProcessor with a Process() method.StripeProvider, PayPalProvider, CryptoProvider.IPaymentProcessor.