Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 231 additions & 0 deletions src/PatternKit.Examples/Decorators/StorageDecoratorExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
using PatternKit.Generators.Decorator;

namespace PatternKit.Examples.Decorators;

/// <summary>
/// Example demonstrating the Decorator pattern with a storage interface.
/// Shows how to use generated decorator base classes to implement caching,
/// logging, and retry logic in a composable way.
/// </summary>
[GenerateDecorator]
public interface IFileStorage
{
string ReadFile(string path);
void WriteFile(string path, string content);
bool FileExists(string path);
void DeleteFile(string path);
}

/// <summary>
/// Simple in-memory file storage implementation.
/// </summary>
public class InMemoryFileStorage : IFileStorage
{
private readonly Dictionary<string, string> _files = new();

public string ReadFile(string path)
{
if (!_files.TryGetValue(path, out var content))
throw new FileNotFoundException($"File not found: {path}");
return content;
}

public void WriteFile(string path, string content)
{
_files[path] = content;
}

public bool FileExists(string path)
{
return _files.ContainsKey(path);
}

public void DeleteFile(string path)
{
_files.Remove(path);
}
}

/// <summary>
/// Caching decorator that caches file reads.
/// </summary>
public class CachingFileStorage : FileStorageDecoratorBase
{
private readonly Dictionary<string, string> _cache = new();

public CachingFileStorage(IFileStorage inner) : base(inner) { }

public override string ReadFile(string path)
{
if (_cache.TryGetValue(path, out var cached))
{
Console.WriteLine($"[Cache] Hit for {path}");
return cached;
}

Console.WriteLine($"[Cache] Miss for {path}");
var content = base.ReadFile(path);
_cache[path] = content;
return content;
}

public override void WriteFile(string path, string content)
{
_cache.Remove(path); // Invalidate cache
base.WriteFile(path, content);
}

public override void DeleteFile(string path)
{
_cache.Remove(path); // Invalidate cache
base.DeleteFile(path);
}
}

