Pan-talk design patterns

 

Foreword

Programmers design pattern has been talked about things, often codereview when someone suggested that this code does not meet the XX design principles or XX design patterns. Books on the market about design patterns also went for many, a dime a dozen. I had the honor have read GOF ( Gang of at Four) of God as "Design Patterns - Object-Oriented Software is reusable," while feeling four masters of wisdom have to admit that some patterns really been behind the times, after all, this book was published in 1995, was limited to some of the machine's hardware (memory, the CPU and other) reasons, there was some high-level language and data structure and there is no standard form, so the book will describe some of today it seems to have old fashioned model. This article does not intend to GOF are described one by one in detail 23 design patterns, some have more resonance modes have specific code examples and detailed descriptions that have no resonance mode might in passing, and this article all of the examples are in C ++ pseudo code, or a part of the code. C ++ implementation of design patterns will depend strongly virtual function, a virtual function can be dynamically bound specific functions at runtime, thus giving more program scalability.

Design Principles

Look at a piece of code is not a good code is not to be applied mechanically to see it in line with what kind of model, but whether it meets the following six design principles. A design pattern is just the surface of the routine, the design principle is the central idea. Or just a means to design patterns, design principles is the goal. As for Zhang San Feng Zhang Wuji dialogue like:

"Loudly you remember it."

"I forgot all about it."

"Then you can play it."

With the advances in language design patterns will change and the gradual evolution, but the design principles will not change, so we have to grasp the fundamental things. The following are the six design principles:

 

1. Dependency Inversion principle: high-level modules should not depend on the underlying module.

2. Open Closed Principle: to develop open, closed for modification.

3. Single Responsibility Principle: duties of a class of only one.

4. Alternatively principle (the principle of replacing Richter): Subclasses must be able to replace the parent class.

5. Interface segregation principle: exposed to the user interface and complete small.

6. Law of Demeter: An object should be kept to a minimum understanding of other objects.

23 design patterns

GOF to 23 design patterns into three categories, belonging schema creation, structural model, behavior patterns.

Create a schema

1. abstract factory ( abstract Factory) mode

2. Generator ( Builder) mode

3. The method of factory ( Factory Method) mode

4. Prototype ( the prototype) mode

Example 5. Single ( Singleton) mode

 

Factory mode it means return a new object out, abstract factory is new grouping of related objects back, the generator model is a responsible step to create objects and define steps into virtual functions, and external inheritance implementation. Prototype model is to create objects by copying itself. Singleton pattern is to be rotten with all modes, only one instance of a class. These are the words can say with a clear pattern behind will not go into details.

 

Structural model

6. Adapter ( Adapter) mode

7. bridge ( Bridge) Mode

8. The composition ( Composite) mode

9. The decorative patterns ( Decorator) mode

10. Appearance ( Facade) mode

11. Flyweight ( of flyweight) mode

12. The proxy ( Proxy) mode

 

Behavior

13. duty chain ( catena alberghiera of Responsibility) mode

14. The command ( the Command) mode

15. Interpreter ( Interpreter) mode

16. iterator ( Iterator) mode

17. Mediator ( Mediator) mode

18. Memorandum ( Memento) mode

19. The observer ( the Observer) mode

20. A state ( State) Mode

21. Strategy ( at Strategy) mode

22. The method template ( Template Method,) mode

23. Visitors ( visitor) mode

There resonance mode

This section will combine the code in the project gives the example code, some of which are already in the current project with a model, there are some code refactoring will be used in the future.

Strategy mode ( at Strategy)

Adapter ( Adapter)

Template Method ( Template Method,)

Decorator ( decerator)

Observer ( the Observer)

Strategy mode ( at Strategy)

GOF defined

Define a series of algorithms, encapsulate them one by one, and makes them interchangeable. This mode makes the algorithm can be varied independently of the clients that use it.

 

Class diagram (taken from GOF) structures

