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
Encapsulation
Inheritance
Polymorphism
Eliminate coupling between types
Substitutability
Extensibility
Interface-ability
Flexibility
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.
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.
classCashFactory { privatestatic CashSuper ce; publicCashFactory(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; } }
publicCashReturn(string target,string re) { this.returnMoney = double.Parse(re); this.targetMoney = double.Parse(target); } publicoverridedoubleAcceptCash(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.
Open for extension
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:
High-level modules should not depend on low-level modules; both should depend on abstractions.
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.
staticvoidMain(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> classPerson { publicPerson() { }
privatestring name; publicPerson(string name) { this.name = name; } publicvirtualvoidShow() //Virtual class, can rely on implementation, but class must have brackets {} { Console.WriteLine("{0}'s outfit is:", this.name); } } classFinery : Person { protected Person person;
Proxy Pattern (Proxy) provides a surrogate or placeholder for another object to control access to it. Application:
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.
Virtual Proxy: creates expensive objects on demand. It uses this to hold real objects that take a long time to instantiate.
Protection Proxy: controls access permissions to the real object.
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
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
staticvoidMain(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 */ abstractclassOperation { privatedouble _num1; privatedouble _num2;
publicdouble Num1 { get => _num1; set => _num1 = value; }
publicdouble Num2 { get => _num2; set => _num2 = value; }
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.
publicoverride 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 } }
classWorkInformation:ICloneable { privatestring workDate; privatestring company; publicstring WorkDate { get => workDate; set => workDate = value; }
publicstring Company { get => company; set => company = value; }
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
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
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.
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 */ abstractclassBuilder { publicabstractvoidBuildPartA(); publicabstractvoidBuildPartB(); publicabstract Product GetResult(); }
classProduct { IList<string> parts = new List<string>();
publicvoidAdd(string part) { parts.Add(part); }
publicvoidShow() { Console.WriteLine("Product result is:\n"); foreach (var part in parts) { Console.WriteLine(part); } } }
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)
/* * Concrete Observer, boss or receptionist as mentioned in the book * Increase role's own status */ classConcreteSubject : OSubject { privatestring subjectState; publicstring SubjectState { get => subjectState; set => subjectState = value; } }
/* * Concrete Notifier, slacking personnel as mentioned in the book */ classConcreteObserver:Observer { privatestring name; privatestring observerState; private ConcreteSubject concreteSubject;//Concrete Notifier 1601: classInvoker publicConcreteObserver(stringname, ConcreteSubjectconcreteSubject) { this.concreteSubject = concreteSubject; this.name = name; } publicoverridevoidUpdate() { 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.
conststring dbName = "mysql"; User u = new User(); u.Name = "noName"; u.Id = 1; AbstractFactory factory = new AbstractFactory(); var user = factory.CreateUser(dbName); user.Insert(u); classAbstractFactory { public IUser CreateUser(string symbol) { IUser result = null; switch (symbol) { case"sql": result = new SqlUser(); break; case"mysql": result = new MySqlUser(); break; }
return result; } }
classUser { privateint _id;
publicint Id { get => _id; set => _id = value; }
privatestring _name;
publicstring Name { get => _name; set => _name = value; } } ///<summary> /// Insert User ///</summary> interfaceIUser { voidInsert(User user); intGetUser(int id); }
publicintGetUser(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.
“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
Benefits of State Pattern: Localize behaviors associated with specific states and separate different state behaviors.
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.
classResetState:EState { publicoverridevoidWriteProgram(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:
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:
staticvoidMain(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 publicclassMemento { privatestring _state;
publicMemento(string state) { _state = state; }
publicstring State { get => _state;//Only can get state } }
classOriginator//Originator, creates a Memento to record its internal state at current moment, and can restore state { privatestring _state;
publicstring State { get => _state; set { _state = value; } }
public Memento CreateMemento() { returnnew Memento(_state); }
publicvoidShow() => Console.WriteLine("State: " + _state); } //Caretaker, stores Memento, but cannot modify or operate on Memento classCaretaker { 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.
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.
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.
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.
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:
publicvoidNotify(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.
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:
publicstring Input { get => _input; set => _input = value; }
privatestring _output;
publicstring 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.