The following section describes the C ++ program. Do you think it can be executed smoothly?
// C ++
Class {
Public:
Void Hello (const std: string & name ){
Std: cout <"hello" <name;
}
};
Int main (int argc, char ** argv)
{
A * pa = NULL ;//!!
Pa-> Hello ("world ");
Return 0;
}
Try to output hello world smoothly. Is it strange? In fact, it is not surprising that according to the C ++ object model, non-Virtual Methods of classes will not exist in the object memory layout. In fact, the compiler converts the Hello method into a global function like this:
Void A_Hello_xxx (A * const this, const std: string & name ){
Std: cout <"hello" <name;
}
The object pointer is actually passed implicitly as the first parameter. pa-> Hello ("world") is actually calling A_Hello_xxx (pa, "world "), this code runs smoothly because no pa is used in A_Hello_xxx.
Object message model
If you are studying the C ++ object model, the above discussion can end here, but here I want to continue to discuss this question from another level. Alan Kay, a pioneer in OOP, stressed in summing up the OO features of Smalltalk:
Smalltalk is not only NOT its syntax or the class library, it is not even about classes. i'm sorry that I long ago coined the term "objects" for this topic because it gets got people to focus on the lesser idea. the big idea is "messaging ".
In other words, compared with the concepts of classes and objects, the message model of object interaction is more essential to OOP, because messages focus on interfaces and interactions between objects, what is important when building a large system is not the internal status of objects/modules, but their interaction. According to the message model, the syntax of "cow. Eat (grass)" is to send a message to "cow", the message type is "eat", and the message content is "Grass ". If the strict message model is followed, the above section of C ++ code should be interpreted as sending a Hello message to a NULL object, which obviously should not be executed smoothly. Similar code in Java or C # will throw an empty reference exception, so the design of Java and C # is more in line with the message model.
However, Java and C # do not fully comply with the message model. Let's look at a classic encapsulation problem:
// C #
Public class Account {
Private int _ amount;
Public void Transfer (Account acc, int delta ){
Acc. _ amount + = delta;
This. _ amount-= delta;
}
...
}
The above defines an Account class. The question is, why can the Transfer method of this class directly access the private member _ amount of the acc of another object? Is there any suspicion of damaging the encapsulation? The classic answer to this question is: encapsulation is not broken. encapsulation is divided into static code boundaries based on classes, so that the modification of private code of classes does not affect the outside world, rather than the protection of dynamic objects. This explanation is reasonable of course, but just as the above C ++ code interpretation belongs to the class C ++ object model, this explanation belongs to the class-based static type OOP language. The message model emphasizes the protection of the internal state of an object and can only change its state through a message. Does the object really have a private member such as _ amout for any other object (even for similar objects) are unknown.
What should we do if we strictly abide by the message model to implement the protection of the internal state of the object? Let's look at an example to define a collection class, including: 1. constructor of the Set object; 2. in method: determine whether an element exists. 3. join method: intersection of two sets; 4. union: concatenates two sets. The following is a Javascript implementation:
// Javascript
// Constructor of the Set class
Function Set (){
Var _ elements = arguments;
// In method: determines whether Element e is In the collection.
This. In = function (e ){
For (var I = 0; I <_ elements. length; ++ I ){
If (_ elements [I] = e) return true;
}
Return false;
};
}
// Join method: intersection of two sets
Set. prototype. Join = function (s2 ){
Var s1 = this;
Var s = new Set ();
S. In = function (e) {return s1.In (e) & s2.In (e );}
Return s;
};
// Union method: returns the Union of two sets.
Set. prototype. Union = function (s2 ){
Var s1 = this;
Var s = new Set ();
S. In = function (e) {return s1.In (e) | s2.In (e );}
Return s;
};
Var s1 = new Set (1, 2, 3, 4, 5 );
Var s2 = new Set (2, 3, 4, 5, 6 );
Var s3 = new Set (3, 4, 5, 6, 7 );
Assert (false = s1.Join (s2). Join (s3). In (2 ));
Assert (true = s1.Join (s2). Uion (s3). In (7 ));
If we want to implement Join or Union of the Collection class in the static type OOP language, most of us will directly perform operations on _ elements inside s2 like the Account example above, in the above section, the Javascript-defined Set interface-based access to object s2 fully complies with the message model. It is not necessary to implement the prototype mechanism of the message model Javascript. The real key lies in the advanced functions and closure features of the function. From this example, we can also understand that the advantages of functional programming are not only zero side effects, but also the combination of functions is also the reason why functional programming is powerful.
Method Missing
Next, let's take a deep adventure. Let's think about what if we send a message that cannot be recognized by an object? In this case, a compilation error with undefined method will be returned in static languages such as C ++, Java, and C #. If it is in Javascript, a runtime exception will occur. For example, s1.count () generates a runtime exception: Object # <Set> has no method 'Count '.
The problem of static type language is seldom paid attention to, but there are many articles in dynamic type language. Let's look at the example below:
// Ruby
Builder = Builder: XmlMarkup. new
Xml = builder. books {| B |
B. book: isbn => "14134" do
B. title "Revelation Space"
B. author "Alastair Renault"
End
B. book: isbn => "53534" do
B. title "Accelerando"
B. author "Charles Stross"
End
}
The above section of the DSL Ruby Code creates such an XML file object:
<Books>
<Book isbn = "14134">
<Title> Revelation Space </title>
<Author> Alastair Renault </author>
</Book>
<Book isbn = "53534">
<Title> Accelerando </title>
<Author> Charles Stross </author>
</Book>
</Books>
Builder. books, B. book, B. title is an object method call. Because the element name of XML is arbitrary, it is impossible to define these methods in advance. Similar code is no method exception in Javascript. Why can the above Ruby code be correctly executed? As long as you understand the message model, it is easy to understand. You only need to define a common message processing method and hand over all unclearly defined messages to it for processing, this is the so-called Method Missing mode:
Class Foo
Def method_missing (method, * args, & block)
...
End
End
In addition to implementing DSL, Method Missing can also be used to generate better debugging and error information, embedding parameters in the case of moderate Method names. Currently, Ruby, Python, and Groovy have good support for Method Missing, and even C #4.0 can be implemented using dynamic features.
Summary
This document describes the features of the object message model and compares the C ++ object model, differences between object models and strict message models in class-based static language such as Java and C # are discussed.