Application Strategy mode is almost ubiquitous, as long as the if / else where in fact be a strategy mode. So our project is possible anywhere, where I first posted a code that does not use the strategy pattern, compare it might mean something more profound.

 1 void messageHandle()
 2 {
 3       ......
 4 
 5       QueueItem* data = NULL;
 6       worker->m_dataQ.pop(data);
 7 
 8       if (NULL == data)
 9       {
10            // record error and return
11 return; 12 } 13 14 if (MSG_FILE_COPY_BT_SYNC == data->m_itemtype ) 15 { 16 Worker->handleBTSync(data); 17 } 18 else if( MSG_FILE_COPY_BT_SVR_REFLUSH == data->m_itemtype) 19 { 20 worker->handleUploadData(data); 21 } 22 else if (MSG_FILE_COPY_BT_META == data->m_itemtype) 23 { 24 worker->handleDatameta(data); 25 } 26 else if(MSG_FILE_COPY_BT_PIECE_TCP == data->m_itemtype) 27 { 28 worker->handleClientPieceRequest(data); 29 } 30 else 31 { 32 ...... 34 } 35 ....... 36 }

 

This mode does not use tactics seemingly no problem, but in case adds a message type it? This time one of the most simple and crude way we think of is on this source code plus a else if, but which violates the design principle of the mode of opening and closing. Because modified the original code would mean the introduction of new risks, when testing is necessary to test this code, which means that we do not reuse code.

Strategy pattern example code:

 1 void messageHandle()
 2 {
 3        ......
 4        QueueItem* data = NULL;
 5        worker->m_dataQ.pop(data);
 6 
 7         if (NULL == data)
 8         {
 9             // record error and return
10 return; 11 } 12 13 Data->handleItem(); 14 ........ 15 }

 

Data are QueueItem subclass to instantiate objects, each of these subclasses implements handleItem () method, so callers do not care about what type of message, directly from the lock queue to take out a unified call handleItem () method can be. If the future has added a message type, just need to inherit QueueItem implement a subclass can call this part of the code is not changed at all in order to achieve true multiplexing.

Adapter ( Adapter)

GOF definition:

Converting the interface of a class clients expect another interface. Adapter pattern makes those classes otherwise because of incompatible interfaces can not work together to work together.

 

Class diagram (taken from GOF) structures

GOF describes two ways to implement the Adapter pattern. The first method is to use multiple inheritance, Adapter classes inherit a new class ( Target) and the old class ( the Adaptee), then calls the old interface in the new interface, of course, a small amount of code that is compatible with the adapter. The second method is to inherit a new class (Target) and a combination of old class (Adaptee). The former is called adapter type, which is called an object adapter. I am a more respected object adapter, because multiple inheritance in a certain sense, is the destruction of the class encapsulation, while also not flexible enough. I understand that some experienced seniors are recommended combinations, rather than multiple inheritance.

 

The project has a apiserver provide services outside the old interface is user in use, but because the old interface definition is narrow (parameter less and scenarios to consider is not in place), we are not able to meet the new needs of users (old user can met), so to define a new interface, but the old interface is a lot of code can be reused, as long as minor adjustments and compatible, just to meet the usage scenarios adapter mode, so as to bring the sample code, use the It is the object adapter implementation.

 

The following sample code:

 1 class NewApi
 2 {
 3 public 4    virtual int newApi1();
 5 };
 6 
 7 class OldApi
 8 {
 9 public:
10   int oldApi1();
11 };
12 
13 class Adapter : pulblic NewApi
14 {
15 private:
16    OldApi* m_oldApi;
17 Public:
18   virtual int newApi1()
19   {
20        @ a small amount of code that is compatible with the adapter 
21 is        m_oldApi-> oldApi1 ();
 22 is    }
 23 is };

 

Template Method ( Template Method,)

GOF definitions: skeleton algorithm defines one operation, while deferring some steps to subclasses. Template Method lets subclasses may not change the structure of the algorithm to a specific algorithm redefine a step change.

 

Class diagram (taken from GOF) structures

 

 

Sample code is as follows:

 1 class Base(第三方开源库代码)
 2 {
 3 public:
 4   void display()
 5   {
 6     doFocus();
 7     //some other code
 8     doDisplay();
 9    //some other code
10    resetFocus();
11 }
12 protected:
13   virtual void doDisplay() = 0;
14   Void doFocus();
15   void resetFocus();
16 };
17 
18 class subClass : public Base(应用代码)
19 {
20 Public:
21   Virtual void doDisplay()
22   { 
23    //implement my doDisplay
24   }
25 };
26 
27 int main()   (应用调用代码)
28 {
29   Base* base = new subClass();
30   base->display();
31   Return 0;
32 }

 

 

