Design pattern routine, conclusion.
Today, write the last part of the design pattern routine: behavioral design patterns.
This is the last article in this series. The first two articles are:
C# design pattern routine worthy of permanent collection (I)
C# design pattern routine worthy of permanent collection (II)
If you haven't read the first two articles, it is recommended to read the first two articles first, which are about the routines of creative design and structural design.
The behavior design pattern is different from the first two patterns in content. Behavior design patterns pay more attention to the communication between objects and the interaction of responsibilities and tasks.
1, Chain of responsibility
The name is clearly a chain of responsibilities or tasks. Why chain? This is because the request is passed back along multiple handlers. A task may be divided into many steps, but you don't want to couple all the steps into one handler, so you will use this routine.
Look at the code:
public interface IHandler { public IHandler SetNext(IHandler handler); public object Handle(object input); } public class Handler : IHandler { private IHandler _handler; public IHandler SetNext(IHandler handler) { _handler = handler; return handler; } public virtual object Handle(object input) { return _handler?.Handle(input); } } public class HandlerA : Handler { public override object Handle(object input) { if (input as string == "A") { Console.WriteLine("HandlerA : just return"); return true; } Console.WriteLine("HandlerA : call next handler"); return base.Handle(input); } } public class HandlerB : Handler { public override object Handle(object input) { if (input as string == "B") { Console.WriteLine("HandlerB : just return"); return true; } Console.WriteLine("HandlerB : call next handler"); return base.Handle(input); } } public class HandlerC : Handler { public override object Handle(object input) { if (input as string == "C") { Console.WriteLine("HandlerC : just return"); return true; } Console.WriteLine("HandlerC : end"); return base.Handle(input); } } public static class Example { public static void Test() { var handlerA = new HandlerA(); var handlerB = new HandlerB(); var handlerC = new HandlerC(); handlerA.SetNext(handlerB).SetNext(handlerC); var resultOne = handlerA.Handle("A"); var resultTwo = handlerA.Handle("B"); var resultThree = handlerA.Handle("C"); var resultFour = handlerA.Handle("D"); } // results A: // HandlerA : just return // results B: // HandlerA : call next handler // HandlerB : just return // results C: // HandlerA : call next handler // HandlerB : call next handler // HandlerC : just return // results D: // HandlerA : call next handler // HandlerB : call next handler // HandlerC : end }
In this, the important sentence is handler a. setnext (handler b). Setnext (handler C). This defines the direction and content of the chain. If you can understand this layer, even if you really understand it.
2, Command
There are a lot of online content, such as Command, which is usually said together with Delegate and Event.
Let's just talk about this command mode.
Command mode is a very common mode. Its function is to convert requests into objects so that we can asynchronously, delay, queue or parameterize requests, and do some revocable work.
The code routine is particularly simple:
public interface ICommand { public void Execute(); } public class DemoCommand : ICommand { private readonly string _parameter; public DemoCommand(string parameter) { _parameter = parameter; } public void Execute() { Console.WriteLine(_parameter); } } public static class Invoker { public static void SendAction(ICommand command) { command.Execute(); } } public static class Example { public static void Test() { var command = new DemoCommand("Hello WangPlus"); Invoker.SendAction(command); } // results: // Hello WangPlus }
There are many application scenarios for this Command. We recommend that you understand it thoroughly. When we do SDK or class library, we often implement business logic in the class library, and the caller uses the Command mode to realize data interaction. For example, make a class library for authentication and authorization, which realizes authentication and generates Token, and the caller judges the verification of login account password. In this way, the library can be a general library independent of the database and account system.
3, Iterator
This is also a much used model.
Its main function is to traverse the elements of the set; The most important feature is that the data itself will not be exposed.
Look at the code:
public abstract class IteratorBase { public abstract bool EndOfDocument(); public abstract object Next(); public abstract object First(); public abstract object Current(); } public class Iterator : IteratorBase { private readonly List<object> _customList; private int current = 0; public Iterator(List<object> customList) { _customList = customList; } public override bool EndOfDocument() { if (current >= _customList.Count - 1) return true; return false; } public override object Current() { return _customList[current]; } public override object Next() { if (current < _customList.Count - 1) return _customList[++current]; return null; } public override object First() { return _customList[0]; } } public static class Example { public static void Test() { var demolist = new List<object>() { "a", "b", "c", "d" }; var iterator = new Iterator(demolist); var item = iterator.First(); while (item != null) { Console.WriteLine(item); item = iterator.Next(); } if (iterator.EndOfDocument()) Console.WriteLine("Iterate done"); } //results: // a // b // c // d // Iterate done }
If you want to understand the principle of iterators, asynchrony and deeper applications, you can check out my article on iterators An asynchronous iterator in C # in Yiwen Shutong
4, Interpreter
This is also a common pattern in behavior patterns. It mainly obtains different types and behaviors according to context. In other words, the same content takes different actions for different types.
Usually, this mode is most used in multilingual scenes.
public class Context { public string Value { get; private set; } public Context(string value) { Value = value; } } public abstract class Interpreter { public abstract void Interpret(Context context); } public class EnglishInterpreter : Interpreter { public override void Interpret(Context context) { switch (context.Value) { case "1": Console.WriteLine("One"); break; case "2": Console.WriteLine("Two"); break; } } } public class ChineseInterpreter : Interpreter { public override void Interpret(Context context) { switch (context.Value) { case "1": Console.WriteLine("one"); break; case "2": Console.WriteLine("two"); break; } } } public static class Example { public static void Test() { var interpreters = new List<Interpreter>() { new EnglishInterpreter(), new ChineseInterpreter() }; var context = new Context("2"); interpreters.ForEach(c => c.Interpret(context)); } // results: // two // two }
The above example is the standard routine of the interpreter. Usually, when we use it, we can cooperate with the abstract factory mode to load a single interpreter independently according to the context, so as to realize the code similar to displaying the interface language according to the browser's setting language.
If it is implemented with Microsoft's standard library, the interpreter and abstract factory have been packaged in the library. When using, you only need to define the language comparison table. But the logic inside is still this.
5, Intermediary
Note that it is an intermediary, not a middleware. These are two things. Don't mix them up.
However, the two principles are somewhat similar. The purpose of mediation mode is to decouple the direct communication between objects and transfer messages from mediation objects.
Also look at the code:
public interface IMediator { public void Send(string message, Caller caller); } public class Mediator : IMediator { public CallerA CallerA { get; set; } public CallerB CallerB { get; set; } public void Send(string message, Caller caller) { if (caller.GetType() == typeof(CallerA)) { CallerB.ReceiveRequest(message); } else { CallerA.ReceiveRequest(message); } } } public abstract class Caller { protected readonly IMediator _mediator; public Caller(IMediator mediator) { _mediator = mediator; } } public class CallerA : Caller { public void SendRequest(string msg) { _mediator.Send(msg, this); } public void ReceiveRequest(string msg) { Console.WriteLine("CallerA Received : " + msg); } public CallerA(IMediator mediator) : base(mediator) { } } public class CallerB : Caller { public void SendRequest(string msg) { _mediator.Send(msg, this); } public void ReceiveRequest(string msg) { Console.WriteLine("CallerB Received : " + msg); } public CallerB(IMediator mediator) : base(mediator) { } } public static class Example { public static void Test() { var mediator = new Mediator(); var callerA = new CallerA(mediator); var callerB = new CallerB(mediator); mediator.CallerA = callerA; mediator.CallerB = callerB; callerA.SendRequest("Hello"); callerB.SendRequest("WangPlus"); } // results: // CallerB Received : Hello // CallerA Received : WangPlus }
There is no direct communication between CallerA and CallerB, but the message is delivered through the intermediary Mediator.
In this mode, the most common scenario is to realize communication between two ready-made class libraries. If you don't want or can't modify the code of the two class libraries, you can make a mediation library for data transmission.
6, Memorandum
Same name. Memo mode is mainly used to save the state of the object and encapsulate the state so that it can be restored to the previous state when necessary.
The routine is like this:
public class Memento { private readonly string _state; public Memento(string state) { _state = state; } public string GetState() { return _state; } } public class Originator { public string State { get; set; } public Originator(string state) { State = state; } public Memento CreateMemento() { return new Memento(State); } public void RestoreState(Memento memento) { State = memento.GetState(); } } public class Taker { private Memento _memento; public void SaveMemento(Originator originator) { _memento = originator.CreateMemento(); } public void RestoreMemento(Originator originator) { originator.RestoreState(_memento); } } public static class Example { public static void Test() { var originator = new Originator("First State"); var careTaker = new Taker(); careTaker.SaveMemento(originator); Console.WriteLine(originator.State); originator.State = "Second State"; Console.WriteLine(originator.State); careTaker.RestoreMemento(originator); Console.WriteLine(originator.State); } // results: // First State // Second State // First State }
This code looks complex, but the core is one point: save the state outside the object before changing the state.
You're fine.
7, Observer
Observer mode mainly deals with one to many communication between objects. If the state of an object changes, the dependent object will notify and update.
public class Updater { public string NewState { get; } private readonly List<ObserverBase> _observers = new List<ObserverBase>(); public Updater(string newState) { NewState = newState; } public void AddObserver(ObserverBase observerBase) { _observers.Add(observerBase); } public void BroadCast() { foreach (var observer in _observers) { observer.Update(); } } } public abstract class ObserverBase { public abstract void Update(); } public class Observer : ObserverBase { private readonly string _name; public string State; private readonly Updater _updater; public Observer(string name, string state, Updater updater) { _name = name; State = state; _updater = updater; } public override void Update() { State = _updater.NewState; Console.WriteLine($"Observer {_name} State Changed to : " + State); } } public static class Example { public static void Test() { var updater = new Updater("WangPlus"); updater.AddObserver(new Observer("1", "WangPlus1", updater)); updater.AddObserver(new Observer("2", "WangPlus2", updater)); updater.AddObserver(new Observer("3", "WangPlus3", updater)); updater.BroadCast(); } // results: // Observer 1 State Changed to : WangPlus // Observer 2 State Changed to : WangPlus // Observer 3 State Changed to : WangPlus }
Well, this code is a bit like the memo mode code above. In fact, the main difference between the two modes is one-to-one and one to many.
As for why the names of the two modes are so different, to be honest, I don't know. In my concept, these two modes can be mixed. In terms of experience, I use memo mode more.
8, Status
State mode is also a common mode.
The status mode is similar to the previous responsibility chain mode. The state mode is more direct, that is, when the state changes, the behavior changes synchronously.
These two modes can effectively reduce the number of branches of if... else in many cases. So, it looks tall again:)
Previous routine:
public interface IState { public void Handle(Context context); } public class StateA : IState { public void Handle(Context context) { context.State = new StateB(); } } public class StateB : IState { public void Handle(Context context) { context.State = new StateA(); } } public class Context { private IState _state; public IState State { get => _state; set { _state = value; Console.WriteLine("State: " + _state.GetType().Name); } } public Context(IState state) { State = state; } public void Action() { State.Handle(this); } } public static class Example { public static void Test() { var context = new Context(new StateA()); context.Action(); context.Action(); context.Action(); context.Action(); } // results: // State: StateA // State: StateB // State: StateA // State: StateB // State: StateA }
do you understand?
If the IState inside is replaced by IHandler, it is a responsibility chain. The difference is that one is data and the other is method, except that they are the same.
Therefore, I always say: don't care about the name, but about the essence. In modern development, data, methods and objects are actually tending to be unified. If you understand this, you will pass.
9, Tactics
Policy patterns are mainly used to encapsulate algorithm families and make them interchangeable, so they can be changed independently without any close coupling.
Yes, it is another architecture for decoupling.
public interface IStrategy { public void AlgorithmAction(); } public class AlgorithmStrategyA : IStrategy { public void AlgorithmAction() { Console.WriteLine("This is Algorithm A"); } } public class AlgorithmStrategyB : IStrategy { public void AlgorithmAction() { Console.WriteLine("This is Algorithm B"); } } public class Context { private readonly IStrategy _strategy; public Context(IStrategy strategy) { _strategy = strategy; } public void GeneralAction() { _strategy.AlgorithmAction(); } } public static class Example { public static void Test() { var context = new Context(new AlgorithmStrategyA()); context.GeneralAction(); context = new Context(new AlgorithmStrategyB()); context.GeneralAction(); } // results: // This is Algorithm A // This is Algorithm A }
The core of this pattern is the IStrategy in the context, which itself is an abstraction, and the implementation of the algorithm is in the entity corresponding to this abstraction.
A standard model.
10, Template
Template pattern is a basic pattern of object-oriented, and it is also widely used.
This pattern is similar to the abstract factory pattern. It establishes the overall operation structure on the base class and rewrites some operations in the subclass according to the changes of requirements.
It sounds very complicated. In fact, the code can be understood at a glance:
public abstract class TemplateBase { public void Operate() { FirstAction(); SecondAction(); } private void FirstAction() { Console.WriteLine("First action from template base"); } protected virtual void SecondAction() { Console.WriteLine("Second action from template base"); } } public class TemplateA : TemplateBase { } public class TemplateB : TemplateBase { protected override void SecondAction() { Console.WriteLine("Second action from template B"); } } public class TemplateC : TemplateBase { protected override void SecondAction() { Console.WriteLine("Second action from template B"); } } public static class Example { public static void Test() { var templateMethodA = new TemplateA(); var templateMethodB = new TemplateB(); var templateMethodC = new TemplateC(); templateMethodA.Operate(); templateMethodB.Operate(); templateMethodC.Operate(); } // results: // First action from template base // Second action from template base // First action from template base // Second action from template B // First action from template base // Second action from template B }
It's simple, isn't it?
11, Visitor
Visitor mode is a variation of the above template mode. The same purpose is to add new methods and behaviors to the hierarchy of subclasses without modifying the base class code.
Look at the code:
public interface IVisitor { public void VisitItem(ItemBase item); } public class VisitorA : IVisitor { public void VisitItem(ItemBase item) { Console.WriteLine("{0} visited by {1}", item.GetType().Name, this.GetType().Name); } } public class VisitorB : IVisitor { public void VisitItem(ItemBase item) { Console.WriteLine("{0} visited by {1}", item.GetType().Name, this.GetType().Name); } } public abstract class ItemBase { public abstract void Accept(IVisitor visitor); } public class ItemA : ItemBase { public override void Accept(IVisitor visitor) { visitor.VisitItem(this); } public void ExtraOperationA() { } } public class ItemB : ItemBase { public override void Accept(IVisitor visitor) { visitor.VisitItem(this); } public void ExtraOperationB() { } } public class StructureBuilder { readonly List<ItemBase> _items = new List<ItemBase>(); public void AddItem(ItemBase element) { _items.Add(element); } public void Accept(IVisitor visitor) { foreach (var item in _items) { item.Accept(visitor); } } } public static class Example { public static void Test() { var structure = new StructureBuilder(); structure.AddItem(new ItemA()); structure.AddItem(new ItemB()); structure.Accept(new VisitorA()); structure.Accept(new VisitorB()); } //results: //ItemA visited by VisitorA //ItemB visited by VisitorA //ItemA visited by VisitorB //ItemB visited by VisitorB }
Visitor mode extends the template mode, which is more extensible, reusable and flexible, and embodies the principle of single responsibility.
12, Summary
It took three articles to finish the pattern routine.
Do you feel it? All modes are for decoupling. The purpose of decoupling is to divide a system into finer components. Subdivision components are more suitable for the development of large teams. The technical structure of a large team is easier to be reflected in various online articles.
Therefore, let me remind you that all architectures need to be familiar with and mastered, which is called the necessary knowledge for interview. In practical application, if you are familiar with the architecture, all programs will look more logical. If you are not familiar with the architecture, it will be a nightmare to look at the code written using the architecture.
Growth lies in a little accumulation, which is the same for everyone and me.