diff --git a/docs/input/docs/features.md b/docs/input/docs/features.md deleted file mode 100644 index 5356bdc..0000000 --- a/docs/input/docs/features.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -Order: 10 -Title: Features -Description: Feature overview of BBT.StrategyPattern. ---- - - diff --git a/docs/input/docs/getting-started/fundamentals.md b/docs/input/docs/getting-started/fundamentals.md new file mode 100644 index 0000000..c0414b3 --- /dev/null +++ b/docs/input/docs/getting-started/fundamentals.md @@ -0,0 +1,234 @@ +--- +Order: 30 +Title: Fundamentals of strategy pattern +Description: Apply code structure, make code readable and fulfill open/close and single responsibility principles using strategy pattern. +--- + +## Case study: The Switch Case Block + +A lot of documentation and tutorials exist explaining the implementation of the strategy pattern. +For an overview see [Strategy pattern (wikipedia)], at least the chapter of [Strategy and open/closed principle] is recommended to read. +The approach of this fundamentals article is to introduce and explain the concepts and reasons of strategy pattern based on a case study: The refactoring of a switch case block. +Many code bases contain distinction of cases implemented by switch case blocks. Those implementations have numerous issues. + +### Violation of open/close principle + +See also [Open/close principle (wikipedia)]. An appropriate class design supports the enhancement of functionality (open for extension) without changing the class itself (closed for modification). +Switch case blocks do not fulfill this principle. + +```csharp +public void Foo(FooEnum fooEnum) +{ + switch (fooEnum) + case FooEnum.Foo1: + Bar1(); + break; + case FooEnum.Foo2: + Bar2(); + break; + default: + throw new NotImplementedException(); + } +} +``` + +If the FooEnum is extended by further enum values, the switch case block must be modified as well. + +### Violation of single responsibility principle + +See also [Single responsibility principle (wikipedia)]. The switch case block from the example above does two things. + +1. A distinction of cases based on the enum values +2. The execution of the enum specific methods + +As a consequence two reasons exist to modify this method. + +1. new enum value are added +2. the enum specific method calls change + +Therefore the single responsibility principle is violated and consequently the reusability is limited. +Neither the distinction of cases nor the code within the case blocks could be used separately. + +### Maintainability + +Most likely the above mentioned distinction of cases happens in multiple code areas. +Each time the FooEnum is extended all those areas must be modified and extended as well. +As a consequence effort increases. Without appropriate test coverage code stability decreases. +This are the main reasons why distinction of cases should be centralized and separated from executing code. + +### Readability and code structure + +To fully understand the switch case block from the example above the whole block must be analyzed. Each case section can be freely implemented. +The case section has no limitations and can access all members and variables within the method scope. The code has a lack of structure. + +## Improvement: Use strategy + +As a first step the functionality of the case sections should be separated into classes. Those classes should be structured by implementing a common interface. + +```csharp +public interface IFooStrategy +{ + void Bar(); +} +``` + +This interface is implemented by each class: + +```csharp +public class Foo1Strategy : IFooStrategy +{ + public void Bar() + { + ... + } +} +``` + +Access to those strategies is provided by a StrategyProvider: + +```csharp +public class FooStrategyProvider +{ + public IFooStrategy GetStrategy(FooEnum fooEnum) + { + switch (fooEnum) + { + case FooEnum.Foo1: + return new Foo1Strategy(); + case FooEnum.Foo2: + return new Foo2Strategy(); + ... + default: + throw new NotImplementedException(); + } + } +} +``` + +Usage of the strategy within the original method: + +```csharp +public class Foo +{ + private readonly FooStrategyProvider FooStrategyProvider = new FooStrategyProvider(); + + public void Foo(FooEnum fooEnum) + { + var barStrategy = this.FooStrategyFactory.GetStrategy(fooEnum); + barStrategy.Bar(); + } +} +``` + +### Improvements of usage of strategy + +#### Single responsibility principle + +With the introduction of FooStrategyFactory the distinction of cases is moved into its own class and separated from execution logic in case blocks. +This leads to higher chance of reuse. If other areas of code also need a distinction based on FooEnum, the FooStrategyFactory can be reused. + +#### Maintainability + +Because the switch case block of FooEnum is centralized by FooStrategyFactory, maintainability and code stability is improved. +Enhancements on FooEnum affects only this single existing class (and the implementation of a new FooStrategy). + +#### Readability and code structure + +After the refactoring the original method became explicit, structured and focused on the Bar method call. + +### Issues + +#### Violation of open/closed principle + +The strategy factory still consist of the switch case block. If the FooEnum is enhanced the GetStrategy method must still be modified as well. + +#### Violation of single responsibility principle + +Besides the strategy selection the implementation of the FooStrategyFactory has another responsibility: The instantiation of the strategy instance. +This aspect should be separated and should have its own realization. There are third party libraries available for object instantiation. +Commonly used are IoC-containers. With those containers the behaviour of object lifecycle (e.g. transient, singleton) can be declared and therefore delegated to those libraries. + +## Improvement: Use generic strategy and and provider + +BBT.StrategyPattern defines an interface for declaration of strategies based on a generic type parameter. + +```csharp +public interface IGenericStrategy +{ + bool IsResponsible(T criterion); +} +``` + +By implementing IsResponsible each strategy itself defines for which criterion it is responsible. The concrete implementation of FooStrategy changes to: + +```csharp +public class Foo1Strategy : IFooStrategy +{ + public bool IsResponsible(FooEnum fooEnum) + { + return fooEnum == FooEnum.Foo1; + } + + public void Bar() + { + ... + } +} +``` + +BBT.StrategyPattern provides a generic strategy provider which can be used to resolve the specific strategy based on the strategy's IsResponsible predicate. +BBT.StrategyPattern's generic strategy provider consults the IStrategyLocator interface to get all concrete strategies implementing the concerned strategy interface. +The IStrategyLocator interface must be implemented, most commonly by using an IoC-container infrastructure. + +Using BBT.StrategyPattern the example above can be refined to: + +```csharp +public class Foo +{ + private readonly FooStrategyProvider FooStrategyProvider; + + // Constructor injection of generic strategy provider, common for IoC / dependency injection framework. + public Foo(IGenericStrategyProvider fooStrategyProvider) + { + this.FooStrategyProvider = fooStrategyProvider; + } + + public void Foo(FooEnum fooEnum) + { + var barStrategy = this.FooStrategyProvider.GetStrategy(fooEnum); + barStrategy.Bar(); + } +} +``` + +How the strategies are obtained lies in responsibility of IStrategyLocator and IoC framework. +Using ninject it could look like: + +```csharp +// IoC registrations +IKernel kernel = new StandardKernel(); + +// Generic type unbound registration of strategy locator. +// When resolved a locator object of concrete generic type is instantiated. +kernel.Bind(typeof(IStrategyLocator<>)).To(typeof(NinjectStrategyLocator<>)); + +// Bindings of the foo strategy implementations. +kernel.Bind().To(); +kernel.Bind().To(); +``` + +### Improvements of usage of generic strategy and provider + +#### Open/closed principle + +When FooEnum is enhanced, with the generic strategy provider, additional strategies can be added without modifying existing classes. +As soon as the new strategies are registered in the IoC container, the provider is extended. + +#### Single responsibility principle + +The generic provider has one single responsibility: Providing the requested strategy. How this strategy is instantiated is separated and handled be the strategy locator (i.e. IoC container). + +[Strategy pattern (wikipedia)]: https://en.wikipedia.org/wiki/Strategy_pattern +[Strategy and open/closed principle]: https://en.wikipedia.org/wiki/Strategy_pattern#Strategy_and_open.2Fclosed_principle +[Open/close principle (wikipedia)]: https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle +[Single responsibility principle (wikipedia)]: https://en.wikipedia.org/wiki/Single_responsibility_principle \ No newline at end of file diff --git a/docs/input/docs/getting-started/index.cshtml b/docs/input/docs/getting-started/index.cshtml new file mode 100644 index 0000000..15d780d --- /dev/null +++ b/docs/input/docs/getting-started/index.cshtml @@ -0,0 +1,7 @@ +--- +Order: 10 +Description: Overview about the features of BBT.Maybe. +--- +

