Strongly typed business logic (continued)

Source: Internet
Author: User
Tags define abstract

Strongly typed business logic (continued)
As a good example, I hope to explain this technology to people around me. They are not familiar with C ++, but are familiar with C #. So I plan to transplant this technology to C # for better understanding. Just do it.

I created a C # project, copied the code, and started to modify it. This saves some trouble. I immediately encountered a problem. C # has a generic type, which is equivalent to a template, but does not support non-type generic parameters, that is, int currtype. Only one type can be used as a generic parameter. In this way, we cannot use the method played in C ++ (typedef currency <n> ). Second, directly use the class to implement the currency type:

Class RMB

{

Public double _ Val;

}

Class USD

{

Public double _ Val;

}

...

This is too tedious and repetitive. We can use a base class to encapsulate _ Val, and the currency class can be inherited from the base class:

Class currbase

{

Public double _ Val;

}

Class RMB: currbase

{

}

Class USD: currbase

{

}

...

Currency classes are empty. They exist only to create a new type.

Now we are dealing with currency conversion issues. C # Operator = cannot be overloaded, so only one helper function generic asign can be used instead:

Class Utility

{

Public static void asign <t1, T2> (T1 C1, T2 C2)

Where t1: currbase

Where t2: currbase

{

C1. _ Val = C2. _ Val * utility. e_rate [c2.curid (), c1.curid ()];

}

}

This asign function is a generic type. The generic parameters represent two operands respectively, and the function implements currency conversion. In order to be able to retrieve the corresponding exchange rate in the Exchange Rate Table, we must define abstract functions for the base class and currency class:

Public abstract class currbase

{

Public double _ Val = 0;

Public abstract int curid ();

}

Public class RMB: currbase

{

Public override int curid ()

{

Return 0;

}

}

...

The base class declares the curid () abstract method and defines it in the currency class. In this way, you can use a unified method for currency conversion:

Asign (RMB _, USD _);

Okay. Although not so beautiful, it is still practical. However, after looking at the code, I found that there is no need to use generics here. We can use OOP polymorphism to implement the same function:

Public static void asign (currbase C1, currbase C2)

{

C1. _ Val = C2. _ Val * utility. e_rate [c2.curid (), c1.curid ()];

}

But it does not matter. The usage method has not changed, but the code is simpler. After all, using generics is not our fundamental goal, right?

Now it is the turn of the operator. However, I don't know where to define generic operators. According to the requirements in the C # document, operators must be static members of the class. However, my generic operators are for many currency classes and are defined in any of them and seem unfair to other classes. So I decided to define it in the base class:

Public abstract class currbase

{

...

Public static currbase operator + <t1, T2> (T1 C1, T2 C2)

Where t1: currbase

Where t2: currbase

{

...

}

}

The compiler immediately gave me the color: the operator cannot be generic at all! Okay, you can't do it. Let's move on to the next step. Use OOP:

Public abstract class currbase

{

...

Public static currbase operator + (currbase C1, currbase C2)

{

...

}

}

However, this concession is a little outrageous. When I write this code, the compiler does not recognize it:

RMB _ = RMB _ + USD _;

Error message: cs0266: The type "st_in_cs.currbase" cannot be implicitly converted to "st_in_cs. RMB ". There is an explicit conversion (is forced conversion missing ?).

I have to use forced type conversion to pass through:

RMB _ = (RMB) (RMB _ + USD _);

That's too exaggerated. Therefore, I was forced to define operator + in each currency class:

Class RMB: currbase

{

...

Public RMB operator + (RMB C1, USD C2)

{

...

}

Public RMB operator + (RMB C1, UKP C2)

{

...

}

...

}

This is incredible. I have to define a + operator for each currency class. The total number of + operators will be the square of the number of currency classes! Each of the other operators is the square of the number of currency classes. I can't stand it!

Fortunately, the cute OOP provides us with a straw so that each operator in each currency class only needs to define one:

Class RMB: currbase

{

...

Public RMB operator + (RMB C1, currbase C2)

{

...

}

...

}

In this way, any currency class can be involved in the operation as the second operand, and the operator only needs to define one. This workload is acceptable for a programmer who is comfortable with the competition. Good, no error in code:

RMB _ = RMB _ + USD _;

But when I wrote the following code, the compiler began to complain again:

UKP _ = RMB _ + USD _;

We still need to display the conversion unless we define the implicit type conversion operator for UKP:

Class UKP

{

...

Public Static Implicit operator UKP (RMB V)

{

...

}

...

}

