diff --git a/docs/document/Csharp Design Patterns/docs/Behavioural/Command.md b/docs/document/Csharp Design Patterns/docs/Behavioural/Command.md index d7ae485a..e16ceced 100644 --- a/docs/document/Csharp Design Patterns/docs/Behavioural/Command.md +++ b/docs/document/Csharp Design Patterns/docs/Behavioural/Command.md @@ -13,6 +13,10 @@ It's heavily used by cli implementations and GUI development. ## ICommand +A simple command implementation is a `ICommand` interface + an object can be applied on by commands. + +A Command should be a immutable object which we will represent it as a record. + ```cs var editor = new Editor(content: "hello"); // wrap command info inside a instance @@ -158,3 +162,86 @@ public class Editor } } ``` + +## Composite Command + +A composite command is the combination of Command pattern and Composite pattern. + +- Collection like to store commands with order. +- Execute as chaining, be aware to handle exceptions. +- Undo as chaining, be aware to handle exceptions. + +> [!tip] +> Commands might also need context to perform actions one by one. + +A base class can be like the following. + +- A composite command should be `ICommand` too. +- A composite command should be a collection like command. +- Default `Execute` can revoke all executed when any command failed. + +```cs +using System.Runtime.InteropServices; + +public interface ICommand +{ + void Execute(); + void Undo(); + bool Success { get; set; } +} + +public abstract class CompositeCommand : List, ICommand where T : class?, ICommand +{ + public bool Success + { + get => this.All(cmd => cmd.Success); + set => field = value; + } + + public virtual void Execute() + { + foreach (var cmd in this) + { + cmd.Execute(); + // if any cmd failed, revoke all executed // [!code highlight] + if (!cmd.Success) // [!code highlight] + { // [!code highlight] + var reverse = CollectionsMarshal.AsSpan(this)[..IndexOf(cmd)]; // [!code highlight] + reverse.Reverse(); // [!code highlight] + foreach (var c in reverse) // [!code highlight] + c.Undo(); // [!code highlight] +// [!code highlight] + return; // [!code highlight] + } // [!code highlight] + } + + Success = true; + } + + public virtual void Undo() + { + foreach (var cmd in Enumerable.Reverse(this)) + // only undo executed + if (cmd.Success) cmd.Undo(); + } +} +``` + +```cs +var editor = new Editor(content: "hello"); + +var commandInsert = new EditorCommand(editor, EditorCommandType.Insert, ", world"); +var commandDelete = new EditorCommand(editor, EditorCommandType.Delete, 1); +// deletion exceeds the length. will revoke all executions. // [!code highlight] +var wrongCommand = new EditorCommand(editor, EditorCommandType.Delete, 100); // [!code highlight] + +// should edit back to `hello` // [!code highlight] +var combined = new EditorCompositeCommand() { commandInsert, commandDelete, wrongCommand }; // [!code highlight] + +combined.Execute(); + +class EditorCompositeCommand : CompositeCommand +{ + +} +``` diff --git a/docs/document/Csharp Design Patterns/docs/Structural/1.Adapter.md b/docs/document/Csharp Design Patterns/docs/Structural/1.Adapter.md index 0f758e50..fd373e77 100644 --- a/docs/document/Csharp Design Patterns/docs/Structural/1.Adapter.md +++ b/docs/document/Csharp Design Patterns/docs/Structural/1.Adapter.md @@ -10,25 +10,29 @@ Assuming that there is a extern class called **Adaptee**(**Adaptee** is the type The type to be adapted `Adaptee` is sealed to simulate the extern class. This pattern helps to prevent from break existing implementation like `ExistingImpl.DoWorkWith(IAdapteeCannotImpl)`, and especially when `ExistingImpl` is not modifiable. -```csharp +```cs interface IAdapteeCannotImpl { string DoWork(); } + sealed class Adaptee { public string DoSpecialWork() => $"{this.GetType().Name} can do something but you cannot simply invoke using existing implementation."; } + class Adapter : IAdapteeCannotImpl { private readonly Adaptee _adaptee = new(); public string DoWork() => _adaptee.DoSpecialWork(); } + static class ExistingImpl { public static string DoWorkWith(IAdapteeCannotImpl obj) => obj?.DoWork()!; } + public class AdapterTest { private readonly ITestOutputHelper? _helper; @@ -49,11 +53,12 @@ public class AdapterTest Assuming that we have a `IGeometry` interface and a `Geometry` class that references the `IGeometry` to simulate the adapter pattern. -```csharp +```cs interface IGeometry { void Draw(); } + static class Geometry { public static void Draw(IGeometry geometry) => geometry.Draw(); @@ -62,11 +67,12 @@ static class Geometry Then we have two internal classes `Point`, `Line` implement `IGeometry` and an extern class `VectorObject` that cannot implement `IGeometry` which is the target to be adapted. Although `VectorObject` does access the internal `Line`, it's just a example. -```csharp +```cs record class Point(int X, int Y) : IGeometry { public void Draw() => Console.WriteLine($"...Drawing point({X}, {Y})"); } + record class Line(Point Start, Point End) : IGeometry { public void Draw() @@ -88,7 +94,7 @@ Since `VectorObject` cannot implement `IGeometry`, we need to make a adapter for The `VectorObjectAdapter` accepts a `VectorObject` to construct a sequence of `Point`, and then implements the `IGeometry` using `Point.Draw()`. This is how `VectorObject` was adapted. -```csharp +```cs class VectorObjectAdapter : Collection, IGeometry { public VectorObjectAdapter(VectorObject obj) @@ -107,11 +113,12 @@ class VectorObjectAdapter : Collection, IGeometry When invoke `Geometry.Draw()`, `VectorObjectAdapter` works fine. -```csharp +```cs VectorObject vec = new( new Line(new(1, 1), new(2, 2)), new Line(new(1, 2), new(3, 4)) ); + VectorObjectAdapter vecAdapter = new(vec); Geometry.Draw(vecAdapter); ``` @@ -198,7 +205,7 @@ Let's code a simple `Vector` in math! `Vector` in math takes some numbers to rep In `C#`, generic type parameter cannot be a literal, so we cannot easily specify the dimension of `Vector`, one approach is specifying dimension using constructor, but we will do the another way. -```csharp +```cs class Vector { protected TData[]? data; @@ -217,11 +224,12 @@ To pass literal information with a type, we need abstraction. Let `IDimension` as interface with some class implementing it. To be organized, specific dimension class can be nested in a `static`/`abstract` class or placed in a different `namespace` -```csharp +```cs interface IDimension { ushort Dimension { get; } } + abstract class Dimension { public class Two : IDimension @@ -238,7 +246,7 @@ abstract class Dimension With implementation above, we now can modify the `Vector` as -```csharp +```cs class Vector where TDim : IDimension, new() { @@ -256,7 +264,7 @@ Since `Dimension` property of `IDimension` belongs to a instance, so `TDim` shal Now we can say, we already done one case of generic adapter! -```csharp +```cs Vector vec = new(); Vector vec = new(); ``` @@ -271,7 +279,7 @@ One approach of implementing operators for `Vector` is using `inter If we don't care scenarios of arrays, we can implement it for numerics like -```csharp +```cs class Vector : IMultiplyOperators, Vector, Vector>, IAdditionOperators, Vector, Vector> @@ -308,7 +316,7 @@ To make vector operators valid when `TData` is array type, we can create a inter ##### Generic Adapter - Reducing Type Parameter -```csharp +```cs class VectorOfIntArray : Vector where TDim : IDimension, new() { @@ -319,7 +327,7 @@ class VectorOfIntArray : Vector Now we need some modification on `Vector` to check uniformity for each dimension of vector which is a array type. -```csharp +```cs class Vector where TDim : IDimension, new() { @@ -343,18 +351,19 @@ class Vector } this.data = data; } +} ``` Now we can check whether it's valid. -```csharp +```cs // Error! Two arrays does not have the same length. VectorOfIntArray vectorOfInt = new(new[] { 1, 2 }, new[] { 1, 2, 3 }); ``` Then we can impalement the `IMultiplyOperators` and `IAdditionOperators` for `vectorOfInt`. -```csharp +```cs class VectorOfIntArray : Vector, IMultiplyOperators, VectorOfIntArray, VectorOfIntArray>, @@ -370,7 +379,7 @@ class VectorOfIntArray : } ``` -```csharp +```cs VectorOfIntArray vec1 = new(new[] { 1, 2, 3 }, new[] { 1, 2, 3 }); VectorOfIntArray vec2 = new(new[] { 2, 2, 2 }, new[] { 1, 1, 1 }); Console.WriteLine(vec1 * vec2 is VectorOfIntArray); // true @@ -388,13 +397,13 @@ Is there any possibility to **"inherit"** the operator? Well, operators are `sta What is the return type of the operator??? -```csharp +```cs public static operator *( left, right) ``` Remember, we are aiming to implement **operator overriding**, so the return type must be the derived type of `Vector`. But we cannot define a generic return type for any method or operator, so the lacked type information shall be added as a new type parameter for the class `Vector`. -```csharp +```cs class Vector where TSelf : Vector, new() where TDim : IDimension, new() @@ -411,40 +420,40 @@ Now we have a new type parameter that inherits from `Vector` Now let's implement the detail. -```csharp +```cs public static TSelf operator *(TSelf left, Vector right) +{ + if (right is not TSelf) + throw new InvalidCastException($"Cannot cast {nameof(right)} to {typeof(TSelf).Name}"); + if (default(TData) is ValueType) { - if (right is not TSelf) - throw new InvalidCastException($"Cannot cast {nameof(right)} to {typeof(TSelf).Name}"); - if (default(TData) is ValueType) + var opMultiInterface = typeof(IMultiplyOperators<,,>).MakeGenericType(typeof(TData), typeof(TData), typeof(TData)); + var interfaceMethod = typeof(TData).GetMethod( + $"{opMultiInterface.Namespace}.{opMultiInterface.Name[..^2]}<{typeof(TData).FullName},{typeof(TData).FullName},{typeof(TData).FullName}>.op_Multiply", + (BindingFlags)(-1) + ); + var ordinaryMethod = typeof(TData).GetMethod("op_Multiply", (BindingFlags)(-1)); + var twoPossibleOperators = new[] { interfaceMethod, ordinaryMethod }; + var value = left.data.Zip(right.data, (x, y) => (x, y)).Select(x => { - var opMultiInterface = typeof(IMultiplyOperators<,,>).MakeGenericType(typeof(TData), typeof(TData), typeof(TData)); - var interfaceMethod = typeof(TData).GetMethod( - $"{opMultiInterface.Namespace}.{opMultiInterface.Name[..^2]}<{typeof(TData).FullName},{typeof(TData).FullName},{typeof(TData).FullName}>.op_Multiply", - (BindingFlags)(-1) - ); - var ordinaryMethod = typeof(TData).GetMethod("op_Multiply", (BindingFlags)(-1)); - var twoPossibleOperators = new[] { interfaceMethod, ordinaryMethod }; - var value = left.data.Zip(right.data, (x, y) => (x, y)).Select(x => - { - return (TData)twoPossibleOperators.Where(x => x is not null).First()!.Invoke(null, new object[] { x.x!, x.y! })!; - }).ToArray(); - return Vector.Create(value); - // ^^^^^^ - } - else - { - // elide details - return new TSelf(); - } + return (TData)twoPossibleOperators.Where(x => x is not null).First()!.Invoke(null, new object[] { x.x!, x.y! })!; + }).ToArray(); + return Vector.Create(value); + // ^^^^^^ } + else + { + // elide details + return new TSelf(); + } +} ``` Limited by `.NET` implementations for some value type, we need to use reflection, and the whole operation includes some sort of boxing, not quite efficient. Anyway, one thing we should notice is that we use **Factory Method** `Vector.Create(value)` to generate new instance of `TSelf`, because in generic class we are not able to call constructors with any parameter. The factory method is quite simple. Do remember dimension checking. -```csharp +```cs private static void CheckDimension(TData[] data) { if (data.GetLength(0) != Dimension) throw new Exception($"Dimension of {nameof(data)} doesn't match."); @@ -460,6 +469,7 @@ private static void CheckDimension(TData[] data) } } } + public static TSelf Create(params TData[] values) { CheckDimension(values); @@ -469,7 +479,7 @@ public static TSelf Create(params TData[] values) Finally! Let's test our new `Vector`! First we create a new derived class as `VectorOf` because we can't use `Vector` directly, we can't simply say -```csharp +```cs Vector, int, Dimension.Two>, int, Dimension.Two> vec = ...; ``` @@ -477,7 +487,7 @@ Vector, int, Dimension.Two>, int, Dimensi The only way to solve this problem is inheriting from the class with recursive type parameter. We can say -```csharp +```cs class VectorOf : Vector, TData, TDim> where TDim : IDimension, new() { } ``` @@ -485,7 +495,7 @@ class VectorOf : Vector, TData, TDim> We don't have to implement any constructor because we have got **Factory Method** `Vector.Create(params TData[])`. Now it's totally valid. -```csharp +```cs VectorOf v1 = VectorOf.Create(1.2m, 2.2m); VectorOf v2 = VectorOf.Create(1.4m, 2.3m); Console.WriteLine(v1 * v2); @@ -504,7 +514,7 @@ You may wonder, where is the adapter? You see, to actually use the recursive gen Let's start with a example. We got `CommandOpen` and `CommandClose` implement the `ICommand` interface. The `Button` class requires a `ICommand` field to perform particular behavior. The `Editor` class contains a sequence of `Button`. -```csharp +```cs interface ICommand { void Execute(); @@ -534,7 +544,7 @@ class Editor To do an adapter, we first register some type. -```csharp +```cs var builder = new ContainerBuilder(); builder.RegisterType().As(); builder.RegisterType().As(); @@ -543,7 +553,7 @@ builder.RegisterType(); Then we say -```csharp +```cs using var container = builder.Build(); var editor = container.Resolve(); ``` @@ -552,7 +562,7 @@ Now what's the status of `editor`? `Autofac` has initialized `IEnumerable