1024 Thoughts after reading

After reading Design Patterns, I realized that many previous project codes could be optimized and streamlined. It can also provide more inspiration for a layman like me in other projects. However, I only skimmed through design patterns 21~28 later. I will supplement them specifically when applying them in the future.
I plan to try the first few design patterns in future projects or use design patterns to optimize previous projects at the end of the year, practicing to deepen my impression.

Object-Oriented Design

Three Basic Characteristics

  1. Encapsulation
  2. Inheritance
  3. Polymorphism
    1. Eliminate coupling between types
    2. Substitutability
    3. Extensibility
    4. Interface-ability
    5. Flexibility
    6. Simplicity

Chapter 1 Simple Factory Pattern

Implementation

Question: Let the user input specified values and calculation method through a console program to calculate the value.
My first thought would be to build four classes representing ‘+’, ‘-‘, ‘*’, ‘/‘ respectively, and calculate separately by calling four methods. However, this way would make main appear very bloated. By inheriting classes, overriding methods, and placing methods in separate calculation classes, the whole code will look more organized.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Please enter two number: ");
Console.WriteLine("The first Number: ");
string numberA = Console.ReadLine();
Console.WriteLine("The Second Number: ");
string numberB = Console.ReadLine();
double doubleA, doubleB;
if(double.TryParse(numberA,out doubleA)&&double.TryParse(numberB,out doubleB))
{
Console.WriteLine("Please enter your operation : '+' '-'");
string o = Console.ReadLine();
var operation = GetOperation(o);
operation.NumberA = doubleA;
operation.NumberB = doubleB;
Console.WriteLine($"{operation.GetResult()}");
}
else
{
throw new InvalidOperationException();
}


}
public static Operation GetOperation(string operation)
{
Operation oper = null;
switch (operation)
{
case "+":
oper = new OperationAdd();
break;
case "-":
oper = new OperationSub();
break;

}
return oper;
}
}
public class Operation
{
private double _numberA = 0;
private double _numberB = 0;
public double NumberA
{
get => _numberA;
set { _numberA = value; }
}
public double NumberB
{
get => _numberB;
set { _numberB = value; }
}
public virtual double GetResult()
{
double result = 0;
return result;
}
}
class OperationAdd : Operation
{
public override double GetResult()
{
return NumberA + NumberB;
}
}
class OperationSub : Operation
{
public override double GetResult()
{
return NumberA - NumberB;
}
}

Chapter 2 Strategy Pattern

Understanding

Strategy Pattern and Factory Pattern both separate algorithms through an intermediate class to reduce coupling between functions. When adding or modifying functions later, it minimizes the impact on other algorithms. The Strategy Pattern focuses more on separating algorithm strategies from results. Since I’m new to design patterns, the underlying logic of both seems the same to me at present, just with some differences in implementation.
Strategy Pattern (Strategy): It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from clients that use it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#region Strategy Pattern
abstract class Strategy
{
public abstract void AlgorithmInterface();
}

class ConcreteStrategyA:Strategy
{
public override void AlgorithmInterface()
{
Console.WriteLine("Algorithm A implementation!");
}
}

class ConcreteStrategyB:Strategy
{
public override void AlgorithmInterface()
{
Console.WriteLine("Algorithm B implementation!");
}
}

class ConcreteStrategyC:Strategy
{
public override void AlgorithmInterface()
{
Console.WriteLine("Algorithm C implementation!");
}
}

class Context
{
private Strategy _strategy;

public Context(Strategy strategy)
{
this._strategy = strategy;
}

public void ContextInterface()
{
_strategy.AlgorithmInterface();
}

}

#endregion

Combining Factory Pattern with Strategy Pattern

Following the case in the book effectively reduces judgments in the main window, logic can be displayed in the Context class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#region Factory Pattern

class CashFactory
{
private static CashSuper ce;

public CashFactory(string type)
{
ce = null;
switch (type)
{
case "Normal Charge":
ce = new CashNormal();
break;
case "300 minus 30":
ce = new CashReturn("300","30");
break;
case "20% off":
ce = new CashRebate("0.8");
break;
}
}

public double ConTextInterface(double money)
{
return ce.AcceptCash(money);
}
}

class CashContext
{
private CashSuper _cashSuper;

public CashContext(CashSuper cashSuper)
{
_cashSuper = cashSuper;
}

public double ContextInterface(double money)
{
return _cashSuper.AcceptCash(money);
}


}
//Abstract Class
abstract class CashSuper
{
public abstract double AcceptCash(double money);
}
/// <summary>
/// Normal Charge
/// </summary>
class CashNormal:CashSuper
{
public override double AcceptCash(double money)
{
Console.WriteLine("Charge: {0}",money);
return money;
}
}
/// <summary>
/// Charge at discount
/// </summary>
class CashRebate:CashSuper
{
private double moneyRebate = 1d;
/// <summary>
/// Input discount rate
/// </summary>
/// <param name="moneyRebate"></param>
public CashRebate(String moneyRebate)
{
this.moneyRebate = double.Parse(moneyRebate);
}

public override double AcceptCash(double money)
{
Console.WriteLine("Charge:{0} Rate:{1}",money * moneyRebate,moneyRebate);
return money * moneyRebate;
}
}
/// <summary>
/// Rebate (300-30)
/// </summary>
class CashReturn:CashSuper
{
private double returnMoney = 0.0d;//Rebate amount
private double targetMoney = 0.0d;//Threshold amount

public CashReturn(string target,string re)
{
this.returnMoney = double.Parse(re);
this.targetMoney = double.Parse(target);
}
public override double AcceptCash(double money)
{
if (money>=targetMoney)//User meets rebate condition
{
double count = money / targetMoney;
double m = money - Math.Floor(count) * returnMoney;
Console.WriteLine("Charge:{0} ,Promotion is {1} minus {2}",m,targetMoney,returnMoney);
return m;
}
else//Did not meet rebate condition
{
return money;
}
}
}

