This document provides a detailed overview of the Semantics library architecture, focusing on the SOLID principles and DRY practices implemented throughout the codebase.
- Overview
- SOLID Principles Implementation
- DRY Implementation
- Design Patterns
- Class Hierarchy
- Validation System
- Testing Strategy
The Semantics library is designed around clean architecture principles, with a focus on maintainability, extensibility, and testability. The core philosophy is to provide type-safe string wrappers while maintaining excellent separation of concerns and avoiding code duplication.
Each class has a single, well-defined responsibility:
ISemanticStringFactory<T>: Object creation onlySemanticStringFactory<T>: Concrete creation logic- Purpose: Separates construction from business logic
IValidationStrategy: Defines validation processingValidateAllStrategy: "All must pass" logicValidateAnyStrategy: "Any can pass" logicValidationStrategyFactory: Strategy creation
Open for extension, closed for modification:
// Add new validation rules without modifying existing code
public class CustomBusinessRule : ValidationRuleBase
{
public override string RuleName => "CustomBusiness";
protected override bool ValidateCore(SemanticStringValidationAttribute attribute, ISemanticString value)
{
// Custom validation logic
return true;
}
}All implementations are fully substitutable through behavioral contracts:
// Contract validation ensures LSP compliance
public static class SemanticStringContracts
{
public static bool ValidateContracts<T>(T instance) where T : SemanticString<T>
{
// Validates basic contracts (reflexivity, etc.)
}
public static bool ValidateEqualityContracts<T>(T? first, T? second) where T : SemanticString<T>
{
// Validates equality behavior
}
}Focused, client-specific interfaces:
ISemanticStringFactory<T>: Factory operations onlyIValidationStrategy: Validation strategy operations onlyIValidationRule: Individual rule operations only
High-level modules depend on abstractions:
// Service depends on abstraction, not concrete implementation
public class UserService
{
private readonly ISemanticStringFactory<EmailAddress> _emailFactory;
public UserService(ISemanticStringFactory<EmailAddress> emailFactory)
{
_emailFactory = emailFactory;
}
}Problem Eliminated: Duplicated validation logic across multiple methods.
Solution: Centralized validation strategies with pluggable implementations.
public interface IValidationStrategy
{
bool Validate(IEnumerable<SemanticStringValidationAttribute> attributes, ISemanticString value);
}
// Reusable strategies eliminate duplication
public class ValidateAllStrategy : IValidationStrategy { /* implementation */ }
public class ValidateAnyStrategy : IValidationStrategy { /* implementation */ }Problem Eliminated: Common validation patterns repeated across attribute types.
Solution: Base class with template method for common functionality.
public abstract class ValidationRuleBase : IValidationRule
{
// Template method - common structure, specific implementation in derived classes
public bool Validate(SemanticStringValidationAttribute attribute, ISemanticString value)
{
if (!IsApplicable(attribute)) return false;
if (value?.ToString() is not string stringValue) return false;
return ValidateCore(attribute, value); // Specific implementation
}
protected abstract bool ValidateCore(SemanticStringValidationAttribute attribute, ISemanticString value);
}- Purpose: Encapsulate object creation logic
- Implementation:
ISemanticStringFactory<T>andSemanticStringFactory<T> - Benefits: Consistent creation, testability, DI support
- Purpose: Interchangeable validation algorithms
- Implementation:
IValidationStrategywith concrete strategies - Benefits: Extensibility, configurable behavior
- Purpose: Define algorithm structure with customizable steps
- Implementation:
ValidationRuleBasewith abstract methods - Benefits: Code reuse, consistent structure
ISemanticString
├── SemanticString<TDerived> (abstract base)
├── SemanticPath<TDerived> (path-specific base)
│ ├── SemanticAbsolutePath<TDerived> (absolute paths base)
│ │ └── AbsolutePath
│ ├── SemanticRelativePath<TDerived> (relative paths base)
│ │ └── RelativePath
│ ├── SemanticFilePath<TDerived> (file paths base)
│ │ ├── FilePath
│ │ ├── AbsoluteFilePath
│ │ └── RelativeFilePath
│ ├── SemanticDirectoryPath<TDerived> (directory paths base)
│ │ ├── DirectoryPath
│ │ ├── AbsoluteDirectoryPath
│ │ └── RelativeDirectoryPath
│ ├── FileName
│ └── FileExtension
└── [Custom semantic string types]
ISemanticStringFactory<T>
└── SemanticStringFactory<T>
IValidationStrategy
├── ValidateAllStrategy
├── ValidateAnyStrategy
└── [Custom validation strategies]
IValidationRule
├── ValidationRuleBase (abstract)
│ ├── LengthValidationRule
│ ├── PatternValidationRule
│ └── [Custom validation rules]
└── [Custom validation rules]
The library provides a comprehensive interface hierarchy for path types that enables polymorphism and type-safe operations:
IPath (base interface for all path types)
├── IAbsolutePath : IPath
├── IRelativePath : IPath
├── IFilePath : IPath
├── IDirectoryPath : IPath
├── IAbsoluteFilePath : IFilePath, IAbsolutePath
├── IRelativeFilePath : IFilePath, IRelativePath
├── IAbsoluteDirectoryPath : IDirectoryPath, IAbsolutePath
└── IRelativeDirectoryPath : IDirectoryPath, IRelativePath
IFileName (separate hierarchy for non-path file components)
IFileExtension (separate hierarchy for file extensions)
Interface Implementation Mapping:
// Path types implement their corresponding interfaces
AbsolutePath : SemanticAbsolutePath<AbsolutePath>, IAbsolutePath
RelativePath : SemanticRelativePath<RelativePath>, IRelativePath
FilePath : SemanticFilePath<FilePath>, IFilePath
DirectoryPath : SemanticDirectoryPath<DirectoryPath>, IDirectoryPath
AbsoluteFilePath : SemanticFilePath<AbsoluteFilePath>, IAbsoluteFilePath
RelativeFilePath : SemanticFilePath<RelativeFilePath>, IRelativeFilePath
AbsoluteDirectoryPath : SemanticDirectoryPath<AbsoluteDirectoryPath>, IAbsoluteDirectoryPath
RelativeDirectoryPath : SemanticDirectoryPath<RelativeDirectoryPath>, IRelativeDirectoryPath
// Non-path types have separate interfaces
FileName : SemanticString<FileName>, IFileName
FileExtension : SemanticString<FileExtension>, IFileExtensionPolymorphic Benefits:
- Type-safe Collections: Store different path types in the same collection using common interfaces
- Polymorphic Methods: Write methods that accept any path type or specific categories
- Interface Segregation: Use the most specific interface needed for your use case
- Extensibility: Easy to add new path types that integrate with existing polymorphic code
The validation system follows a layered approach:
- Attribute Layer:
SemanticStringValidationAttributeclasses - Strategy Layer:
IValidationStrategyimplementations - Rule Layer:
IValidationRuleimplementations - Factory Layer:
ValidationStrategyFactorycreates strategies
User Creates Semantic String
↓
SemanticStringFactory
↓
Get Validation Attributes
↓
ValidationStrategyFactory.GetStrategy()
↓
IValidationStrategy.Validate()
↓
For Each Attribute: IValidationRule.Validate()
↓
Combine Results (All/Any/Custom)
↓
Return Valid Object or Throw Exception
- All implementations must pass contract validation
SemanticStringContractsprovides standardized tests- Ensures LSP compliance
[Test]
public void EmailAddress_ShouldSatisfyContracts()
{
var email1 = _factory.Create("user1@example.com");
var email2 = _factory.Create("user2@example.com");
var email3 = _factory.Create("user3@example.com");
Assert.IsTrue(SemanticStringContracts.ValidateContracts(email1));
Assert.IsTrue(SemanticStringContracts.ValidateEqualityContracts(email1, email2));
Assert.IsTrue(SemanticStringContracts.ValidateComparisonContracts(email1, email2, email3));
}This architecture ensures the library remains maintainable, extensible, and testable while providing excellent performance and type safety.