Skip to content

Latest commit

 

History

History
262 lines (202 loc) · 8.49 KB

File metadata and controls

262 lines (202 loc) · 8.49 KB
status draft

Keybinding Library Architecture Analysis

This document analyzes how the Keybinding library implements SOLID and DRY principles and supports both standalone and dependency injection usage patterns.

SOLID Principles Implementation

1. Single Responsibility Principle (SRP)

Each class and interface has a single, well-defined responsibility:

  • ICommandRegistry / CommandRegistry: Manages command registration and retrieval only
  • IProfileManager / ProfileManager: Handles profile lifecycle management only
  • IKeybindingService / KeybindingService: Coordinates keybinding operations only
  • IKeybindingRepository / JsonKeybindingRepository: Handles data persistence only
  • KeybindingManager: Acts as a facade coordinating all services

2. Open/Closed Principle (OCP)

The library is open for extension but closed for modification:

  • Repository Pattern: New storage mechanisms can be implemented via IKeybindingRepository without modifying existing code
  • Service Interfaces: All core services are interface-based, allowing custom implementations
  • Factory Pattern: IKeybindingManagerFactory allows different creation strategies
  • Configuration Abstraction: IKeybindingConfiguration enables different configuration sources

Example Extension:

// Extend with custom repository without modifying existing code
public class DatabaseKeybindingRepository : IKeybindingRepository
{
    // Custom implementation for database storage
}

// Register with DI
services.AddKeybinding<DatabaseKeybindingRepository>();

3. Liskov Substitution Principle (LSP)

All implementations can be substituted for their interfaces without breaking functionality:

  • Any IKeybindingRepository implementation works with KeybindingManager
  • Any ICommandRegistry implementation maintains expected behavior
  • Mock implementations work seamlessly in unit tests

4. Interface Segregation Principle (ISP)

Interfaces are focused and clients depend only on what they need:

  • ICommandRegistry: Only command-related operations
  • IProfileManager: Only profile-related operations
  • IKeybindingService: Only keybinding coordination
  • IKeybindingRepository: Only persistence operations
  • IKeybindingConfiguration: Only configuration properties
  • IKeybindingManagerFactory: Only factory methods

No interface forces clients to depend on methods they don't use.

5. Dependency Inversion Principle (DIP)

High-level modules depend on abstractions, not concretions:

  • KeybindingManager depends on interfaces (ICommandRegistry, IProfileManager, IKeybindingRepository)
  • KeybindingService depends on abstractions (ICommandRegistry, IProfileManager)
  • Dependency Injection: All dependencies are injected via constructor
  • Factory Pattern: Creates objects through abstractions

DRY Principle Implementation

1. Service Registration

The ServiceCollectionExtensions eliminates repetitive DI registration:

// DRY: Single method registers all services
services.AddKeybinding("./data");

// Instead of repeating registration for each service
services.AddSingleton<ICommandRegistry, CommandRegistry>();
services.AddSingleton<IProfileManager, ProfileManager>();
// ... etc

2. Configuration Management

The IKeybindingConfiguration abstraction eliminates repeated configuration handling:

// DRY: Configuration is centralized and reusable
var config = new KeybindingConfiguration(dataDirectory, profileId, profileName);

3. Factory Pattern

The IKeybindingManagerFactory eliminates duplicate creation logic:

// DRY: Factory handles creation complexity
var manager = factory.CreateManager();
// Instead of repeating constructor calls with dependency resolution

4. Extension Methods

Service registration extensions eliminate repetitive DI setup code:

// DRY: Multiple registration patterns available
services.AddKeybinding();                    // Default singleton
services.AddKeybindingScoped();             // Scoped services
services.AddKeybinding<CustomRepository>(); // Custom repository

Standalone vs Dependency Injection Support

Standalone Usage

The library works perfectly without any DI container:

// Simple constructor-based instantiation
var manager = new KeybindingManager("./data");

// Or with custom services
var manager = new KeybindingManager(
    new CommandRegistry(),
    new ProfileManager(), 
    new JsonKeybindingRepository("./data"));

Benefits:

  • Zero dependencies on DI frameworks
  • Simple instantiation
  • Full control over object lifetime
  • Suitable for console apps, simple applications

Dependency Injection Usage

Full DI support with multiple registration patterns:

// Simple registration
services.AddKeybinding("./data");

// Custom repository
services.AddKeybinding<DatabaseRepository>();

// Scoped services (for multi-tenant scenarios)
services.AddKeybindingScoped();

Benefits:

  • Automatic dependency resolution
  • Configurable lifetimes (singleton, scoped, transient)
  • Integration with ASP.NET Core, Generic Host
  • Easy mocking for unit tests
  • Configuration binding support

Architecture Flexibility

1. Multiple Instantiation Patterns

Direct Instantiation:

var manager = new KeybindingManager("./data");

Service Locator:

var manager = serviceProvider.GetRequiredService<KeybindingManager>();

Factory Pattern:

var manager = factory.CreateManager("./tenant-data");

2. Configurable Dependencies

Default Dependencies:

services.AddKeybinding(); // Uses JsonKeybindingRepository

Custom Repository:

services.AddKeybinding<DatabaseKeybindingRepository>();

Custom Factory:

services.AddKeybinding<CustomRepository>(provider => 
    new CustomRepository(provider.GetService<IOptions<DbConfig>>()));

3. Lifetime Management

Singleton (Default):

services.AddKeybinding(); // All services as singletons

Scoped:

services.AddKeybindingScoped(); // All services as scoped

Mixed Lifetimes:

services.AddSingleton<ICommandRegistry, CommandRegistry>();
services.AddScoped<KeybindingManager>();

Testing Support

The architecture enables comprehensive testing strategies:

1. Unit Testing with Mocks

var mockRepository = new Mock<IKeybindingRepository>();
var mockCommands = new Mock<ICommandRegistry>();
var manager = new KeybindingManager(mockCommands.Object, mockProfiles.Object, mockRepository.Object);

2. Integration Testing

var services = new ServiceCollection();
services.AddKeybinding();
var provider = services.BuildServiceProvider();
var manager = provider.GetRequiredService<KeybindingManager>();

3. Test-Specific Implementations

public class InMemoryKeybindingRepository : IKeybindingRepository
{
    // In-memory implementation for testing
}

services.AddKeybinding<InMemoryKeybindingRepository>();

Benefits Summary

SOLID Compliance

Single Responsibility: Each class has one reason to change
Open/Closed: Extensible without modification
Liskov Substitution: Implementations are truly substitutable
Interface Segregation: Focused, cohesive interfaces
Dependency Inversion: Depends on abstractions, not concretions

DRY Compliance

No Repeated Logic: Common patterns are abstracted into helper classes
Reusable Components: Services can be composed differently
Configuration Centralization: Settings managed in one place
Extension Methods: Eliminate repetitive setup code
Disposal Patterns: Centralized disposal checking via DisposalHelper
Async Batch Operations: Reusable async iteration patterns via AsyncBatchHelper
Operation Counting: Standardized error-handling loops via OperationHelper
Validation Patterns: Common validation logic via ValidationHelper

Usage Flexibility

Standalone Usage: Works without DI containers
DI Integration: Full support for modern DI patterns
Multiple Lifetimes: Singleton, scoped, transient support
Custom Implementations: Easy to extend and customize
Testing Friendly: Mockable interfaces and test implementations

The Keybinding library successfully implements SOLID and DRY principles while providing maximum flexibility for both standalone and dependency injection usage patterns.