#endregion


Single Responsibility Principle

Single Responsibility Principle (SRP): As for a class, there should be only one reason for it to change.

Open-Closed Principle

Open-Closed Principle: Software entities (classes, modules, functions, etc.) should be extensible but not modifiable.

  1. Open for extension
  2. Closed for modification

Inappropriate example: In the example, the abstract class is open, the implemented class is closed and has only one responsibility.

Dependency Inversion Principle

Dependency Inversion Principle:

  1. High-level modules should not depend on low-level modules; both should depend on abstractions.
  2. Abstractions should not depend on details; details should depend on abstractions.
    Liskov Substitution Principle [LSP]: Subtypes must be able to replace their parent types.
    If a software entity uses a parent class, it must be applicable to its subclasses without detecting the difference between the parent and child classes. In other words, if all parent classes are replaced by subclasses in the software entity, the behavior of the program does not change.

Chapter 6 Decorator Pattern

The Decorator Pattern uses SetComponent to wrap objects, so that the implementation of each decorative object is separated from how to use that object. Each decorative object only cares about its own function and does not need to care about how it is added to the object chain.
In my opinion, compared with the Factory Pattern and Strategy Pattern mentioned before, this reduces coupling. It not only keeps large modules from disturbing each other but also establishes isolation between small methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
      abstract class Component
{
public abstract void Operation();
}
class ConcreteComponent : Component
{
public override void Operation()
{
Console.WriteLine("Operation of specific object!");
}
}
abstract class Decorator : Component
{
protected Component _component;

public void SetCompont(Component component)
{
_component = component;
}

public override void Operation()
{
if (_component != null)
{
_component.Operation();
}
}
}
class ConcreteDecoratorA : Decorator
{
public string AddStates;
public override void Operation()
{
base.Operation();
AddStates = "New States";
Console.WriteLine("Operation method of specific object A");
}
}

class ConcreteDeciratorB : Decorator
{
public void AddBehavior()
{
Console.WriteLine("behavor!!!");
}
public override void Operation()
{
base.Operation();
AddBehavior();
Console.WriteLine("Implementation method of specific object B!");
}

}


Clothes decoration in the book

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
 static void Main(string[] args)
{
//ConcreteComponent concrete = new ConcreteComponent();
//ConcreteDecoratorA decoratorA = new ConcreteDecoratorA();
//ConcreteDeciratorB deciratorB = new ConcreteDeciratorB();
//decoratorA.SetCompont(concrete);
//deciratorB.SetCompont(decoratorA);
//deciratorB.Operation();
//Console.Read();
Person person = new Person("Xiao Cai");
TShirts shirts = new TShirts();
BigTrousers bigTrousers = new BigTrousers();
shirts.Decorate(person);
bigTrousers.Decorate(shirts);
bigTrousers.Show();
Console.Read();

}
/// <summary>
/// Person
/// </summary>
class Person
{
public Person() { }

private string name;
public Person(string name)
{
this.name = name;
}
public virtual void Show() //Virtual class, can rely on implementation, but class must have brackets {}
{
Console.WriteLine("{0}'s outfit is:", this.name);
}
}

class Finery : Person
{
protected Person person;

public void Decorate(Person component)
{
person = component;
}
public override void Show()
{
if (person != null)
{
person.Show();
}
}
}
class TShirts : Finery
{
public override void Show()
{
base.Show();
Console.WriteLine("T-Shirt");

}
}
class BigTrousers : Finery
{
public override void Show()
{
base.Show();
Console.WriteLine("Big Trousers");

}
}

Proxy Pattern

Proxy Pattern (Proxy) provides a surrogate or placeholder for another object to control access to it.
Application:

  1. Remote Proxy: provides a local representative for an object in a different address space. This can hide the fact that an object exists in a different address space.
  2. Virtual Proxy: creates expensive objects on demand. It uses this to hold real objects that take a long time to instantiate.
  3. Protection Proxy: controls access permissions to the real object.
  4. Smart Reference: performs additional actions when the real object is accessed.

The previous Simple Factory Pattern, Strategy Pattern, and Decorator Pattern are all implementation patterns under combined matching of multiple functions. By reducing coupling, they achieve extensibility and protect the original class.
Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 static void Main(string[] args)
{
RealSubject realSubject = new RealSubject();
Proxy proxy = new Proxy(realSubject);
proxy.Request();

}
abstract class Subject
{
public abstract void Request();//Abstract class, no content needed in class, must be forced to override
}
class RealSubject : Subject
{
public override void Request()
{
Console.WriteLine("Real Request!!!");
}
}

class Proxy : Subject
{
RealSubject realSubject;
public Proxy(RealSubject realSubject) => this.realSubject = realSubject;
public override void Request()
{
if (realSubject != null)
{
realSubject.Request();
}
}
}

Factory Pattern