/// <summary>
/// Logging decorator that logs all operations.
/// </summary>
public class LoggingFileStorage : FileStorageDecoratorBase
{
public LoggingFileStorage(IFileStorage inner) : base(inner) { }

public override string ReadFile(string path)
{
Console.WriteLine($"[Log] Reading file: {path}");
try
{
var result = base.ReadFile(path);
Console.WriteLine($"[Log] Successfully read {result.Length} characters from {path}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"[Log] Error reading {path}: {ex.Message}");
throw;
}
}

public override void WriteFile(string path, string content)
{
Console.WriteLine($"[Log] Writing {content.Length} characters to {path}");
base.WriteFile(path, content);
Console.WriteLine($"[Log] Successfully wrote to {path}");
}

public override bool FileExists(string path)
{
Console.WriteLine($"[Log] Checking existence of {path}");
var exists = base.FileExists(path);
Console.WriteLine($"[Log] File {path} exists: {exists}");
return exists;
}

public override void DeleteFile(string path)
{
Console.WriteLine($"[Log] Deleting file: {path}");
base.DeleteFile(path);
Console.WriteLine($"[Log] Successfully deleted {path}");
}
}

/// <summary>
/// Retry decorator that retries failed operations.
/// </summary>
public class RetryFileStorage : FileStorageDecoratorBase
{
private readonly int _maxRetries;
private readonly int _retryDelayMs;

public RetryFileStorage(IFileStorage inner, int maxRetries = 3, int retryDelayMs = 100)
: base(inner)
{
_maxRetries = maxRetries;
_retryDelayMs = retryDelayMs;
}

public override string ReadFile(string path)
{
return RetryOperation(() => base.ReadFile(path), $"ReadFile({path})");
}

public override void WriteFile(string path, string content)
{
RetryOperation(() => { base.WriteFile(path, content); return true; }, $"WriteFile({path})");
}

public override void DeleteFile(string path)
{
RetryOperation(() => { base.DeleteFile(path); return true; }, $"DeleteFile({path})");
}

private T RetryOperation<T>(Func<T> operation, string operationName)
{
for (int i = 0; i < _maxRetries; i++)
{
try
{
return operation();
}
catch (Exception ex) when (i < _maxRetries - 1)
{
Console.WriteLine($"[Retry] Attempt {i + 1}/{_maxRetries} failed for {operationName}: {ex.Message}");
Thread.Sleep(_retryDelayMs);
}
}
// Last attempt - let exception propagate
return operation();
}
}

/// <summary>
/// Demonstrates using the decorator pattern with the generated base class.
/// </summary>
public static class StorageDecoratorDemo
{
public static void Run()
{
Console.WriteLine("=== Decorator Pattern Demo ===\n");

// Base storage
var baseStorage = new InMemoryFileStorage();

// Compose decorators: Logging -> Caching -> Retry -> Base
// Order matters: outermost decorator is applied first
var storage = FileStorageDecorators.Compose(
baseStorage,
inner => new LoggingFileStorage(inner),
inner => new CachingFileStorage(inner),
inner => new RetryFileStorage(inner)
);

Console.WriteLine("--- Writing a file ---");
storage.WriteFile("test.txt", "Hello, Decorators!");

Console.WriteLine("\n--- Reading the file (cache miss) ---");
var content1 = storage.ReadFile("test.txt");
Console.WriteLine($"Content: {content1}");

Console.WriteLine("\n--- Reading the file again (cache hit) ---");
var content2 = storage.ReadFile("test.txt");
Console.WriteLine($"Content: {content2}");

Console.WriteLine("\n--- Checking file existence ---");
var exists = storage.FileExists("test.txt");
Console.WriteLine($"Exists: {exists}");

Console.WriteLine("\n--- Deleting the file ---");
storage.DeleteFile("test.txt");

Console.WriteLine("\n--- Trying to read deleted file (will fail with retries) ---");
try
{
storage.ReadFile("test.txt");
}
catch (FileNotFoundException ex)
{
Console.WriteLine($"Expected error: {ex.Message}");
}

Console.WriteLine("\n=== Demo Complete ===");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace PatternKit.Generators.Decorator;

/// <summary>
/// Marks a member that should not participate in decoration.
/// The generator will still emit a forwarding member in the decorator. For concrete
/// members it strips <c>virtual</c>/<c>override</c> so the member is not decorated,
/// but when required to satisfy an abstract or virtual contract it emits a
/// <c>sealed override</c> instead.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class DecoratorIgnoreAttribute : Attribute
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
namespace PatternKit.Generators.Decorator;

/// <summary>
/// Marks an interface or abstract class for Decorator pattern code generation.
/// Generates a base decorator class that forwards all members to an inner instance,
/// with optional composition helpers for building decorator chains.
/// </summary>
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class GenerateDecoratorAttribute : Attribute
{
/// <summary>
/// Name of the generated base decorator class.
/// Default is {ContractName}DecoratorBase (e.g., IStorage -> StorageDecoratorBase).
/// </summary>
public string? BaseTypeName { get; set; }

/// <summary>
/// Name of the generated helpers/composition class.
/// Default is {ContractName}Decorators (e.g., IStorage -> StorageDecorators).
/// </summary>
public string? HelpersTypeName { get; set; }

/// <summary>
/// Determines what composition helpers are generated.
/// Default is HelpersOnly (generates a Compose method for chaining decorators).
/// </summary>
public DecoratorCompositionMode Composition { get; set; } = DecoratorCompositionMode.HelpersOnly;

/// <summary>
/// Reserved for future use.
/// Currently ignored by the Decorator generator and has no effect on generated code.
/// Default is false.
/// </summary>
public bool GenerateAsync { get; set; }

/// <summary>
/// Reserved for future use.
/// Currently ignored by the Decorator generator and has no effect on generated code.
/// Default is false.
/// </summary>
public bool ForceAsync { get; set; }
}

/// <summary>
/// Determines what composition utilities are generated with the decorator.
/// </summary>
public enum DecoratorCompositionMode
{
/// <summary>
/// Do not generate any composition helpers.
/// </summary>
None = 0,

/// <summary>
/// Generate a static Compose method for chaining decorators in order.
/// Decorators are applied in array order (first element is outermost).
/// </summary>
HelpersOnly = 1,

/// <summary>
/// Generate pipeline-style composition with explicit "next" parameter.
/// (Reserved for future use - v2 feature)
/// </summary>
PipelineNextStyle = 2
}
5 changes: 5 additions & 0 deletions src/PatternKit.Generators/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@ PKMEM006 | PatternKit.Generators.Memento | Info | Init-only or readonly restrict
PKVIS001 | PatternKit.Generators.Visitor | Warning | No concrete types found for visitor generation
PKVIS002 | PatternKit.Generators.Visitor | Error | Type must be partial for Accept method generation
PKVIS004 | PatternKit.Generators.Visitor | Error | Derived type must be partial for Accept method generation
PKDEC001 | PatternKit.Generators.Decorator | Error | Unsupported target type for decorator generation
PKDEC002 | PatternKit.Generators.Decorator | Error | Unsupported member kind for decorator generation
PKDEC003 | PatternKit.Generators.Decorator | Error | Name conflict for generated decorator types
PKDEC004 | PatternKit.Generators.Decorator | Warning | Member is not accessible for decorator generation
PKDEC005 | PatternKit.Generators.Decorator | Error | Generic contracts are not supported for decorator generation
Loading