If you have RMB, you may not have to. You still have to have USD, jpd, and so on .... In this case, we must define all types conversion operators for each currency class. It is also a combination explosion. By now, I have already been poor. Who makes C # Not support = Operator Overloading and operator templatification. No way, you have to endure it.

However, if we can reduce the requirements, things will still turn around. If we do not use operators, but use the static member Method for currency calculation, we can save a lot of code:

Public class Utility

{

Public static T1 asign <t1, T2> (T1 C1, T2 C2)

Where t1: currbase, new ()

Where t2: currbase

{

C1. _ Val = C2. _ Val * utility. curr_rate [c2.curid (), c1.curid ()];

Return C1;

}

Public static T1 add <t1, T2> (T1 C1, T2 C2)

Where t1: currbase, new ()

Where t2: currbase

{

T1 T = new T1 ();

T. _ Val = C1. _ Val + C2. _ Val *

Utility. curr_rate [c2.curid (), c1.curid ()];

Return T;

}

...

}

Here, I still use generics because these functions need to return a value. Only generic functions can return a clear type to avoid the requirement of forced conversion. Therefore, the assignment and calculation code becomes:

Asign (jpd _, asign (UKP _, add (RMB _, USD _); // jpd _ = UKP _ = RMB _ + USD _

It is really ugly, but in order to be able to write less code, this can only be done.

Well, I tried my best to implement a strong and computable currency system in C. Although I can finally develop a group of currency classes with the same effect as C ++ in C # (except for the value assignment operation), I need to write a lot of code to implement various computing operations, and type conversion between currency classes (combination explosion ). Compared with a total of 200 lines of code in C ++, it is indeed complicated and bloated.

I don't want to write this article as "C ++ vs C #" (although I'm glad to see that C ++ is better than C ). I want to display the results of different technologies through such a code optimization task. At the same time, we can also compare the two implementations to understand the role of generic programming and the requirements of generic programming for languages.

Without a doubt, C ++ uses pure generic programming, so it can be highly abstract. The problem is modeled in the simplest form by using every point of information that can be abstracted. C #, the language with OOP as the core, has weak support for generics. More importantly, the application of C # generics to generic parameters is heavily dependent on the constraints of generic parameters (where ). If no WHERE clause is available, C # treats generic parameters as object types. In this case, generic parameters are meaningless (I cannot access members of this type ). If the WHERE clause is available, C # requires that the generic parameters have an inheritance relationship with the type specified in the WHERE clause (for example, T1 in asign must be an inheritance class of currbase ). The use of generic parameters in generic functions is also limited to the constraint type (namely, currbase. Therefore, we can directly use the asign function with the base class (currbase) as the parameter to replace asign of the generic version. Due to the inheritance requirements of C # on generic parameters, generics are trapped and cannot play the role of applications. Due to these problems, C ++ adopts the current template instead of the generic mode that is the same as C.

Some people may say that since OOP can solve the problem (asign does not need generics at first, but still needs generics to control the type of returned values), why does it need GP?

The answer to this question is also given. Because C # Generics do not support non-Generic parameters, we are forced to use the traditional OOP Method: Using the base class to implement the currency class and defining the currency class to create new types, enable strong currency typing and use virtual functions to provide unique currency information. On this layer alone, the OOP method is much inferior to the GP method. GP only defines one template, and all the currency types are generated by a typedef statement without more code. The OOP method requires that you write a class for each currency, and the amount of code is much higher than the GP method.

After that, C ++ has implemented currency operations by reloading a set of operator templates. The currency template and the typedef that generates the currency type do not need to be changed. In C #, a large number of type-specific operators are forced to be defined because they do not support generic operators. All operation operators must be reloaded once in each currency class. The transformation operator must be reloaded n-1 times in each currency class.

In other words, with n currencies and M operators (including transformation operators), we need to define n + 1 classes (including base classes ), N x m + n x (n-1) operators. Suppose n = 10, M = 10, then a total of 11 class definitions are required, and 190 operators are overloaded! If 20 lines of code are required for each class definition and 5 lines of code are required for each operator overload, a total of 1170 lines of code are required. If the number of currencies increases, the total number of codes increases in a geometric level.

The above calculation shows that although OOP can solve the problem and achieve our goal, the development and maintenance volume is unbearable. Moreover, it is very difficult to expand the OOP method. As the scale of the system expands, the expansion will become more and more difficult. All of these indicate that although OOP is the star of software engineering, there are many problems that OOP cannot solve or are hard to solve in actual situations. This is why leading figures in the industry are constantly expanding and reinforcing generic programming.
 

Related Article

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.