Factory Method Pattern (Factory Method): Defines an interface for creating objects, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Code Example, modified the Simple Factory Method, created an interface, and created method classes separately by inheriting the interface

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
static void Main(string[] args)
{
/*
* Use Factory Method. When adding new features later, only one step needs to be modified during use.
* This is different from the Simple Factory Method, which requires adding a calculation class and adding options in the factory method.
*/
IOperation iOper = new Factory.SubOperationFactory();
Operation operation = iOper.CreatOperation();
operation.Num1 = 1;
operation.Num2 = 2;
var result = operation.GetResult();
Console.WriteLine(result);

Console.Read();
}
/*
* Factory Pattern, build a factory method for each class
* Open-Closed Principle: Open for extension, closed for modification
* Compared to Simple Factory Pattern, if adding a solution requires modifying the factory class
* It violates the Open-Closed Principle. Using Factory Method when there are large changes later
* is superior to Simple Factory Method
*/
abstract class Operation
{
private double _num1;
private double _num2;

public double Num1
{
get => _num1;
set => _num1 = value;
}

public double Num2
{
get => _num2;
set => _num2 = value;
}

public abstract double GetResult();
}

class AddOperation:Operation
{
public override double GetResult()
{
return Num1 + Num2;
}
}

class SubOperation:Operation
{
public override double GetResult()
{
return Num1 - Num2;
}
}
interface IOperation
{
Operation CreatOperation();
}
class Factory
{
public class AddOperationFactory:IOperation
{
public Operation CreatOperation()
{
Operation operation = new AddOperation();
return operation;
}
}
public class SubOperationFactory:IOperation
{
public Operation CreatOperation()
{
Operation operation = new SubOperation();
return operation;
}
}

}

Prototype Pattern

Prototype Pattern (Prototype): Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
Prototype Pattern is actually creating another create-able object from one object, without needing to know any creation details.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
static void Main(string[] args)
{
Resume resume1 = new Resume("Xiao Ming");
resume1.SetAgeAndSex("Male","23");
resume1.SetWorkInformation("2020-10-08","A");
Resume resume2 = (Resume) resume1.Clone();
resume2.SetWorkInformation("2020-10-08","B");
Console.WriteLine("resume1's workInformation:workDate={0},company={1}",resume1.WorkInformation.WorkDate,resume1.WorkInformation.Company);
Console.WriteLine("resume2's workInformation:workDate={0},company={1}",resume2.WorkInformation.WorkDate,resume2.WorkInformation.Company);

Console.ReadLine();
}
/*
* Need to understand shallow copy, value copy and reference copy
* .NET implements cloning by referencing ICloneable
*/
abstract class Prototype
{
private string id;

public string Id
{
get => id;
set => id = value;
}

public Prototype(string id)
{
this.id = id;
}

public abstract Prototype Clone();

}

class ConcretePrototype:Prototype
{
public ConcretePrototype(string id) : base(id)
{
}

public override Prototype Clone()
{
return (Prototype) this.MemberwiseClone();//If it is a value type, bitwise copy; if it is a reference type, reference is copied but object is not copied
}
}

class WorkInformation:ICloneable
{
private string workDate;
private string company;

public string WorkDate
{
get => workDate;
set => workDate = value;
}

public string Company
{
get => company;
set => company = value;
}

public object Clone()
{
return (object)this.MemberwiseClone();
}
}

class Resume:ICloneable
{
private string name;
private string sex;
private string age;
private WorkInformation _workInformation;

public Resume(string name)
{
this.name = name;
_workInformation = new WorkInformation();
}

private Resume(WorkInformation workInformation)
{
_workInformation = (ConsoleApp.WorkInformation) workInformation.Clone();
}

public void SetAgeAndSex(string sex, string age)
{
this.age = age;
this.sex = sex;
}

public void SetWorkInformation(string workDate, string company)
{
_workInformation.Company = company;
_workInformation.WorkDate = workDate;
}

public WorkInformation WorkInformation => _workInformation;

public object Clone()
{
Resume resume = new Resume(this._workInformation);
resume.age = this.age;
resume.sex = this.sex;
resume.name = this.name;
return resume;
}
}

Here workInformation undergoes Deep Copy: Variable of reference object points to the newly copied object. Rather than the original referenced object.

1
2
3
4
5
6
7
8
9
10
11
12
private Resume(WorkInformation workInformation)
{
_workInformation = (ConsoleApp.WorkInformation) workInformation.Clone();
}
public object Clone()
{
Resume resume = new Resume(this._workInformation);
resume.age = this.age;
resume.sex = this.sex;
resume.name = this.name;
return resume;
}

I wanted to compare the reference addresses between the two, but since C# requires writing class methods for this, I didn’t verify it further. However, this should be the same as the definition of value reference in C++. The copied value pointer points to its address, so subsequent values will be overwritten. Accessing it requires creating a new memory address to store the corresponding value. (I will verify this when I write programs using C++)


Template Method Pattern

Template Method Pattern: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
Abstract methods need to define an abstract class and write some content that does not need to be changed, such as the test questions in the example below. Modify the parts that need to be changed to virtual classes and modify them, such as: the answers in the example below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static void Main(string[] args)
{
TestPaperA tpa = new TestPaperA();
tpa.TestQuestion1();
TestPaperB tpb = new TestPaperB();
tpb.TestQuestion1();
Console.ReadLine();
}
public class TemplateMethod
{
public void TestQuestion1()
{
Console.WriteLine("The answer to the first question is: A:a, B:b, C:c, D:d");
Console.WriteLine("answer:{0}",Answer1());
}

protected virtual string Answer1()
{
return "";
}

}

class TestPaperA:TemplateMethod
{
protected override string Answer1()
{
return "A";
}
}

class TestPaperB:TemplateMethod
{
protected override string Answer1()
{
return "C";
}
}


Law of Demeter

Law of Demeter (LoD), also known as: Least Knowledge Principle: If two classes do not need to communicate directly with each other, then the two classes should not interact directly. If one class needs to call a method of another class, it can forward the call through a third party.

Facade Pattern (Facade)