@Html.Raw(Model.String(DocsKeys.Description))

+ +@Html.Partial("_ChildPages") \ No newline at end of file diff --git a/docs/input/docs/getting-started/principles.md b/docs/input/docs/getting-started/principles.md new file mode 100644 index 0000000..9cb1847 --- /dev/null +++ b/docs/input/docs/getting-started/principles.md @@ -0,0 +1,16 @@ +--- +Order: 20 +Title: Core principles +Description: Core principles of BBT.StrategyPattern. +--- + +Goal of this infrastructure is to support the providing of specific strategies achieved by usage of generics. +This infrastructure introduces a generic strategy interface managed by generic strategy provider. +The generic parameter acts as selection criterion and declares the responsibility of the specific strategy. +The generic strategy interface leads to more structured code. +Based on its generic type parameter, strategies are categorized and make their responsibility explicit. +As a consequence this leads to higher chance of reuse and better readable code. +The implementation is driven by SOLID principles like open/closed, single responsibility and dependency inversion. +For further explanations see [Fundamentals of strategy pattern] + +[Fundamentals of strategy pattern]: ./fundamentals \ No newline at end of file diff --git a/docs/input/docs/getting-started/whystrategypattern.md b/docs/input/docs/getting-started/whystrategypattern.md new file mode 100644 index 0000000..9a13f19 --- /dev/null +++ b/docs/input/docs/getting-started/whystrategypattern.md @@ -0,0 +1,22 @@ +--- +Order: 10 +Title: Why does BBT.StrategyPattern exist? +Description: Introduction to BBT.StrategyPattern, what problems are solved and how it is distinctive from other implementations of strategy pattern. +--- + +## Introduce structure to your code by using BBT.StrategyPattern + +BBT.StrategyPattern is an implementation of the strategy design pattern. + +BBT.StrategyPattern has the following goals: + +* Support the usage of strategies with no need to implement specific strategy factories +* Provide a generic strategy provider based on generics +* Introduce strategy categorization based on generic parameter + +Implementation details of BBT.StrategyPattern are: + +* Introduces a generic strategy interface +* The generic parameter represents the criterion for the selection of the specific strategy +* Works with any IoC framework +* Supports generic type unbound IoC registrations diff --git a/docs/input/docs/usage/obtain.md b/docs/input/docs/usage/obtain.md new file mode 100644 index 0000000..4ea4c5e --- /dev/null +++ b/docs/input/docs/usage/obtain.md @@ -0,0 +1,9 @@ +--- +Order: 10 +Title: Obtain +Description: Obtain BBT.StrategyPattern +--- + +## Nuget + +> `nuget install bbt.strategypattern` diff --git a/docs/input/docs/usage/use-with-ioc.md b/docs/input/docs/usage/use-with-ioc.md index f606231..612d6c8 100644 --- a/docs/input/docs/usage/use-with-ioc.md +++ b/docs/input/docs/usage/use-with-ioc.md @@ -1,5 +1,5 @@ --- -Order: 20 +Order: 30 Title: Use with IoC Description: Example how to use BBT.StrategyPattern with IoC. --- @@ -13,9 +13,7 @@ public void WorksWithIocNinject() IKernel kernel = new StandardKernel(); - kernel - .Bind>() - .ToMethod((con) => new NinjectStrategyLocator(kernel)); + kernel.Bind(typeof(IStrategyLocator<>)).To(typeof(NinjectStrategyLocator<>)); kernel .Bind() diff --git a/docs/input/docs/usage/use-without-ioc.md b/docs/input/docs/usage/use-without-ioc.md index 2a7fc8a..8c2f1a7 100644 --- a/docs/input/docs/usage/use-without-ioc.md +++ b/docs/input/docs/usage/use-without-ioc.md @@ -1,5 +1,5 @@ --- -Order: 10 +Order: 20 Title: Use without IoC Description: Example how to use BBT.StrategyPattern without IoC. ---