Do what C ++ cannot do-Visitor Mode

Source: Internet
Author: User

Do what C ++ cannot do-Visitor Mode
I finished a very difficult task last night and sent it to my American colleague for Review. So today, I only need to modify the code and submit it according to their suggestions, and the task will be completed in one week. In the remaining two or three days, I can spend some spare time looking at other materials to enrich myself. Open the Review Board and you can see that my code has been marked as ready for submission, but the annotations left below caught my attention: "Great job! With this solution, we can start our integration work and perform testing earlier. one thing is that we have used several "instance of" in the overrided function. that's double dispatch, an obvious signature for using Visitor pattern. we can switch to that pattern in our future work. I know the Visitor mode, but what does Double Dispatch mean? I opened the search engine and found several introductory articles about Double Dispatch. Of course, the most clear and accurate description of Double Dispatch is on Wikipedia: In software engineering, Double Dispatch is a special form of multiple dispatch, and a mechanic that dispatches a function call to different concrete functions depending on the runtime types of two objects involved in the call. in most object-oriented systems, the concrete function that is called from a function call in the code depends On the dynamic type of a single object and therefore they are known as single dispatch CILS, or simply virtual function CILS. at the end of the paragraph, I saw a familiar term "virtual function ". As soon as I saw this word, I began to recall the steps for calling the virtual function: when calling the virtual function, during the C ++ runtime, the virtual function table corresponding to the object will be searched, and the corresponding virtual function implementation will be called Based on the address recorded in the virtual function table. Because the virtual function table is associated with the type, the logic for calling the virtual function is related to the type of the object. The Double Dispatch must be related to the two objects involved in the function call. So I thought: Can I implement Double Dispatch by adding a function overload for the type? I opened Visual Studio and wrote the following code: 1 // ordinary car, discount of 0.03 2 class Vehicle 3 {4 public: 5 virtual double GetBaseDiscountRate () {return 0.03;} 6}; 7 8 // as it is a special Mercedes-Benz dealer, you can get a larger discount of 9 class Benz: public Vehicle10 {11 public: 12 virtual double GetBaseDiscountRate () {return 0.06 ;}13 }; 14 15 // ordinary sales staff can only sell 16 class Sales17 {18 public according to the discount stipulated by the company: 19 virtual double GetDiscountRate (Vehicle & vehicle) 20 {21 return vehi Cle. getBaseDiscountRate (); 22} 23 24 virtual double GetDiscountRate (Benz & benz) 25 {26 return benz. getBaseDiscountRate (); 27} 28}; 29 30 // sales manager, which offers an additional 31 class SalesManager: public Sales32 {33 public: 34 virtual double GetDiscountRate (Vehicle & vehicle) 35 {36 return vehicle. getBaseDiscountRate (); 37} 38 39 virtual double GetDiscountRate (Benz & benz) 40 {41 return benz. getBaseDiscountRate () * 1.1; 4 2} 43}; 44 45 int _ tmain (int argc, _ TCHAR * argv []) 46 {47 // There are two vehicles to be sold, one is a regular car, the other is Mercedes-benz 48 Vehicle & vehicle = Vehicle (); 49 Vehicle & Benz = benz (); 50 51 // ask General Sales about the discounts for the two vehicles 52 Sales * pSales = new Sales (); 53 double rate = pSales-> GetDiscountRate (vehicle ); 54 cout <"Sales: The rate for common vehicle is:" <rate <endl; 55 rate = pSales-> GetDiscountRate (benz); 56 cout <"Sales: the rate for benz is: "< <Rate <endl; 57 58 // ask the sales manager about the discount of the two vehicles 59 SalesManager * pSalesManager = new SalesManager (); 60 rate = pSalesManager-> GetDiscountRate (vehicle); 61 cout <"Sales Manager: The rate for common vehicle is:" <rate <endl; 62 rate = pSalesManager-> GetDiscountRate (benz); 63 cout <"Sales Manager: The rate for benz is:" <rate <endl; 64 65 return 0; 66} click "run", but the answer is not what I think: Ah, the sales manager did not offer any extra discounts. This is a big headache. Start the Visual Studio debugging function. I see that the statement "pSalesManager-> GetDiscountRate (benz)" calls the heavy load defined for a common car in the SalesManager class: 1 class SalesManager: public Sales2 {3 public: 4 virtual double GetDiscountRate (Vehicle & vehicle) <---- the input parameter runtime type is Benz, but the overload 5 {6 return Vehicle defined for vehicle is called. getBaseDiscountRate (); 7} 8 ...... 9}; do I have a wrong understanding of function overloading? In the search engine, type "C ++ overload resolution". I opened the C ++ standard's explanation of function overload resolutions. At the beginning, I gave my answer: In order to compile a function call, the compiler must first perform name lookup, which, for functions, may involve argument-dependent lookup, and for function templates may be followed by template argument deduction. if these steps produce more than one candidate function, then overload resolution is med to select the function that will be actually be called. oh, yes! The function overload resolution is completed during compilation. Because we passed in a reference of the Vehicle type, the compiler cannot know whether the parameter of the GetDiscountRate () function is a Vehicle instance or a Benz instance at runtime, therefore, the compiler may only choose to call the overload that accepts the reference of the Vehicle type. If the input parameter benz is no longer a Vehicle reference but a more specific Benz reference, the compiler will correctly determine the function to be called: however, this does not dynamically decide the logic to be called Based on the parameter type, nor is it a Double Dispatch. How can this effect be achieved? I am thinking hard. "What are you thinking ?" Colleagues around me handed me the fruit distributed by the company today and asked me while eating. I told him the program I just wrote and the problem I was thinking about. "Since you need to dynamically determine the logic to be called, place these logics in the Dynamic Running place, for example, put them in your car class and then expose a virtual function, you can determine the discount rate required for the Car Based on the incoming car type." "Oh, right," I suddenly realized. C ++ is a virtual function, which is a Single Dispatch. If two virtual functions are called consecutively on the object and input parameters, isn't it Double Dispatch? In the example of selling cars, I want to determine the logic to be executed based on the sales personnel's title and the type of cars sold. First, we need to call a virtual function through the Sales pointer to determine the actual logic that the salesperson needs to execute during Sales. In the process of executing these logics, we can continue to call the virtual functions defined on the input parameter instance to determine the logic to be executed based on the input parameter type! Just do it. I added a new virtual function GetManagerDiscountRate () to the Vehicle class to allow the SalesManager class function implementation to call it to get the discount the sales manager can get, in the Benz class, rewrite it to return the special discount rate for the Benz class. In the implementation of the Sales and SalesManager classes, we need to call the GetBaseDiscountRate () and the new GetManagerDiscountRate () functions to return the discount rates available to the general Sales and Sales managers respectively. In this way, we can determine the discount rate based on the position of the sales staff and the models we sell. The changed code is as follows: 1 // regular car, with a discount of 0.03 2 class Vehicle 3 {4 public: 5 virtual double GetBaseDiscountRate () {return 0.03 ;} 6 virtual double GetManagerDiscountRate () {return 0.03 ;}7}; 8 9 // as it is a Mercedes-Benz special seller, you can get a larger discount of 10 class Benz: public Vehicle11 {12 public: 13 virtual double GetBaseDiscountRate () {return 0.06;} 14 virtual double GetManagerDiscountRate () {return 0.066 ;}15}; 16 17 // regular salesperson, you can only follow the discount prescribed by the company 18 class Sales19 {20 public: 21 virtual double GetDiscountRate (Vehicle & vehicle) 22 {23 return vehicle. getBaseDiscountRate (); 24} 25}; 26 27 // Sales Manager, can offer additional discounts for certain models 28 class SalesManager: public Sales29 {30 public: 31 virtual double GetDiscountRate (Vehicle & vehicle) 32 {33 return vehicle. getManagerDiscountRate (); 34} 35}; 36 37 int _ tmain (int argc, _ TCHAR * argv []) 38 {39 // two vehicles to be sold 40 Vehicle & vehicle = Vehicle (); 41 Benz & benz = Benz (); 42 43 // ask the average Sales about the discounts of the two vehicles 44 Sales * pSales = new Sales (); 45 double rate = pSales-> GetDiscountRate (vehicle); 46 cout <"Sales: The rate for common vehicle is:" <rate <endl; 47 rate = pSales-> GetDiscountRate (benz); 48 cout <"Sales: The rate for benz is:" <rate <endl; 49 50 // ask the sales manager about the discount for the two vehicles 51 SalesManager * pSalesManager = new SalesManager (); 52 rate = pSalesM Anager-> GetDiscountRate (vehicle); 53 cout <"Sales Manager: The rate for common vehicle is:" <rate <endl; 54 rate = pSalesManager-> GetDiscountRate (benz); 55 cout <"Sales Manager: The rate for benz is:" <rate <endl; 56 57 return 0; 58} running the program again, I found that the correct result is now available: that is, the self-created Double Dispatch implementation has been able to run correctly. Hello, Visitor, "Why do C ++ advanced languages do not support Double Dispatch directly ?" I asked my colleagues who are struggling with fruit. "No ." He did not lift his head. He replied casually and picked up another fruit. In other words, he can really eat. "Really don't need either ?" I entered "why C ++ double dispatch" in the search engine ". After years of work, I have developed a fixed learning habit. For example, for a knowledge point, I often first know How, that is, How it works; then Why, that is, Why it works in this way; then When, that is, we can use it only when we know why we work in this way. Fortunately, I have discussed in many forums why these languages do not support Double Dispatch directly. Simply put, a language often does not support all functions. Otherwise, the language will become very complex and it will become very difficult to compile its compiler and runtime. Therefore, what functions are supported are actually determined by the target field of a language. When a language can solve a specific problem in a simple and clear way, the language no longer has to provide a built-in solution for the specific problem. These solutions are gradually fixed and assigned a unique name. For example, a common mode in C ++ is Observer. This mode is simple and easy to understand. Other languages may provide native support for Observer, such as delegate in C. The Visitor mode is actually the standard simulation of the Double Dispatch function by C ++. Next, I searched several standard implementations of the Visitor mode and began to compare the differences between the standard implementations of the Double Dispatch and the Visitor mode. This is another habit of mine: practice can often test whether your understanding of a certain knowledge point is biased. Just like my previous misunderstanding of heavy-load resolutions, the process of building my own solutions often gives me a better understanding of why a technology is doing so. By comparing my own solutions and standard solutions, I can find some very sophisticated solutions that others have done and standardize their own implementations. I carefully checked the differences between the instance for selling cars and the standard Visitor mode. Obviously, the standard implementation of the Visitor mode is smarter: in the Sales and SalesManager member functions, the compiler knows the type of the instance to which this points, therefore, if you pass * this as a parameter to the function, you can correctly use the function overload resolution function provided by C ++. This is better than my method for calling different functions in implementation. I don't know how much: 1 class SalesManager: public Sales2 {3 public: 4 virtual double GetDiscountRate (Vehicle & vehicle) 5 {6 return vehicle. getDiscountRate (* this); <---- the compiler knows that * this is a SalesManager type instance, so you can correctly select to accept the reload of the SalesManager type parameter 7} 8 }; in the Vehicle class and Benz class, we only need to create a function overload that receives different types of parameters: 1 class Benz: public Vehicle2 {3 public: 4 virtual double GetDiscountRate (Sales & sales) {return 0.06;} 5 virtual doub Le GetDiscountRate (SalesManager & salesManager) {return 0.066;} 6}; in the standard implementation of the Visitor mode, we need to use Visit () and Accept () the function replaces the above member functions and defines a public interface for the car and sales personnel. Therefore, for the example of selling cars above, the standard Visitor mode is: 1 class Sales; 2 class SalesManager; 3 4 // automotive interface 5 class IVehicle 6 {7 public: 8 virtual double Visit (Sales & sales) = 0; 9 virtual double Visit (SalesManager & sales) = 0; 10}; 11 12 // average car, with a discount of 0.0313 class Vehicle: public IVehicle14 {15 public: 16 virtual double Visit (Sales & sales) {return 0.03;} 17 virtual double Visit (SalesManager & salesManager) {return 0.03;} 18 }; 19 20 // as it is a special Mercedes, you can get a larger discount 21 class Benz: public IVehicle22 {23 public: 24 virtual double Visit (Sales & sales) {return 0.06;} 25 virtual double Visit (SalesManager & salesManager) {return 0.066 ;}26}; 27 28 class ISales29 {30 public: 31 virtual double Accept (IVehicle & vehicle) = 0; 32}; 33 34 // ordinary salesperson, can only sell 35 class Sales: public ISales36 {37 public: 38 virtual double Accept (IVehicle & Vehicle) 39 {40 return vehicle. visit (* this); 41} 42}; 43 44 // sales manager, which offers an additional 45 class SalesManager: public ISales46 {47 public: 48 virtual double Accept (IVehicle & vehicle) 49 {50 return vehicle. visit (* this); 51} 52}; 53 54 int _ tmain (int argc, _ TCHAR * argv []) 55 {56 // two vehicles to be sold 57 Vehicle & vehicle = Vehicle (); 58 Benz & benz = Benz (); 59 60 // ask General Sales about the discounts for the two vehicles 61 Sales * pSales = new Sales (); 62 double r Ate = pSales-> Accept (vehicle); 63 cout <"Sales: The rate for common vehicle is:" <rate <endl; 64 rate = pSales-> Accept (benz); 65 cout <"Sales: The rate for benz is:" <rate <endl; 66 67 // ask the sales manager about the discounts of the two vehicles 68 SalesManager * pSalesManager = new SalesManager (); 69 rate = pSalesManager-> Accept (vehicle ); 70 cout <"Sales Manager: The rate for common vehicle is:" <rate <endl; 71 rate = pSalesMa Nager-> Accept (benz); 72 cout <"Sales Manager: The rate for benz is:" <rate <endl; 73 74 return 0; 75} "How do I expand the Visitor mode?" I asked myself. After all, in enterprise applications, the scalability of each component can largely determine the maintainability and scalability of the system. I noticed that the preceding Visitor mode has two main types: IVehicle and ISales. It is very easy to add a new car type in the Visitor implementation. Derive from IVehicle and implement the corresponding logic: 1 class Fiat: public IVehicle2 {3 public: 4 virtual double Visit (Sales & sales) {return 0.05 ;} 5 virtual double Visit (SalesManager & salesManager) {return 0.06 ;}6}; but it is very difficult to add a type that implements the ISales interface: you need to change all known vehicle types and add overloading specific to the Implementation type of this interface. What should I do if two groups of Chengdu need to be changed? After searching, I also found a mode that allows two types of simultaneous addition: Acyclic Visitor. There are also a series of related patterns, such as Hierachical Visitor Pattern. There seems to be a lot of knowledge related to the Visitor model. I opened the search engine again to continue my self-learning journey. Our colleagues continue to struggle with fruits.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.