Facade Pattern (Facade): Provide a unified interface to a set of interfaces in a system. This pattern defines a high-level interface that makes the subsystem easier to use.


This design pattern is suitable for a program where the early stage is unclear for the later stage, or where new methods need to be expanded beyond the original important methods in the later stage

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Facade
Facade facade = new Facade();
facade.MethodA();
public class Facade
{
private SubSymbolOne _symbolOne;
private SubSymbolTwo _symbolTwo;
private SubSymbolThree _symbolThree;
private SubSymbolFour _symbolFour;
public Facade()
{
_symbolOne = new SubSymbolOne();
_symbolTwo = new SubSymbolTwo();
_symbolThree = new SubSymbolThree();
_symbolFour = new SubSymbolFour();
}

public void MethodA()
{
_symbolOne.MethodOne();
_symbolTwo.MethodTwo();
_symbolFour.MethodFour();
}

public void MethodB()
{
_symbolThree.MethodThree();
}
}

class SubSymbolOne
{
public void MethodOne()
{
Console.WriteLine("Subsystem Method One");
}
}

class SubSymbolTwo
{
public void MethodTwo()
{
Console.WriteLine("Subsystem Method Two");
}
}

class SubSymbolThree
{
public void MethodThree() => Console.WriteLine("Subsystem Method Three");
}

class SubSymbolFour
{
public void MethodFour() => Console.WriteLine("Subsystem Method Four");
}

Builder Pattern (Builder)

Builder Pattern (Builder) separates the construction of a complex object from its representation so that the same construction process can create different representations.

If applied to the civil engineering field, it can be understood as follows: Structural models, whether calculation models, BIM models, or other 3D models, can be divided into four major categories: walls, beams, slabs, and columns. By creating separate creation methods for each category in each format model and controlling the generation of all or individual categories through a final controller.

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

ConcreteBuilder1 builder1 = new ConcreteBuilder1();//Builder 1
ConcreteBuilder2 builder2 = new ConcreteBuilder2();//Builder 2
Director dir= new Director();//Initialize Director
dir.Construe(builder1);//Pass in builder to create specified parts
var product1 = builder1.GetResult();
dir.Construe(builder2);
var product2 = builder2.GetResult();
product2.Show();
product1.Show();
/*
* Abstract Builder Class, determines how many parts the component consists of
* And declare a method to get the result
*/
abstract class Builder
{
public abstract void BuildPartA();
public abstract void BuildPartB();
public abstract Product GetResult();
}

class Product
{
IList<string> parts = new List<string>();

public void Add(string part)
{
parts.Add(part);
}

public void Show()
{
Console.WriteLine("Product result is:\n");
foreach (var part in parts)
{
Console.WriteLine(part);
}
}
}

class ConcreteBuilder1:Builder
{
private Product _product = new Product();
public override void BuildPartA()
{
_product.Add("MethodA");
}

public override void BuildPartB()
{
_product.Add("MethodB");
}

public override Product GetResult()
{
return _product;
}
}

class ConcreteBuilder2:Builder
{
private Product _product = new Product();
public override void BuildPartA()
{
_product.Add("MethodC");
}

public override void BuildPartB()
{
_product.Add("MethodD");
}

public override Product GetResult()
{
return _product;
}
}

class Director
{
public void Construe(Builder builder)
{
builder.BuildPartA();
builder.BuildPartB();
}
}

Observer Pattern (Observer)

Observer Pattern is also called Publish-Subscribe Pattern. It defines a one-to-many dependency relationship, allowing multiple observer objects to listen to a subject object at the same time. When the state of this subject object changes, it will notify all observer objects so that they can update themselves (this is more like or INotifyChange interface used in WPF)

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
ConcreteSubject subject = new ConcreteSubject();
subject.Attach(new ConcreteObserver("X",subject));
subject.Attach(new ConcreteObserver("Y",subject));
ConcreteSubject subject2 = new ConcreteSubject();
subject2.Attach(new ConcreteObserver("Z",subject2));
subject.Update+=new SubjectHandler(new ConcreteObserver("X",subject).Update);
subject.Update+=new SubjectHandler(new ConcreteObserver("Y",subject).Update);
subject2.Update+=new SubjectHandler(new ConcreteObserver("Z",subject2).Update);

subject.SubjectState = "is subject";
subject.Notify();

subject2.SubjectState = "is subject2";
subject2.Notify();
//Observer Pattern
abstract class Observer
{
public abstract void Update();
}
/*
* Notification Class
* Add or remove observers
* Finally output results through Notify method
* When using delegates, add or remove observer classes can be cancelled
*/
delegate void SubjectHandler();//Declare delegate
abstract class OSubject
{
private IList<Observer> _observers = new List<Observer>();

public event SubjectHandler Update;//Create delegate class Update
//Add observer
public void Attach(Observer observer)
{
_observers.Add(observer);
}
//Remove observer
public void Detach(Observer observer)
{
_observers.Remove(observer);
}

//Notify
public void Notify()
{
Update();//Call delegate
// foreach (var observer in _observers)
// {
// observer.Update();
// }
}
}

/*
* Concrete Observer, boss or receptionist as mentioned in the book
* Increase role's own status
*/
class ConcreteSubject : OSubject
{
private string subjectState;


public string SubjectState
{
get => subjectState;
set => subjectState = value;
}
}

/*
* Concrete Notifier, slacking personnel as mentioned in the book
*/
class ConcreteObserver:Observer
{
private string name;
private string observerState;
private ConcreteSubject concreteSubject;//Concrete Notifier

1601: class Invoker

public ConcreteObserver(string name, ConcreteSubject concreteSubject)
{
this.concreteSubject = concreteSubject;
this.name = name;
}

public override void Update()
{
observerState = ConcreteSubject.SubjectState;//Get ruler status
Console.WriteLine("Observer {0}'s new status is {1}",name,observerState);
}

public ConcreteSubject ConcreteSubject
{
get => concreteSubject;
set => concreteSubject = value;
}
}