从以上例子中我们可以看出,Base定义了一个display的方法,display的算法框架是固定的,这里是先调用doFocus,在调用doDisplay(),再是resetFocus。但是其中的一步doDisplay是延迟到了子类的实现中,具体怎么doDisplay由子类决定。即“子类可以不改变一个算法的结构即可,重定义改算法的特定步骤”。然后main函数的调用是不用关心display的内部实现,它只需要调用基类的display方法即可。

 

这个设计模式常用的场景是当Base类是第三方开源库的某一个类,但是第三方库开发人员是无法知道特定的用户想怎么实现用户层的代码,所以把某个特定的步骤预留出来让用户自己实现是一种可行的办法。例如下面是libevhtp的用户示例代码:

其实这些步骤都是固定的(固定的算法骨架),不用用户在自己的代码里面再把这些示例的代码再复制粘贴过来,作者完全可以自己把它封装成一个函数,然后里面在调用特定的要用户自己设计的代码(特定步骤)再封装成另外一个虚函数让用户自己实现。这样用户的main函数里面只要有一行调用代码即可。

 

装饰器(decerator)

GOF定义

动态给对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

 

类图(摘自GOF)结构

 

有时候运用Decorator模式会比生成子类的方法少实现很多子类,即能减少子类的数目,从而节省开发的时间成本。

 

 

如上图所示,现在有两个Stream的子类,MemoryStream, FileStream,现在想实现一个ASCII转码功能和一个压缩功能,一般我们继承MemoryStream,然后将转码和压缩的功能实现一遍,然后另外子类继承FileStream,然后将转码和压缩的功能实现一遍。但是我们很容易发现尽管MemoryStream和FileStream的读写方法不同,但是它们转码和压缩的代码是一样的,那么久应该把转码和压缩分别作为子类,然后让MemoryStream和FileStream的对象作为参数动态的传递给转码和压缩的类对象,所以就有了上面的类图架构。

 

 1 Class Stream
 2 {
 3 Public:
 4 Virtual int read() =0;
 5 Vritral int write() = 0;
 6 };
 7 
 8 Class MemoryStream:public Stream
 9 {
10 Public:
11    Int read()
12    {
13      //do memory stream read
14    }
15    Int wrtie()
16    {
17    //do memory stream write
18    }
19 };
20 
21 Class FileStream : public Stream
22 {
23 //同理
24 };
25 
26 Class Decorator:public Stream
27 {
28 Private:
29  Stream* m_stream;
30 
31 Public:
32 Decorator(Stream* s)
33 {
34   m_stream = s;
35 }
36 
37 Int Read()
38 {
39   m_stream->Read();
40 }
41 };
42 
43 class CompressingStream : pulic Decorator
44 {
45 Public:
46 Int Read()
47 {
48 //compress data in buffer
49   Decorator::Read();
50 }
51 };

 

这样实现我们就可以动态的给文件流增加转码和压缩的功能。例如组合一个转码压缩的文件流只需一行代码:

1 Stream* s = new CompressingStream(new ASCII7Stream(new FileStream()));

 

 

观察者(observer)

GOF定义

定义对象间的一种一对多的依赖关系,对一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。

 

类图(摘自GOF)结构

 

观察者模式的核心其实就是观察者(observer)向目标(subject)注册一个回调,当目标观察到条件发生时就回调之前观察者注册的回调函数。

 

这个模式可以用到zookeeper的应用代码中,zookeeper在我们项目中扮演者服务发现和分布式配置和分布式锁的角色。当有很多个类对象都依赖于zookeeper的时候,可以让一个类(subject)专门watch zookeeper的状态,其他类(observer)向subject注册回调,当zookeeper发生状态改变的时候就回调这些回调函数。

 

 1 Class ZKSubject
 2 {
 3 Private:
 4   Vector<Base*> m_observerList;
 5 Public:
 6 Void attach(Base* base);
 7 Void detach(Base* base);
 8 Void notify()
 9 {
10    For(int i = 0; i < m_observerLis.size(); ++i)
11    {
12       m_observerList[i]->update(this);
13    }
14 }
15 };
16 
17 Class Base
18 {
19 Public:
20 virtual void update(ZKSubject* s) = 0;
21 };
22 
23 Class SubClass1: public Base
24 {
25 Public:
26    Void update(ZKSubject* s)
27    {
28    //do youself about zookeeper
29    }
30 };
31 
32 Class SubClass2: public Base
33 {
34 Public:
35    Void update(ZKSubject* s)
36    {
37     //do youself about zookeeper
38    }
39 };

 

