OOP, SOLID Principles, and Design Patterns - Comprehensive Senior Guide
Table of Contents
- Object-Oriented Programming - Deep Dive
- SOLID Principles - Detailed Implementation
- Creational Design Patterns
- Structural Design Patterns
- Behavioral Design Patterns
- Advanced OOP Concepts
- Pattern Selection Guide
Object-Oriented Programming - Deep Dive
1. Four Pillars of OOP
A. Abstraction
Abstraction è il processo di nascondere la complessità e esporre solo l’interfaccia essenziale.
// ❌ WRONG - Over-complicated implementation exposed
public class PaymentProcessor
{
public void ProcessCreditCard(string cardNumber, string cvv, string expiry,
decimal amount, string name, string email, string address,
int zipCode, string country, bool is3DSecure, string bankCode)
{
// 100 lines of implementation details
ValidateBankConnection();
EncryptSensitiveData();
CheckFraudScores();
// ... lots of complexity
}
}
// ✅ CORRECT - Implementation hidden, clean interface
public interface IPaymentProcessor
{
Task<PaymentResult> ProcessAsync(PaymentDetails details);
}
public class PaymentDetails
{
public string CardNumber { get; set; }
public string CVV { get; set; }
public string Expiry { get; set; }
public decimal Amount { get; set; }
// ... other properties encapsulated
}
public class CreditCardProcessor : IPaymentProcessor
{
private readonly ICardValidator _cardValidator;
private readonly IFraudDetectionService _fraudDetection;
private readonly IEncryptionService _encryption;
public async Task<PaymentResult> ProcessAsync(PaymentDetails details)
{
// Clean, abstracted interface
// Implementation details hidden
await _cardValidator.ValidateAsync(details);
await _fraudDetection.CheckAsync(details);
var encrypted = _encryption.Encrypt(details);
return new PaymentResult { Success = true };
}
}
// Advanced Abstraction - Abstract Classes
public abstract class BaseRepository<T> where T : class
{
protected DbContext Context { get; set; }
public abstract Task<T> GetByIdAsync(int id);
public abstract Task<List<T>> GetAllAsync();
public abstract Task AddAsync(T entity);
public abstract Task UpdateAsync(T entity);
public abstract Task DeleteAsync(int id);
// Template method pattern - abstraction of common behavior
protected virtual void ValidateEntity(T entity)
{
if (entity == null)
throw new ArgumentNullException(nameof(entity));
}
protected virtual async Task LogOperationAsync(string operation, T entity)
{
// Default implementation - can be overridden
await Task.CompletedTask;
}
}
// Abstraction Levels
/*
Level 1: Concrete Implementation
----- (Low-level details)
public void PaymentProcess() { /* 100 lines */ }
Level 2: Public Method
-----
public async Task<PaymentResult> ProcessAsync(PaymentDetails details)
Level 3: Interface
-----
public interface IPaymentProcessor
{
Task<PaymentResult> ProcessAsync(PaymentDetails details);
}
Level 4: Service Layer
-----
public class OrderService
{
private readonly IPaymentProcessor _processor;
public async Task CompleteOrderAsync(Order order)
{
var result = await _processor.ProcessAsync(order.PaymentDetails);
}
}
Level 5: API Layer
-----
[HttpPost("complete")]
public async Task<IActionResult> CompleteOrder([FromBody] OrderRequest request)
{
await _orderService.CompleteOrderAsync(request);
}
Business stakeholders - não veem nenhum detalhe
*/
B. Encapsulation
Encapsulation protege estado interno e força acesso através de interfaces.
// ❌ BAD - Public fields, no protection
public class BankAccount
{
public decimal Balance;
public string AccountNumber;
public void Withdraw(decimal amount)
{
Balance -= amount; // Sem validação!
}
}
// ❌ WORSE - Alguém pode fazer isto:
var account = new BankAccount();
account.Balance = -1000000; // Set negative balance directly!
// ✅ CORRECT - Encapsulated with validation
public class BankAccount
{
private decimal _balance;
private readonly object _lockObject = new();
private readonly List<Transaction> _transactions = new();
public decimal Balance
{
get { return _balance; }
private set { _balance = value; }
}
public string AccountNumber { get; private set; }
public BankAccount(string accountNumber, decimal initialBalance)
{
if (string.IsNullOrEmpty(accountNumber))
throw new ArgumentException("Account number required");
if (initialBalance < 0)
throw new ArgumentException("Initial balance cannot be negative");
AccountNumber = accountNumber;
_balance = initialBalance;
}
public bool Withdraw(decimal amount)
{
lock (_lockObject)
{
if (amount <= 0)
throw new ArgumentException("Withdrawal amount must be positive");
if (amount > _balance)
return false; // Insufficient funds
_balance -= amount;
_transactions.Add(new Transaction
{
Type = "Withdrawal",
Amount = amount,
Date = DateTime.UtcNow
});
return true;
}
}
public void Deposit(decimal amount)
{
lock (_lockObject)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive");
_balance += amount;
_transactions.Add(new Transaction
{
Type = "Deposit",
Amount = amount,
Date = DateTime.UtcNow
});
}
}
public IReadOnlyList<Transaction> GetTransactions()
{
return _transactions.AsReadOnly(); // Retorna cópia immutable
}
}
// Encapsulation Benefits:
// 1. ✓ Validação de entrada
// 2. ✓ Proteção de estado interno
// 3. ✓ Thread safety (lock)
// 4. ✓ Auditoria (transactions log)
// 5. ✓ Flexibilidade de implementação (pode mudar internamente)
C. Inheritance
Inheritance cria relacionamento “is-a” entre classes.
// Hierarchy - Base class
public abstract class Animal
{
public string Name { get; set; }
public virtual void Speak()
{
Console.WriteLine($"{Name} makes a sound");
}
public abstract void Move();
}
// Single Inheritance
public class Dog : Animal
{
public override void Speak()
{
Console.WriteLine($"{Name} barks: Woof!");
}
public override void Move()
{
Console.WriteLine($"{Name} runs on four legs");
}
}
// Multi-level Inheritance
public class Terrier : Dog
{
public override void Speak()
{
Console.WriteLine($"{Name} yaps: Yip yip!");
}
}
// ❌ ANTIPATTERN - Deep hierarchies (more than 3 levels)
public class BasePersistence { }
public class DataAccessLayer : BasePersistence { }
public class RepositoryBase : DataAccessLayer { }
public class ProductRepository : RepositoryBase { }
public class CachedProductRepository : ProductRepository { }
// Too deep! Hard to maintain
// ✅ BETTER - Use composition instead
public class CachedProductRepository
{
private readonly IProductRepository _repository;
private readonly IDistributedCache _cache;
public async Task<Product> GetByIdAsync(int id)
{
var cached = await _cache.GetAsync($"product_{id}");
if (cached != null)
return cached;
var product = await _repository.GetByIdAsync(id);
await _cache.SetAsync($"product_{id}", product);
return product;
}
}
// Inheritance Rules for Senior Developers:
// 1. Use inheritance para "is-a" relationships ONLY
// 2. Never inherit just to reuse code (use composition instead)
// 3. Keep hierarchies flat (max 3 levels)
// 4. Prefer interfaces over abstract classes for contracts
// 5. Violate LSP (Liskov) e você criou um bug
D. Polymorphism
Polymorphism permite múltiplas formas de mesma interface.
// 1. COMPILE-TIME POLYMORPHISM (Method Overloading)
public class Calculator
{
// Mesma método name, diferentes assinaturas
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public string Add(string a, string b) => a + b; // String concatenation
public int Add(int a, int b, int c) => a + b + c;
public List<int> Add(List<int> numbers) => numbers;
// Compiler resuelve qual método chamar em compile-time
}
// 2. RUNTIME POLYMORPHISM (Method Overriding)
public abstract class Shape
{
public abstract double GetArea();
public abstract void Draw();
}
public class Circle : Shape
{
public double Radius { get; set; }
public override double GetArea() => Math.PI * Radius * Radius;
public override void Draw() => Console.WriteLine("Drawing circle");
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public override double GetArea() => Width * Height;
public override void Draw() => Console.WriteLine("Drawing rectangle");
}
// Runtime - which shape method gets called é determinado em runtime
public class ShapeProcessor
{
public void ProcessShapes(List<Shape> shapes)
{
foreach (var shape in shapes)
{
double area = shape.GetArea(); // Polymorphic call
shape.Draw();
}
}
}
// 3. INTERFACE POLYMORPHISM
public interface IPaymentMethod
{
Task<bool> ProcessAsync(decimal amount);
Task RefundAsync(decimal amount);
}
public class CreditCard : IPaymentMethod
{
public async Task<bool> ProcessAsync(decimal amount) => true;
public async Task RefundAsync(decimal amount) => Console.WriteLine("Refunding credit card");
}
public class PayPal : IPaymentMethod
{
public async Task<bool> ProcessAsync(decimal amount) => true;
public async Task RefundAsync(decimal amount) => Console.WriteLine("Refunding PayPal");
}
public class Cryptocurrency : IPaymentMethod
{
public async Task<bool> ProcessAsync(decimal amount) => true;
public async Task RefundAsync(decimal amount) => Console.WriteLine("Refunding crypto wallet");
}
public class PaymentGateway
{
private readonly List<IPaymentMethod> _methods = new();
public async Task ProcessPaymentAsync(IPaymentMethod method, decimal amount)
{
// Polimorfismo - mesma interface, múltiplas implementações
bool success = await method.ProcessAsync(amount);
if (!success)
await method.RefundAsync(amount);
}
}
// 4. GENERIC POLYMORPHISM
public class GenericRepository<T> where T : class
{
private DbContext _context;
public async Task<T> GetByIdAsync(int id)
{
return await _context.Set<T>().FindAsync(id);
}
public async Task<List<T>> GetAllAsync()
{
return await _context.Set<T>().ToListAsync();
}
}
// Runtime - mesma repositório class, diferentes tipos
var userRepo = new GenericRepository<User>();
var productRepo = new GenericRepository<Product>();
// Polymorphism Benefits:
// ✓ Flexibility - easy to add new types
// ✓ Loose coupling - depend on abstractions
// ✓ Maintainability - changes in one place
// ✓ Extensibility - new implementations without changing caller
SOLID Principles - Detailed Implementation
S - Single Responsibility Principle
// ❌ VIOLATES SRP - Uma classe com múltiplas responsabilidades
public class UserManager
{
// Responsabilidade 1: User management
public void RegisterUser(string email, string password) { }
public void LoginUser(string email, string password) { }
public void UpdateUserProfile(int userId, string name) { }
// Responsabilidade 2: Email handling
public void SendWelcomeEmail(string email) { }
public void SendPasswordResetEmail(string email) { }
// Responsabilidade 3: Database operations
public void SaveToDatabase(User user) { }
public void DeleteFromDatabase(int userId) { }
// Responsabilidade 4: Validation
public bool ValidateEmail(string email) { }
public bool ValidatePassword(string password) { }
// Responsabilidade 5: Logging
public void LogUserActivity(int userId, string activity) { }
// Se precisa mudar: Email service, Database, Validation, Logging
// CADA mudança afeta esta classe
}
// ✅ FOLLOWS SRP - Separação de responsabilidades
public interface IUserRepository
{
Task<User> GetByEmailAsync(string email);
Task AddAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int userId);
}
public interface IEmailService
{
Task SendWelcomeEmailAsync(string email);
Task SendPasswordResetEmailAsync(string email);
}
public interface IUserValidator
{
ValidationResult ValidateEmail(string email);
ValidationResult ValidatePassword(string password);
}
public interface ILogger
{
void LogUserActivity(int userId, string activity);
}
public class UserRegistrationService
{
private readonly IUserValidator _validator;
private readonly IUserRepository _repository;
private readonly IEmailService _emailService;
private readonly ILogger _logger;
public async Task RegisterUserAsync(string email, string password)
{
// Validate
var emailValidation = _validator.ValidateEmail(email);
if (!emailValidation.IsValid)
throw new ValidationException(emailValidation.Errors);
var passwordValidation = _validator.ValidatePassword(password);
if (!passwordValidation.IsValid)
throw new ValidationException(passwordValidation.Errors);
// Create user
var user = new User { Email = email, Password = password };
// Save
await _repository.AddAsync(user);
// Send email
await _emailService.SendWelcomeEmailAsync(email);
// Log
_logger.LogUserActivity(user.Id, "User registered");
}
}
public class UserAuthenticationService
{
private readonly IUserRepository _repository;
private readonly ILogger _logger;
public async Task<User> LoginAsync(string email, string password)
{
var user = await _repository.GetByEmailAsync(email);
if (user == null || !VerifyPassword(user.Password, password))
{
_logger.LogUserActivity(0, $"Failed login attempt for {email}");
throw new UnauthorizedException();
}
_logger.LogUserActivity(user.Id, "User logged in");
return user;
}
private bool VerifyPassword(string hashedPassword, string inputPassword)
{
return BCrypt.Net.BCrypt.Verify(inputPassword, hashedPassword);
}
}
// Benefícios:
// ✓ Cada classe tem uma razão para mudar
// ✓ Fácil testar em isolamento
// ✓ Reutilizar componentes
// ✓ Manutenção simplificada
O - Open/Closed Principle
// ❌ VIOLATES OCP - Precisa modificar classe existente para adicionar novo tipo
public class NotificationService
{
public void SendNotification(string type, string message, string recipient)
{
if (type == "email")
SendEmail(recipient, message);
else if (type == "sms")
SendSMS(recipient, message);
else if (type == "push")
SendPushNotification(recipient, message);
else if (type == "telegram")
SendTelegram(recipient, message); // ← NOVA mudança REQUER mudança na classe
else if (type == "slack")
SendSlack(recipient, message); // ← OUTRA mudança
// Não escala bem!
}
}
// ✅ FOLLOWS OCP - Aberta para extensão, fechada para modificação
public interface INotificationChannel
{
Task SendAsync(string recipient, string message);
bool Supports(string channel);
}
public class EmailChannel : INotificationChannel
{
public bool Supports(string channel) => channel == "email";
public async Task SendAsync(string recipient, string message)
{
// Email implementation
await Task.CompletedTask;
}
}
public class SMSChannel : INotificationChannel
{
public bool Supports(string channel) => channel == "sms";
public async Task SendAsync(string recipient, string message)
{
// SMS implementation
await Task.CompletedTask;
}
}
public class PushNotificationChannel : INotificationChannel
{
public bool Supports(string channel) => channel == "push";
public async Task SendAsync(string recipient, string message)
{
// Push implementation
await Task.CompletedTask;
}
}
// NOVA FUNCIONALIDADE - Sem modificar classe existente!
public class TelegramChannel : INotificationChannel
{
public bool Supports(string channel) => channel == "telegram";
public async Task SendAsync(string recipient, string message)
{
// Telegram implementation
await Task.CompletedTask;
}
}
public class NotificationService
{
private readonly List<INotificationChannel> _channels;
public NotificationService(IEnumerable<INotificationChannel> channels)
{
_channels = channels.ToList();
}
public async Task SendAsync(string type, string message, string recipient)
{
var channel = _channels.FirstOrDefault(c => c.Supports(type));
if (channel == null)
throw new InvalidOperationException($"Channel {type} not supported");
await channel.SendAsync(recipient, message);
}
}
// Registro DI
public static void ConfigureNotifications(IServiceCollection services)
{
services.AddScoped<INotificationChannel, EmailChannel>();
services.AddScoped<INotificationChannel, SMSChannel>();
services.AddScoped<INotificationChannel, PushNotificationChannel>();
services.AddScoped<INotificationChannel, TelegramChannel>(); // ← Só adiciona, sem modificar nada
services.AddScoped<NotificationService>();
}
// Benefícios OCP:
// ✓ Estável para modificação
// ✓ Extensível sem quebrar código existente
// ✓ Fácil testar novos canais em isolamento
// ✓ Suporta plugin architecture
L - Liskov Substitution Principle
// ❌ VIOLATES LSP - Subclass não pode substituir parent
public class Bird
{
public virtual void Fly()
{
Console.WriteLine("Flying");
}
public virtual int GetAltitude()
{
return 5000;
}
}
public class Penguin : Bird
{
public override void Fly()
{
throw new NotImplementedException("Penguins can't fly");
}
public override int GetAltitude()
{
return 0; // Penguins don't fly
}
}
// Problema: Se código espera Bird, não pode passar Penguin
public void ReleaseBird(Bird bird)
{
bird.Fly(); // Crashes se bird é Penguin!
}
// ✅ FOLLOWS LSP - Proper inheritance hierarchy
public interface IFlyer
{
void Fly();
int GetAltitude();
}
public interface ISwimmer
{
void Swim();
}
public abstract class Bird
{
public abstract void Move();
}
public class Eagle : Bird, IFlyer
{
public void Fly() => Console.WriteLine("Eagle flying high");
public int GetAltitude() => 8000;
public override void Move() => Fly();
}
public class Duck : Bird, IFlyer, ISwimmer
{
public void Fly() => Console.WriteLine("Duck flying");
public int GetAltitude() => 2000;
public void Swim() => Console.WriteLine("Duck swimming");
public override void Move() => Swim();
}
public class Penguin : Bird, ISwimmer
{
public void Swim() => Console.WriteLine("Penguin swimming");
public override void Move() => Swim();
}
// Agora safe:
public void ReleaseFlyingBird(IFlyer bird)
{
bird.Fly(); // Safe - só recebe things que podem voar
}
public void ReleaseSwimmingBird(ISwimmer bird)
{
bird.Swim(); // Safe - só recebe things que podem nadar
}
// Liskov Substitution Checklist:
// ✓ Subclass nunca deve ser mais restrictiva que parent
// ✓ Subclass nunca deve requirir mais que parent
// ✓ Subclass nunca deve lançar exceções que parent não lançaria
// ✓ Subclass deve poder substituir parent em qualquer lugar
I - Interface Segregation Principle
// ❌ VIOLATES ISP - Fat interface força implementações desnecessárias
public interface IWorker
{
void Work();
void TakeBreak();
void Eat();
void Sleep();
void Manage();
void Code();
void Design();
}
public class Developer : IWorker
{
public void Work() => Console.WriteLine("Coding");
public void TakeBreak() => Console.WriteLine("Break");
public void Eat() => Console.WriteLine("Eating");
public void Sleep() => Console.WriteLine("Sleeping");
// Developer não gerencia
public void Manage() => throw new NotImplementedException();
// Developer pode codificar
public void Code() => Console.WriteLine("Writing code");
// Nem sempre desenvolvedor design
public void Design() => throw new NotImplementedException();
}
// ✅ FOLLOWS ISP - Segregated, focused interfaces
public interface IWorker
{
void Work();
}
public interface IHuman
{
void Eat();
void Sleep();
void TakeBreak();
}
public interface IManager
{
void Manage();
}
public interface IDeveloper
{
void Code();
}
public interface IDesigner
{
void Design();
}
public class Developer : IWorker, IHuman, IDeveloper
{
public void Work() => Console.WriteLine("Working");
public void Code() => Console.WriteLine("Coding");
public void Eat() => Console.WriteLine("Eating");
public void Sleep() => Console.WriteLine("Sleeping");
public void TakeBreak() => Console.WriteLine("Break");
}
public class Manager : IWorker, IHuman, IManager
{
public void Work() => Console.WriteLine("Managing");
public void Manage() => Console.WriteLine("Managing team");
public void Eat() => Console.WriteLine("Eating");
public void Sleep() => Console.WriteLine("Sleeping");
public void TakeBreak() => Console.WriteLine("Break");
}
public class TeamLead : IWorker, IHuman, IDeveloper, IManager
{
public void Work() => Console.WriteLine("Working");
public void Code() => Console.WriteLine("Coding");
public void Manage() => Console.WriteLine("Managing");
public void Eat() => Console.WriteLine("Eating");
public void Sleep() => Console.WriteLine("Sleeping");
public void TakeBreak() => Console.WriteLine("Break");
}
// ISP Guidelines:
// ✓ Interfaces devem ser específicas, não genéricas
// ✓ Muitas interfaces pequenas > poucas interfaces grandes
// ✓ Clientes não devem ser forçadas implementar métodos não usados
// ✓ Interface segregation aumenta reusabilidade
D - Dependency Inversion Principle
// ❌ VIOLATES DIP - High-level depends on low-level (tight coupling)
public class EmailSender
{
public void SendEmail(string to, string message)
{
// Direct SMTP implementation
using (var client = new SmtpClient("smtp.gmail.com"))
{
client.Send("from@email.com", to, "Subject", message);
}
}
}
public class NotificationService
{
private EmailSender _emailSender = new EmailSender(); // Direct dependency
public void NotifyUser(string email, string message)
{
_emailSender.SendEmail(email, message);
}
}
// Problem: Se mudar SMTP provider, precisa mudar NotificationService
// ✅ FOLLOWS DIP - High-level depends on abstraction
public interface IEmailSender
{
Task SendAsync(string to, string subject, string message);
}
public class GmailSender : IEmailSender
{
public async Task SendAsync(string to, string subject, string message)
{
using (var client = new SmtpClient("smtp.gmail.com"))
{
await client.SendMailAsync("from@email.com", to, subject, message);
}
}
}
public class SendGridSender : IEmailSender
{
private readonly SendGridClient _client;
public async Task SendAsync(string to, string subject, string message)
{
var msg = new SendGridMessage()
{
From = new EmailAddress("from@email.com"),
Subject = subject,
PlainTextContent = message,
HtmlContent = message,
};
msg.AddTo(new EmailAddress(to));
await _client.SendEmailAsync(msg);
}
}
public class NotificationService
{
private readonly IEmailSender _emailSender;
// Dependency injected
public NotificationService(IEmailSender emailSender)
{
_emailSender = emailSender;
}
public async Task NotifyUserAsync(string email, string message)
{
await _emailSender.SendAsync(email, "Notification", message);
}
}
// Dependency injection container
var services = new ServiceCollection();
services.AddScoped<IEmailSender, SendGridSender>(); // Swap implementation anytime
services.AddScoped<NotificationService>();
// DIP Benefits:
// ✓ Loose coupling - easy to swap implementations
// ✓ Testable - mock IEmailSender for tests
// ✓ Flexible - change providers without modifying NotificationService
// ✓ Follows Dependency Inversion - high-level doesn't depend on low-level
Creational Design Patterns
1. Singleton Pattern (Thread-Safe)
// ✅ RECOMMENDED - Lazy<T> implementation (simple and safe)
public class DatabaseConnection
{
private static readonly Lazy<DatabaseConnection> _instance =
new Lazy<DatabaseConnection>(() => new DatabaseConnection());
public static DatabaseConnection Instance => _instance.Value;
private DatabaseConnection()
{
Console.WriteLine("Initializing database connection");
// Expensive initialization
}
public void Connect() => Console.WriteLine("Connected to database");
}
// ✅ ALTERNATIVE - Static constructor (also thread-safe)
public class Logger
{
private static readonly Logger _instance = new Logger();
public static Logger Instance => _instance;
private Logger()
{
Console.WriteLine("Logger initialized");
}
public void Log(string message) => Console.WriteLine($"[LOG] {message}");
}
// ✅ MODERN - Static class (when no initialization needed)
public static class ApplicationSettings
{
public const string AppName = "MyApp";
public const string Version = "1.0.0";
public static string GetSetting(string key) => "";
}
// Usage:
var db = DatabaseConnection.Instance;
db.Connect();
var logger = Logger.Instance;
logger.Log("Application started");
// Singleton Antipatterns:
// ❌ Static variables (not thread-safe)
// ❌ Double-checked locking (complex, often wrong)
// ❌ Singletons everywhere (hidden dependencies)
// When to use Singleton:
// ✓ Database connections (connection pooling usually better)
// ✓ Logger (if centralized)
// ✓ Configuration (if read-only)
// ✓ Rarely - prefer dependency injection
2. Factory Pattern
// ✅ Simple Factory
public interface ILogger { }
public class ConsoleLogger : ILogger { }
public class FileLogger : ILogger { }
public class CloudLogger : ILogger { }
public class LoggerFactory
{
public static ILogger CreateLogger(string type) =>
type.ToLower() switch
{
"console" => new ConsoleLogger(),
"file" => new FileLogger(),
"cloud" => new CloudLogger(),
_ => throw new ArgumentException($"Unknown logger type: {type}")
};
}
// ✅ Abstract Factory - Creating families of objects
public interface IUIFactory
{
IButton CreateButton();
ITextBox CreateTextBox();
}
public interface IButton { void Click(); }
public interface ITextBox { void Input(string text); }
// Windows family
public class WindowsButton : IButton
{
public void Click() => Console.WriteLine("Windows button clicked");
}
public class WindowsTextBox : ITextBox
{
public void Input(string text) => Console.WriteLine($"Windows text input: {text}");
}
public class WindowsUIFactory : IUIFactory
{
public IButton CreateButton() => new WindowsButton();
public ITextBox CreateTextBox() => new WindowsTextBox();
}
// Mac family
public class MacButton : IButton
{
public void Click() => Console.WriteLine("Mac button clicked");
}
public class MacTextBox : ITextBox
{
public void Input(string text) => Console.WriteLine($"Mac text input: {text}");
}
public class MacUIFactory : IUIFactory
{
public IButton CreateButton() => new MacButton();
public ITextBox CreateTextBox() => new MacTextBox();
}
// Client code - doesn't know which family
public class Application
{
private readonly IButton _button;
private readonly ITextBox _textBox;
public Application(IUIFactory factory)
{
_button = factory.CreateButton();
_textBox = factory.CreateTextBox();
}
public void Run()
{
_button.Click();
_textBox.Input("Hello");
}
}
// Usage:
IUIFactory factory = GetUIFactory(); // Returns Windows or Mac factory
var app = new Application(factory);
app.Run();
// When to use Factory:
// ✓ Object creation is complex
// ✓ Different implementations based on runtime conditions
// ✓ Want to decouple creation from usage
// ✓ Multiple related objects (Abstract Factory)
3. Builder Pattern
// ✅ String Query Builder
public class SqlQueryBuilder
{
private string _select = "";
private string _from = "";
private string _where = "";
private string _orderBy = "";
private string _limit = "";
public SqlQueryBuilder Select(string columns)
{
_select = $"SELECT {columns}";
return this;
}
public SqlQueryBuilder From(string table)
{
_from = $"FROM {table}";
return this;
}
public SqlQueryBuilder Where(string condition)
{
_where = $"WHERE {condition}";
return this;
}
public SqlQueryBuilder OrderBy(string column)
{
_orderBy = $"ORDER BY {column}";
return this;
}
public SqlQueryBuilder Limit(int count)
{
_limit = $"LIMIT {count}";
return this;
}
public string Build()
{
return $"{_select} {_from} {_where} {_orderBy} {_limit}".Trim();
}
}
// Usage
var query = new SqlQueryBuilder()
.Select("Id, Name, Price")
.From("Products")
.Where("Price > 100")
.OrderBy("Name")
.Limit(10)
.Build();
// Result: SELECT Id, Name, Price FROM Products WHERE Price > 100 ORDER BY Name LIMIT 10
// ✅ Configuration Builder
public class RequestBuilder
{
private string _url;
private HttpMethod _method = HttpMethod.Get;
private Dictionary<string, string> _headers = new();
private string _body;
private TimeSpan _timeout = TimeSpan.FromSeconds(30);
public RequestBuilder WithUrl(string url)
{
_url = url;
return this;
}
public RequestBuilder WithMethod(HttpMethod method)
{
_method = method;
return this;
}
public RequestBuilder WithHeader(string key, string value)
{
_headers[key] = value;
return this;
}
public RequestBuilder WithBody(string body)
{
_body = body;
return this;
}
public RequestBuilder WithTimeout(TimeSpan timeout)
{
_timeout = timeout;
return this;
}
public HttpRequestMessage Build()
{
var request = new HttpRequestMessage(_method, _url);
foreach (var header in _headers)
request.Headers.Add(header.Key, header.Value);
if (!string.IsNullOrEmpty(_body))
request.Content = new StringContent(_body);
request.Headers.Add("Timeout", _timeout.TotalSeconds.ToString());
return request;
}
}
// Usage
var request = new RequestBuilder()
.WithUrl("https://api.example.com/products")
.WithMethod(HttpMethod.Post)
.WithHeader("Authorization", "Bearer token123")
.WithBody(JsonConvert.SerializeObject(new { name = "Product" }))
.WithTimeout(TimeSpan.FromSeconds(60))
.Build();
// When to use Builder:
// ✓ Complex object construction
// ✓ Optional parameters
// ✓ Immutable objects
// ✓ Fluent interface preferred
Structural Design Patterns
1. Adapter Pattern
// ✅ Converting incompatible interfaces
public interface IModernPaymentProcessor
{
Task<bool> ProcessAsync(decimal amount);
}
public class LegacyPaymentGateway
{
public bool Process(double amount, string currency = "USD")
{
// Old interface - synchronous, uses double, has currency
return amount > 0;
}
}
// Adapter - makes legacy compatible with modern
public class LegacyPaymentAdapter : IModernPaymentProcessor
{
private readonly LegacyPaymentGateway _legacyGateway;
public LegacyPaymentAdapter(LegacyPaymentGateway legacyGateway)
{
_legacyGateway = legacyGateway;
}
public async Task<bool> ProcessAsync(decimal amount)
{
// Adapt: decimal -> double, async -> sync, USD assumed
return await Task.FromResult(_legacyGateway.Process((double)amount, "USD"));
}
}
// Now can use legacy gateway with modern interface
public class CheckoutService
{
private readonly IModernPaymentProcessor _processor;
public CheckoutService(IModernPaymentProcessor processor)
{
_processor = processor;
}
public async Task CheckoutAsync(decimal amount)
{
bool success = await _processor.ProcessAsync(amount);
Console.WriteLine(success ? "Payment processed" : "Payment failed");
}
}
// Usage
var legacyGateway = new LegacyPaymentGateway();
IModernPaymentProcessor adapter = new LegacyPaymentAdapter(legacyGateway);
var checkout = new CheckoutService(adapter);
await checkout.CheckoutAsync(100);
// Adapter uses:
// ✓ Integrating legacy code
// ✓ Working with third-party libraries
// ✓ Converting between incompatible interfaces
2. Decorator Pattern
// ✅ Adding functionality dynamically
public interface IDataRepository
{
Task<T> GetAsync<T>(int id);
Task SaveAsync<T>(T entity);
}
public class BasicRepository : IDataRepository
{
public async Task<T> GetAsync<T>(int id)
{
await Task.Delay(100); // DB query
return (T)(object)new { Id = id };
}
public async Task SaveAsync<T>(T entity)
{
await Task.Delay(100); // DB insert
}
}
// Decorator 1: Caching
public class CachedRepository : IDataRepository
{
private readonly IDataRepository _innerRepository;
private readonly IDistributedCache _cache;
public CachedRepository(IDataRepository innerRepository, IDistributedCache cache)
{
_innerRepository = innerRepository;
_cache = cache;
}
public async Task<T> GetAsync<T>(int id)
{
var cacheKey = $"{typeof(T).Name}_{id}";
var cached = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cached))
return JsonConvert.DeserializeObject<T>(cached);
var result = await _innerRepository.GetAsync<T>(id);
await _cache.SetStringAsync(cacheKey, JsonConvert.SerializeObject(result));
return result;
}
public async Task SaveAsync<T>(T entity)
{
await _innerRepository.SaveAsync(entity);
// Invalidate cache...
}
}
// Decorator 2: Logging
public class LoggedRepository : IDataRepository
{
private readonly IDataRepository _innerRepository;
private readonly ILogger<LoggedRepository> _logger;
public LoggedRepository(IDataRepository innerRepository, ILogger<LoggedRepository> logger)
{
_innerRepository = innerRepository;
_logger = logger;
}
public async Task<T> GetAsync<T>(int id)
{
_logger.LogInformation($"Getting {typeof(T).Name} with id {id}");
var result = await _innerRepository.GetAsync<T>(id);
_logger.LogInformation($"Retrieved {typeof(T).Name}");
return result;
}
public async Task SaveAsync<T>(T entity)
{
_logger.LogInformation($"Saving {typeof(T).Name}");
await _innerRepository.SaveAsync(entity);
_logger.LogInformation($"Saved {typeof(T).Name}");
}
}
// Stack decorators - Composition
IDataRepository repo = new BasicRepository();
repo = new CachedRepository(repo, cache); // Add caching
repo = new LoggedRepository(repo, logger); // Add logging
// Usage
var user = await repo.GetAsync<User>(1); // Gets: cached → logged → basic
// Decorator Benefits:
// ✓ Add functionality without modifying original
// ✓ Multiple decorators can be stacked
// ✓ Better than inheritance for feature combination
// ✓ Runtime behavior changes
3. Facade Pattern
// ✅ Simplifying complex subsystem
public class OrderSubsystem
{
// Complex interfaces for different components
public interface IInventoryService
{
Task ReserveProductAsync(int productId, int quantity);
}
public interface IPaymentService
{
Task<bool> ProcessPaymentAsync(decimal amount);
}
public interface IShippingService
{
Task<string> CalculateShippingAsync(Address address);
Task ArrangeShippingAsync(Order order);
}
public interface INotificationService
{
Task SendConfirmationAsync(string email);
}
// Complex process - requiring multiple services
public class ComplexOrderProcess
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IShippingService _shipping;
private readonly INotificationService _notification;
public async Task ProcessOrderAsync(Order order)
{
// Many steps, many services
await _inventory.ReserveProductAsync(order.ProductId, order.Quantity);
bool paymentSuccess = await _payment.ProcessPaymentAsync(order.Total);
if (!paymentSuccess)
throw new PaymentFailedException();
var shippingCost = await _shipping.CalculateShippingAsync(order.Address);
await _shipping.ArrangeShippingAsync(order);
await _notification.SendConfirmationAsync(order.CustomerEmail);
}
}
}
// Facade - Simple interface for complex process
public interface IOrderService
{
Task<OrderResult> PlaceOrderAsync(OrderRequest request);
}
public class OrderFacade : IOrderService
{
private readonly IInventoryService _inventory;
private readonly IPaymentService _payment;
private readonly IShippingService _shipping;
private readonly INotificationService _notification;
public OrderFacade(
IInventoryService inventory,
IPaymentService payment,
IShippingService shipping,
INotificationService notification)
{
_inventory = inventory;
_payment = payment;
_shipping = shipping;
_notification = notification;
}
public async Task<OrderResult> PlaceOrderAsync(OrderRequest request)
{
try
{
// All complexity hidden inside
await _inventory.ReserveProductAsync(request.ProductId, request.Quantity);
if (!await _payment.ProcessPaymentAsync(request.Total))
return new OrderResult { Success = false, Message = "Payment failed" };
var shippingCost = await _shipping.CalculateShippingAsync(request.Address);
var order = CreateOrder(request, shippingCost);
await _shipping.ArrangeShippingAsync(order);
await _notification.SendConfirmationAsync(request.Email);
return new OrderResult { Success = true, OrderId = order.Id };
}
catch (Exception ex)
{
return new OrderResult { Success = false, Message = ex.Message };
}
}
private Order CreateOrder(OrderRequest request, decimal shippingCost)
{
return new Order
{
ProductId = request.ProductId,
Quantity = request.Quantity,
Total = request.Total + shippingCost,
Address = request.Address,
CustomerEmail = request.Email
};
}
}
// Simple usage
public class CheckoutController
{
private readonly IOrderService _orderService; // ← Uses facade
[HttpPost("order")]
public async Task<IActionResult> PlaceOrder([FromBody] OrderRequest request)
{
var result = await _orderService.PlaceOrderAsync(request);
return Ok(result);
}
}
// Facade Benefits:
// ✓ Simplifies complex subsystems
// ✓ Decouples clients from subsystem
// ✓ Centralized error handling
// ✓ Easier maintenance
Behavioral Design Patterns
1. Observer Pattern
// ✅ Notification system
public interface IObserver
{
void Update(string message);
}
public interface ISubject
{
void Attach(IObserver observer);
void Detach(IObserver observer);
void Notify();
}
public class OrderService : ISubject
{
private string _state;
private List<IObserver> _observers = new();
public void PlaceOrder(Order order)
{
_state = $"Order placed: {order.Id}";
Notify();
}
public void ProcessPayment(Order order)
{
_state = $"Payment processed: {order.Id}";
Notify();
}
public void ShipOrder(Order order)
{
_state = $"Order shipped: {order.Id}";
Notify();
}
public void Attach(IObserver observer) => _observers.Add(observer);
public void Detach(IObserver observer) => _observers.Remove(observer);
public void Notify() => _observers.ForEach(o => o.Update(_state));
}
public class EmailNotifier : IObserver
{
public void Update(string message) => Console.WriteLine($"Email: {message}");
}
public class SMSNotifier : IObserver
{
public void Update(string message) => Console.WriteLine($"SMS: {message}");
}
public class LoggingObserver : IObserver
{
public void Update(string message) => Console.WriteLine($"Log: {message}");
}
// Usage
var orderService = new OrderService();
orderService.Attach(new EmailNotifier());
orderService.Attach(new SMSNotifier());
orderService.Attach(new LoggingObserver());
var order = new Order { Id = 1 };
orderService.PlaceOrder(order); // All observers notified
orderService.ProcessPayment(order); // All observers notified
orderService.ShipOrder(order); // All observers notified
// Modern C# - Events
public class OrderServiceModern
{
public event EventHandler<OrderEventArgs> OrderPlaced;
public event EventHandler<OrderEventArgs> PaymentProcessed;
public void PlaceOrder(Order order)
{
// ... business logic
OrderPlaced?.Invoke(this, new OrderEventArgs { Order = order });
}
public void ProcessPayment(Order order)
{
// ... business logic
PaymentProcessed?.Invoke(this, new OrderEventArgs { Order = order });
}
}
public class OrderEventArgs : EventArgs
{
public Order Order { get; set; }
}
// Subscriber
var orderService = new OrderServiceModern();
orderService.OrderPlaced += (sender, args) => Console.WriteLine($"Email: Order {args.Order.Id} placed");
orderService.PaymentProcessed += (sender, args) => Console.WriteLine($"SMS: Payment for {args.Order.Id} processed");
// Observer/Event Uses:
// ✓ Loose coupling between components
// ✓ Publish-subscribe pattern
// ✓ Event-driven architecture
2. Strategy Pattern
// ✅ Interchangeable algorithms
public interface IDiscountStrategy
{
decimal CalculateDiscount(decimal originalPrice, int quantity);
}
public class VolumeDiscountStrategy : IDiscountStrategy
{
public decimal CalculateDiscount(decimal originalPrice, int quantity)
{
if (quantity >= 100)
return originalPrice * 0.20m; // 20% discount
if (quantity >= 50)
return originalPrice * 0.15m; // 15% discount
if (quantity >= 10)
return originalPrice * 0.10m; // 10% discount
return 0;
}
}
public class SeasonalDiscountStrategy : IDiscountStrategy
{
public decimal CalculateDiscount(decimal originalPrice, int quantity)
{
if (DateTime.Now.Month == 12) // Christmas
return originalPrice * 0.25m;
if (DateTime.Now.Month == 7) // Summer sale
return originalPrice * 0.15m;
return 0;
}
}
public class MembershipDiscountStrategy : IDiscountStrategy
{
private readonly MembershipLevel _level;
public MembershipDiscountStrategy(MembershipLevel level)
{
_level = level;
}
public decimal CalculateDiscount(decimal originalPrice, int quantity)
{
return _level switch
{
MembershipLevel.Gold => originalPrice * 0.25m,
MembershipLevel.Silver => originalPrice * 0.15m,
MembershipLevel.Bronze => originalPrice * 0.05m,
_ => 0
};
}
}
public class PricingService
{
private readonly IDiscountStrategy _discountStrategy;
public PricingService(IDiscountStrategy discountStrategy)
{
_discountStrategy = discountStrategy;
}
public decimal CalculateFinalPrice(decimal originalPrice, int quantity)
{
var discount = _discountStrategy.CalculateDiscount(originalPrice, quantity);
return originalPrice - discount;
}
}
// Usage - Strategy determined at runtime
IDiscountStrategy strategy = GetApplicableStrategy(customer);
var pricingService = new PricingService(strategy);
decimal finalPrice = pricingService.CalculateFinalPrice(100, 50);
// Strategy Pattern Benefits:
// ✓ Encapsulate algorithms
// ✓ Swap strategies at runtime
// ✓ Avoid conditional logic
// ✓ Easy to test each strategy
3. Command Pattern
// ✅ Encapsulate requests as objects
public interface ICommand
{
Task ExecuteAsync();
Task UndoAsync();
}
public class TransferMoneyCommand : ICommand
{
private readonly IAccountService _accountService;
private readonly Account _from;
private readonly Account _to;
private readonly decimal _amount;
private bool _executed = false;
public TransferMoneyCommand(IAccountService accountService, Account from, Account to, decimal amount)
{
_accountService = accountService;
_from = from;
_to = to;
_amount = amount;
}
public async Task ExecuteAsync()
{
await _accountService.WithdrawAsync(_from, _amount);
await _accountService.DepositAsync(_to, _amount);
_executed = true;
}
public async Task UndoAsync()
{
if (!_executed)
throw new InvalidOperationException("Cannot undo unexecuted command");
await _accountService.DepositAsync(_from, _amount);
await _accountService.WithdrawAsync(_to, _amount);
_executed = false;
}
}
public class CommandInvoker
{
private readonly Stack<ICommand> _executedCommands = new();
private readonly Stack<ICommand> _undoneCommands = new();
public async Task ExecuteAsync(ICommand command)
{
await command.ExecuteAsync();
_executedCommands.Push(command);
_undoneCommands.Clear(); // Clear redo history
}
public async Task UndoAsync()
{
if (_executedCommands.Count == 0)
return;
var command = _executedCommands.Pop();
await command.UndoAsync();
_undoneCommands.Push(command);
}
public async Task RedoAsync()
{
if (_undoneCommands.Count == 0)
return;
var command = _undoneCommands.Pop();
await command.ExecuteAsync();
_executedCommands.Push(command);
}
}
// Usage
var invoker = new CommandInvoker();
var transferCommand = new TransferMoneyCommand(
accountService,
fromAccount,
toAccount,
100);
await invoker.ExecuteAsync(transferCommand);
await invoker.UndoAsync(); // Reverses transfer
await invoker.RedoAsync(); // Re-applies transfer
// Command Pattern Uses:
// ✓ Undo/Redo functionality
// ✓ Transaction logging
// ✓ Queuing operations
// ✓ Scheduling tasks
// ✓ Macro recording
Advanced OOP Concepts
1. Generic Constraints
// ✅ Reference type constraint
public class ReferenceRepository<T> where T : class
{
public void Add(T item) { }
// T can be any reference type
}
// ✅ Value type constraint
public class ValueTypeHandler<T> where T : struct
{
public void Process(T value) { }
// T can be any value type (struct, int, bool, etc.)
}
// ✅ Default constructor constraint
public class Factory<T> where T : new()
{
public T CreateInstance()
{
return new T(); // T must have parameterless constructor
}
}
// ✅ Base class constraint
public class EmployeeRepository<T> where T : Employee
{
public void ProcessEmployee(T employee)
{
Console.WriteLine($"Processing {employee.Name}");
}
}
// ✅ Interface constraint
public class ValidationService<T> where T : IValidatable
{
public bool Validate(T item)
{
return item.IsValid();
}
}
// ✅ Enum constraint
public class EnumHelper<T> where T : Enum
{
public List<T> GetAllValues()
{
return Enum.GetValues(typeof(T)).Cast<T>().ToList();
}
}
// ✅ Multiple constraints
public class AdvancedService<T> where T : class, IDisposable, IComparable<T>, new()
{
// T must be:
// - Reference type
// - Implement IDisposable
// - Implement IComparable<T>
// - Have parameterless constructor
}
// ✅ Generic type constraints
public class GenericRepository<T> where T : class
{
private List<T> _items = new();
public void Add<TKey>(TKey id, T item) where TKey : IEquatable<TKey>
{
_items.Add(item);
}
}
2. Covariance and Contravariance
// ✅ Covariance (out) - Return type can be more specific
public interface IRepository<out T>
{
T Get();
}
public class Animal { }
public class Dog : Animal { }
public class DogRepository : IRepository<Dog>
{
public Dog Get() => new Dog();
}
// Covariance allows this:
IRepository<Animal> repo = new DogRepository(); // ✓ Safe - Dog IS-A Animal
Animal animal = repo.Get(); // ✓ You get at least an Animal
// ✅ Contravariance (in) - Parameter type can be more general
public interface IProcessor<in T>
{
void Process(T item);
}
public class AnimalProcessor : IProcessor<Animal>
{
public void Process(Animal animal) { }
}
// Contravariance allows this:
IProcessor<Dog> processor = new AnimalProcessor(); // ✓ Safe
processor.Process(new Dog()); // ✓ AnimalProcessor can process any Animal, including Dogs
// Real-world example with Func/Action
Func<Dog> getDog = () => new Dog();
Func<Animal> getAnimal = getDog; // ✓ Covariance - return type is more specific
Action<Animal> processAnimal = (a) => Console.WriteLine(a);
Action<Dog> processDog = processAnimal; // ✓ Contravariance - parameter type is more specific
Pattern Selection Guide
// When to use each pattern:
// CREATIONAL PATTERNS (Object creation)
/*
Singleton - Single instance, lazy initialization
Factory - Create objects without specifying classes
Abstract Factory - Create families of related objects
Builder - Complex object construction
Prototype - Clone objects
*/
// STRUCTURAL PATTERNS (Object composition)
/*
Adapter - Make incompatible interfaces work together
Bridge - Separate abstraction from implementation
Composite - Tree-like hierarchies
Decorator - Add functionality dynamically
Facade - Simplified interface for complex system
Flyweight - Share objects efficiently
Proxy - Control access to another object
*/
// BEHAVIORAL PATTERNS (Object communication)
/*
Chain of Resp. - Pass requests along a chain
Command - Encapsulate requests as objects
Interpreter - Language interpretation
Iterator - Sequential access to elements
Mediator - Centralized communication
Memento - Capture/restore object state
Observer - Notify multiple objects
State - Alter behavior when state changes
Strategy - Encapsulate algorithms
Template Method - Define algorithm skeleton
Visitor - Apply operations to objects
*/
public class PatternSelector
{
public string SelectPattern(string problem)
{
return problem switch
{
// Single instance needed
"Need one global logger" => "Singleton",
// Creating different types
"Creating notifications (email, SMS, push)" => "Factory or Strategy",
// Complex construction
"Building complex configurations" => "Builder",
// Interface incompatibility
"Using legacy code with new interface" => "Adapter",
// Adding features dynamically
"Adding caching to repository" => "Decorator",
// Simplifying complex system
"Wrapping complex order processing" => "Facade",
// Handling multiple observers
"Notifying multiple listeners of events" => "Observer",
// Selecting algorithm at runtime
"Different discount calculations" => "Strategy",
// Undoable operations
"Implementing undo/redo" => "Command",
// Hierarchical data
"File system folders and files" => "Composite",
// State-dependent behavior
"Order status transitions" => "State",
_ => "No pattern identified"
};
}
}
Concluzie
Diferențiatori Senior Developer în OOP/SOLID/Patterns:
-
Understanding Trade-offs
- Know when to apply patterns
- Understand cost of abstraction
- Don’t over-engineer
-
Code Review Skills
- Identify violations
- Suggest improvements
- Explain rationale
-
Mentoring
- Teach principles clearly
- Show examples
- Help junior devs improve
-
Production Mindset
- Performance implications
- Maintainability long-term
- Real-world constraints
-
Continuous Learning
- Patterns evolve
- New languages bring new patterns
- Study codebase designs
Data: 2025 C# Version: 12+ .NET Version: 8+