Abstract Factory Pattern (AbstractFactory)

Abstract Factory Pattern (AbstractFactory): Provide an interface for creating families of related or dependent objects without specifying their concrete classes.


When seeing the example in the book, I found that the case of connecting to the database I learned before is very similar to the abstract factory pattern. However, most of the time I only connect to one database. By storing the account password database type in the config file and encrypting the file, reading the password in the configuration file achieves data confidentiality, prevents repeated input of passwords, improves security, and reduces coupling between connection classes, passwords, and some statements.
If combining Database ORM with Abstract Factory Pattern, it should be possible to operate multiple databases simultaneously. I can try it.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

const string dbName = "mysql";
User u = new User();
u.Name = "noName";
u.Id = 1;
AbstractFactory factory = new AbstractFactory();
var user = factory.CreateUser(dbName);
user.Insert(u);
class AbstractFactory
{
public IUser CreateUser(string symbol)
{
IUser result = null;
switch (symbol)
{
case "sql":
result = new SqlUser();
break;
case "mysql":
result = new MySqlUser();
break;
}

return result;
}

}

class User
{
private int _id;

public int Id
{
get => _id;
set => _id = value;
}

private string _name;

public string Name
{
get => _name;
set => _name = value;
}
}
/// <summary>
/// Insert User
/// </summary>
interface IUser
{
void Insert(User user);
int GetUser(int id);
}

class SqlUser:IUser
{
public void Insert(User user)
{
Console.WriteLine("sql:insert database values {0}",user.Name);
}

public int GetUser(int id)
{
Console.WriteLine("sql:select name where id = {0}",id);
return - 1;
}
}

class MySqlUser:IUser
{
public void Insert(User user)
{
Console.WriteLine("mysql:insert database values {0}",user.Name);
}

public int GetUser(int id)
{
Console.WriteLine("mysql:select name where id = {0}",id);
return - 1;
}
}

Chapter 16 State Pattern

State Pattern (State): Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.

  1. State pattern mainly solves the problem when the conditional expression controlling the state transition of an object is too complicated. Simplifying the complex judgment logic by transferring the state logic to a series of classes representing different states“ – If this pattern is applied in Revit secondary development, we can refer to judging if a line segment is in the four quadrants of the coordinate system, partitioning the state by angle for automatic judgment
  2. Benefits of State Pattern: Localize behaviors associated with specific states and separate different state behaviors.
  3. When an object’s behavior depends on its state, and it must change its behavior at runtime according to the state, State Pattern can be considered.
  4. code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
static void Main(string[] args)
{
Content c = new Content(new ConcreteStateA());
c.Request();
c.Request();
c.Request();
c.Request();
Console.Read();
}


//State Pattern
abstract class State
{
public abstract void Handle(Content content);
}

class ConcreteStateA:State
{
public override void Handle(Content content)
{
content.State=new ConcreteStateB();
}
}

class ConcreteStateB:State
{
public override void Handle(Content content)
{
content.State=new ConcreteStateA();
}
}

internal class Content
{
private State _state;

public Content(State state)
{
this._state = state;
}

public State State
{
get => _state;
set { _state = value; Console.WriteLine("Current State:"+_state.GetType().Name);}
}

public void Request()
{
_state.Handle(this);
}
}
//example
abstract class EState
{
public abstract void WriteProgram(Work work);
}

class Work
{
private EState _state;

public Work(EState state)
{
_state = state;
}

private double _hour;

public double Hour
{
get => _hour;
set => _hour = value;
}

private bool _finish;

public bool Finish
{
get => _finish;
set => _finish = value;
}

public void SetState(EState state) => _state = state;//Change State

public void WriteProgram() => _state.WriteProgram(this);

}
class ForenoonState:EState
{
public override void WriteProgram(Work work)
{
if (work.Hour < 12)
Console.WriteLine("Current Time:{0} AM working, full of energy", work.Hour);
else
work.SetState(new NoonState()); work.WriteProgram();
}
}

class NoonState:EState
{
public override void WriteProgram(Work work)
{
if(work.Hour<13)
Console.WriteLine("Current Time: {0} Lunch break, sleepy");
else
work.SetState(new AfternoonState());
}
}

class AfternoonState:EState
{
public override void WriteProgram(Work work)
{
if (work.Finish)
work.SetState(new ResetState());
else
Console.WriteLine("Overtime!");
}

}

class ResetState:EState
{
public override void WriteProgram(Work work)
{
Console.WriteLine("Current Time: {0} , Go home after work");

}
}

Chapter 17 Adapter Pattern

Seeing the title of this chapter, one would first think of if...end if... controlling between different versions through pre-compilation.
Adapter Pattern (Adapter): Match interfaces of classes that are incompatible. Adapter pattern makes classes that would otherwise not work together due to incompatible interfaces work together.
When using database data in classes, we store database data in DataSet for operation Adapter.Fill
code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
     static void Main(string[] args)
{
Target target = new Adapter();
target.Request();
Console.Read();
}


class Target
{
public virtual void Request()
{
Console.WriteLine("Normal Request");
}
}

class Adaptee
{
public void SpecificRequest() => Console.WriteLine("Specific Request");
}

class Adapter:Target
{
private Adaptee _adaptee = new Adaptee();

public override void Request()
{
_adaptee.SpecificRequest();
}
}

Chapter 18 Memento Pattern