架构级模式

这两个模式讲究的是系统架构级的接口隔离。外观模式是让一堆紧耦合的类在系统的内部,然后外部只和一个接口类打交道,接口类负责内外的处理。这样暴露给外界的接口就小而完备,内部的改动也不会影响到外部应用。代理模式是本来应该调用A,但是由于这样或者那样的原因必须要经过B才能访问A,这样B就是A的代理类。典型的场景就是分布式使用thrift(一个异步RPC框架)接口的时候,我们如果直接调用对端的类,而是通过thrift的生成类去分布式(网络通信和序列化以及反序列化)调用。

外观模式(facade)

代理模式(proxy)

与数据结构相关的模式

以下的这四个设计模式是让调用方从一对多变成一对一,比如说职责链模式,调用方调用一个对象节点就相当于调用整个链表的节点,而是否调用下一个节点,怎么调用下一个节点调用方式不需要关心的,从而实现抽象调用。

职责链(chain of responsibility)链表

迭代器(Iterator)迭代器

组合(composite)树

享元模式(flyweight)map

 

职责链(chain of responsibility)链表

GOF定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

 

类图(摘自GOF)结构

 

职责链模式的核心就是链表,所有的对象组成一个链表,每个对象是其中的一个结点。当这个对象处理不了这个请求的时候就会将这个请求传递给它知道的下一个对象,如此递归,直到有一个对象处理它为止。

 

这个模式的应用只适合于请求者不知道哪一个接受者能处理它的情况,也就是说一定要遍历一遍的情况。假如说已经知道了请求者和接受者之间的对象关系,直接找到接收者处理它即可。例如我们项目的各种任务消息对象的dbproxy的写redis线程,这里就可以把任务消息看作是请求者,写redis的线程看作是接受者,假如消息和写redis线程的对象关系不知道,那就要一个个遍历这些redis线程,到底哪个线程能够处理这个消息(这里考虑到写redis的时序性问题,所以不同的redis线程处理不同的消息(不同taskid))。当现实应用中消息和写redis线程是有一个哈希算法的,所以根据map就能够轻松的找到对应的redis写线程。

职责链示例代码:

 1 Class Base
 2 {
 3 Public:
 4    Virtual int handle() = 0;
 5 };
 6 
 7 Class Node : public Base
 8 {
 9 Private:
10    Node* m_nextNode;
11 Public:
12    Bool isIcanHandle();
13    Virtual int handle()
14    {
15      If(isIcanHandle())
16      //deal it
17      Else
18         m_nextNode->handle();
19    }
20 }; 
 

同理组合模式也是让对象连成一颗树,叶子节点和非叶子节点分开处理,有请求过来就遍历整棵树,或者从某个节点开始遍历。迭代器模式就是一个迭代器,有前后对象的指针,依次遍历处理。享元模式的核心就是维护一个map,然后遍历一个这个map有没有这类对象,没有就创建一个,有就共用之前的。

总结

设计模式可以说得上是程序员修炼中最难的一环,讲究的是思维模式的变化。如果是某一门编程语言不会,按部就班把这门语言对应的语法过一篇就差不多,但是何时用何种设计模式都是没有一个定论的。有时候代码看着差不多,实际上已经差之千里了。所以考究一段代码是否设计合理,只需要判断是否符合设计原则即可,而不是更要生搬硬套它符合什么模式。

正如GOF所说,这不是一本看一遍就能够束之高阁的书,设计模式的探究将会伴随着程序员的一生,所以这仅仅是个开始,文章的理解可能不是很深刻,如有错漏,还望指正,不胜感激。

Guess you like

Origin www.cnblogs.com/makelu/p/11002656.html