Memento Pattern (Memento): Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
     static void Main(string[] args)
{
//Create Originator and assign value
Originator originator = new Originator();
originator.State = "On";
originator.Show();
//Create Caretaker and store Memento
Caretaker caretaker = new Caretaker();
caretaker.Memento = originator.CreateMemento();
//Modify Originator's value
originator.State = "Off";
originator.Show();
//Restore to initial value
originator.SetMemento(caretaker.Memento);
originator.Show();
}
//Memento, responsible for storing internal state of Originator object, preventing other objects from accessing Memento
public class Memento
{
private string _state;

public Memento(string state)
{
_state = state;
}

public string State
{
get => _state;//Only can get state
}
}

class Originator//Originator, creates a Memento to record its internal state at current moment, and can restore state
{
private string _state;

public string State
{
get => _state;
set { _state = value; }
}

public Memento CreateMemento()
{
return new Memento(_state);
}

public void SetMemento(Memento memento)
{
_state = memento.State;
}

public void Show() => Console.WriteLine("State: " + _state);
}
//Caretaker, stores Memento, but cannot modify or operate on Memento
class Caretaker
{
private Memento _memento;

public Memento Memento
{
get => _memento;
set => _memento = value;
}
}

Chapter 19 Composite Pattern

Composite Pattern (Composite): Change objects into tree structures to represent ‘part-whole’ hierarchies. Composite allows clients to treat individual objects and compositions of objects uniformly.

  • Transparent Pattern: Declare all methods used to manage child objects in Component, including Add, Remove, etc., thus implementing all methods for managing child objects in the Component interface. The benefit is that leaf nodes and branch nodes are indistinguishable to the outside world, having consistent behavioral interfaces. However, for the leaf node itself, it does not have Add, Remove behaviors, so the interface is meaningless.
  • Safe Pattern: Do not declare Add and Remove methods in Component, so Leaf does not need to implement them, but declare methods for managing child objects in Branch Node (Composite). This pattern requires corresponding judgment when the client calls, bringing inconvenience.

Xml or TreeView hierarchy
code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
      static void Main(string[] args)
{
Composite site = new Composite("root");
site.Add(new Leaf("Leaf A"));
site.Add(new Leaf("Leaf B"));

Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf XA"));
comp.Add(new Leaf("Leaf XB"));
site.Add(comp);

Composite comp2 = new Composite("Composite XY");
comp2.Add(new Leaf("Leaf XYA"));
comp2.Add(new Leaf("Leaf XYB"));
comp.Add(comp2);

site.Add(new Leaf("Leaf C"));

Leaf leaf = new Leaf("Leaf D");
site.Add(leaf);

site.Display(1);
Console.Read();
}

class Composite:CComponent
{
private IList<CComponent> children = new List<CComponent>();
public Composite(string name) : base(name)
{
}

public override void Add(CComponent c)
{
children.Add(c);
}

public override void Remove(CComponent c)
{
children.Remove(c);
}

public override void Display(int depth)
{
Console.WriteLine((new string('-',depth))+name);
foreach (CComponent child in children)
{
child.Display(depth+2);
}
}
}

abstract class CComponent
{
protected string name;
public CComponent(string name) => this.name = name;

public abstract void Add(CComponent c);
public abstract void Remove(CComponent c);
public abstract void Display(int depth);
}

class Leaf:CComponent
{
public Leaf(string name) : base(name)
{
}

public override void Add(CComponent c)
{
Console.WriteLine("cannot add to a leaf");
}

public override void Remove(CComponent c)
{
Console.WriteLine("cannot remove a leaf");
;
}

public override void Display(int depth)
{
Console.WriteLine(new string('-',depth)+name);
}
}

Image Description

Chapter 20 Iterator Pattern

Interface: IEnumerator
Using c.begin()–>c.end() in C++ can create an iterator belonging to C++


Chapter 21 Singleton Pattern

Singleton Pattern (Singleton): Ensure a class has only one instance and provide a global point of access to it.

  • Usually we can make a global variable so that an object can be accessed, but it cannot prevent you from instantiating multiple objects. The best way is to let the class itself be responsible for saving its only instance. This class can ensure that no other instances can be created, and it can provide a method to access the instance.
  • Eager Singleton: Instantiates itself when loaded.
  • Lazy Singleton: Instantiates itself only when referenced for the first time.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
 static void Main(string[] args)
{
Singleton s1 = Singleton.GetInstance();
Singleton s2 = Singleton.GetInstance();
if (s1 == s2)
{
Console.WriteLine("Two objects are the same instance");
}

Console.Read();
}

class Singleton
{
private static Singleton instance;
private static readonly object syncRoot = new object();
private Singleton(){}

public static Singleton GetInstance()
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new Singleton();
}
}
}

return instance;
}
}

Chapter 22 Bridge Pattern

Bridge Pattern (Bridge): Decouple an abstraction from its implementation so that the two can vary independently.

  • Composite/Aggregate Reuse Principle (CARP): Try to use composition/aggregation, try not to use class inheritance.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
static void Main(string[] args)
{
//Bridge
Abstraction abstraction = new RefinedAbstraction();
abstraction.SetImplementor(new ConcreteImplementorA());
abstraction.Operator();
abstraction.SetImplementor(new ConcreteImplementorB());
abstraction.Operator();
Console.Read();
}
/*
* Bridge Pattern
*/
public class Bridge
{

}

abstract class Implementor
{
public abstract void Operator();
}

class ConcreteImplementorA:Implementor
{
public override void Operator()
{
Console.WriteLine("Execution of specific implementation method A");
}
}

class ConcreteImplementorB:Implementor
{
public override void Operator()
{
Console.WriteLine("Execution of specific implementation method B");
}
}

class Abstraction
{
protected Implementor _implementor;

public void SetImplementor(Implementor implementor)
{
this._implementor = implementor;
}

public virtual void Operator()
{
_implementor.Operator();
}
}

class RefinedAbstraction:Abstraction
{
public override void Operator()
{
_implementor.Operator();
}
}

Chapter 23 Command Pattern

Command Pattern (Command): Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

  • It is easier to design a command queue.
  • In needed cases, commands can be logged.
  • Allow the receiving party to decide whether to veto the request.
  • Easy to implement undo and redo of requests.
  • Since adding new command classes does not affect other classes, it is easy to add new concrete command classes.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static void Main(string[] args)
{
//Command
Receiver re = new Receiver();
Command c = new ConcreteCommand(re);
Invoker i = new Invoker();
i.SetCommand(c);
i.ExecuteCommand();
Console.Read();
}
abstract class Command
{
protected Receiver _receiver;
public Command(Receiver receiver) => _receiver = receiver;
abstract public void Execute();
}

class ConcreteCommand:Command
{
public ConcreteCommand(Receiver receiver) : base(receiver)
{
}

public override void Execute()
{
_receiver.Action();
}
}


class Invoker
{
private Command _command;

public void SetCommand(Command command)
{
_command = command;
}

public void ExecuteCommand()
{
_command.Execute();
}
}

class Receiver
{
public void Action()
{
Console.WriteLine("Execute Request");
}
}

Chain of Responsibility Pattern

Chain of Responsibility Pattern (Chain of Responsibility): Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

  • When a client submits a request, the request is passed along the chain until a ConcreteHandler object is responsible for handling it.
  • Neither the receiver nor the sender has explicit information about the other, and the objects in the chain do not know the structure of the chain. The result is that the Chain of Responsibility can simplify object interconnections. They only need to keep a reference to their successor, and do not need to keep references to all candidates.
  • Flexibility in assigning responsibilities to objects by adding or modifying the structure of handling a request at any time.
  • Need to consider handling at the end of the chain.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
static void Main(string[] args)
{
//ChainOfResponsibility
ConcreteHandler handle1 = new ConcreteHandler();
ConcreteHandlerB handle2 = new ConcreteHandlerB();
ConcreteHandlerC handle3 = new ConcreteHandlerC();
handle1.SetChainOfResponsilbility(handle2);
handle3.SetChainOfResponsilbility(handle3);
int[] resquests = {2, 5, 14, 22, 18, 3, 27, 20};
foreach (var i in resquests)
{
handle1.HandlerRequest(i);
}
}

abstract class ChainOfResponsibilty
{
protected ChainOfResponsibilty successor;

public void SetChainOfResponsilbility(ChainOfResponsibilty chainOfResponsibilty)
{
successor = chainOfResponsibilty;
}

public abstract void HandlerRequest(int request);
}

class ConcreteHandler:ChainOfResponsibilty
{
public override void HandlerRequest(int request)
{
if (request>=0&& request<10)
{
Console.WriteLine("{0} handle request {1}",this.GetType().Name,request);
}
else if (successor != null)
{
successor.HandlerRequest(request);
}
}
}

class ConcreteHandlerB:ChainOfResponsibilty
{
public override void HandlerRequest(int request)
{
if (request >= 10 && request < 20)
{
Console.WriteLine("{0} handle request {1}",this.GetType().Name,request);
}
else if(successor !=null)
{
successor.HandlerRequest(request);
}

}
}

class ConcreteHandlerC:ChainOfResponsibilty
{
public override void HandlerRequest(int request)
{
if (request > 20 && request < 30)
{
Console.WriteLine("{0} handle request {1}",this.GetType().Name,request);
}
else if (successor != null)
{
successor.HandlerRequest(request);
}

}
}

Chapter 25 Mediator Pattern

Mediator Pattern (Mediator): Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.

  • Mediator pattern is easy to apply in the system and easy to misuse. When a complex “many-to-many” interaction object group appears in the system, do not rush to use the mediator pattern, but reflect on whether your system design is reasonable.
  • By abstracting how objects collaborate, treating the mediator as a blockage concept and encapsulating it in an object, the focus shifts from the behavior of the objects themselves to their interaction, which means looking at the system from a more macro perspective.
  • Mediator pattern is generally applied where a set of objects communicate in a well-defined but complex way; and when wanting to customize a behavior distributed in multiple classes without generating too many subclasses.
    code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static void Main(string[] args)
{
ConcreteMediator m = new ConcreteMediator();
ConcreteColleague c1 = new ConcreteColleague(m);
ConcreteColleague2 c2 = new ConcreteColleague2(m);
m.Colleague1 = c1;
m.Colleague2 = c2;
c2.Send("Have you eaten? What about you?");
c1.Send("Not yet, together?");
}
abstract class Mediator
{
public abstract void Send(string message,Colleague colleague);
}

abstract class Colleague
{
protected Mediator _mediator;
public Colleague(Mediator mediator) => _mediator = mediator;
}

class ConcreteMediator:Mediator
{
private ConcreteColleague _colleague1;
private ConcreteColleague2 _colleague2;

public ConcreteColleague Colleague1
{
set => _colleague1 = value;
}

public ConcreteColleague2 Colleague2
{
set => _colleague2 = value;
}

public override void Send(string message, Colleague colleague)
{
if (colleague == _colleague1)
{
_colleague2.Notify(message);
}
else
{
_colleague1.Notify(message);
}
}
}

class ConcreteColleague:Colleague
{
public ConcreteColleague(Mediator mediator) : base(mediator)
{
}

public void Send(string message)
{
_mediator.Send(message,this);
}

public void Notify(string message)
{
Console.WriteLine("Colleague 1 received message: "+ message);
}
}

class ConcreteColleague2:Colleague
{
public ConcreteColleague2(Mediator mediator) : base(mediator)
{
}

public void Send(string message)
{
_mediator.Send(message,this);
}

public void Notify(string message)
{
Console.WriteLine("Colleague 2 received message: "+message);
}

}

Chapter 26 Flyweight Pattern

Flyweight Pattern (FlyWeight): Use sharing to support large numbers of fine-grained objects efficiently.

  • If an application uses a large number of objects, and the large number of objects causes a lot of storage overhead, you should consider using it. Also, most of the state of the object can be external state. If the external state of the object is deleted, then relatively few shared objects can replace many groups of objects. In this case, use Flyweight Pattern.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
static void Main(string[] args)
{
//FlyWeight

int i = 22;
FlyWeightFactory f = new FlyWeightFactory();

FlyWeight fx = f.GetFlyWeight("X");
fx.Operation(--i);

FlyWeight fy = f.GetFlyWeight("Y");
fy.Operation(--i);

FlyWeight fz = f.GetFlyWeight("Z");
fz.Operation(--i);

FlyWeight uf = new UnSharedConcreteFlyWeight();
uf.Operation(--i);
}
abstract class FlyWeight
{
public abstract void Operation(int exterinsicatae);
}

class ConcreteFlyWeight:FlyWeight
{
public override void Operation(int exterinsicatae)
{
Console.WriteLine("Concrete FlyWeight: "+exterinsicatae);
}
}

class UnSharedConcreteFlyWeight:FlyWeight
{
public override void Operation(int exterinsicatae)
{
Console.WriteLine("Unshared Concrete FlyWeight: " + exterinsicatae);
}
}

class FlyWeightFactory
{
private Hashtable flyWeights = new Hashtable();

public FlyWeightFactory()
{
flyWeights.Add("X",new ConcreteFlyWeight());
flyWeights.Add("Y",new ConcreteFlyWeight());
flyWeights.Add("Z",new ConcreteFlyWeight());
}

public FlyWeight GetFlyWeight(string key)
{
return (FlyWeight) flyWeights[key];
}

}

Chapter 27 Interpreter Pattern

Interpreter Pattern (Interpreter): Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

  • If a particular type of problem occurs frequently enough, then it might be worth expressing instances of the problem as sentences in a simple language. Then you can build an interpreter that solves the problem by interpreting these sentences.
    code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static void Main(string[] args)
{
IContext context = new IContext();
List<AbstractExpression> list = new List<AbstractExpression>();
list.Add(new NonterminalExpression());
list.Add(new TerminalExpression());
list.Add(new TerminalExpression());

foreach (var expression in list)
{
expression.Interpret(context);
}
}
abstract class AbstractExpression
{
public abstract void Interpret(IContext context);
}
class NonterminalExpression:AbstractExpression
{
public override void Interpret(IContext context)
{
Console.WriteLine("Non-terminal Interpreter");
}
}

class TerminalExpression:AbstractExpression
{
public override void Interpret(IContext context)
{
Console.WriteLine("Terminal Interpreter");
}
}

class IContext
{
private string _input;

public string Input
{
get => _input;
set => _input = value;
}

private string _output;

public string Output
{
get => _output;
set => _output = value;
}
}

Visitor Pattern

Visitor Pattern (Visitor): Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

  • Visitor pattern applies to systems with relatively stable data structures. It decouples the data structure from the operations on the structure, allowing the set of operations to evolve relatively freely.
  • The purpose of the Visitor Pattern is to separate data processing from data structure. If there is a relatively stable data structure and algorithms that are easy to change, it is more appropriate to use the Visitor Pattern, because the Visitor Pattern makes adding algorithm operations easier.
  • The advantage of the Visitor Pattern is that adding new operations is easy, because adding a new operation means adding a new visitor. The Visitor Pattern concentrates related behaviors into a behavioral object.

code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
static void Main(string[] args)
{
ObjectStructure structure = new ObjectStructure();
structure.Attach(new ConcreteElementA());
structure.Attach(new ConcreteElementB());

ConcreteVisitor v1 = new ConcreteVisitor();
ConcreteVisitor2 v2 = new ConcreteVisitor2();

structure.Accept(v1);
structure.Accept(v2);
}
abstract class Visitor
{
public abstract void VisitConcreteElementA(ConcreteElementA concreteElementA);
public abstract void VisitConcreteElementB(ConcreteElementB concreteElementB);
}

abstract class Element
{
public abstract void Accept(Visitor visitor);
}

class ConcreteElementA:Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}

public void OperationA()
{

}
}

class ConcreteElementB:Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementB(this);
}

public void OperationB()
{

}
}

class ConcreteVisitor:Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine("{0} visited by {1}",concreteElementA.GetType().Name,this.GetType().Name);
}

public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine("{0} visited by {1}",concreteElementB.GetType().Name,this.GetType().Name);
}
}

class ConcreteVisitor2:Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine("{0} visited by {1}",concreteElementA.GetType().Name,this.GetType().Name);
}

public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine("{0} visited by {1}",concreteElementB.GetType().Name,this.GetType().Name);
}
}

class ObjectStructure
{
private IList<Element> elements = new List<Element>();

public void Attach(Element element)
{
elements.Add(element);
}

public void Detach(Element element)
{
elements.Remove(element);
}

public void Accept(Visitor visitor)
{
foreach (var element in elements)
{
element.Accept(visitor);
}
}