Analysis of polymorphisms in Delphi
1What is polymorphism? 2
1.1 Concept 2
1.2 The meaning of polymorphism 2
1.3 How to implement polymorphism in delphi? 2
1.3.1 Inheritance 2
1.3.2 Virtual methods, dynamic methods and abstract methods, VMT/DMT, static binding and dynamic binding 2
1.3.3 Overload and polymorphism 2
1.4 Discussion on polymorphic species 2
1.4.1 Two-level polymorphism 2
1.4.2 Unsafe polymorphism 2
2 Applications of polymorphism in VCL2
2.1 Construction and destructuring method 2
2.2 Tstrings2
2.3 Others (please add soul) 2
Abstract Polymorphism is the soul of object-oriented. Understanding polymorphism is one of the keys to master object-oriented technology. This article focuses on analyzing the basic principles of polymorphism, the essence of polymorphism, and its application in VCL.
Keyword polymorphism, inheritance, object-oriented, VCL, virtual method, override
question
Polymorphism is the soul of object-oriented, and understanding polymorphism is one of the keys to master object-oriented technology. But what exactly is polymorphism? What is the meaning of polymorphism? How to achieve polymorphism? I can understand the concept of polymorphism, but I don’t know how to use it and when to use it? Please read this article in detail.
Expert analysis
The world (object, i.e., object) is ever-changing; but in the computer world, there are only one line of machine instructions, and the two seem to be irrelevant. In the past, it was very difficult to describe the real world well in computer language. Although some people write object-oriented programs in C language, I dare to conclude that the writing method is extremely tedious. Until the emergence of object-oriented (Oriented-Object (OO), everything changed accordingly, and the entire software industry was earth-shaking. Changes, starting from the changes in programming languages, a series of object-oriented programming languages (OOPs) such as SmallTalk, C++, java, Object Pascal, C#, etc.; various object-oriented development tools have also appeared, such as VC, Delphi, and BCB. , JBuilder, etc., and many excellent class libraries have emerged, such as VCL, .net Framework and some commercial class libraries; then they have developed to object-oriented design (OOD), object-oriented analysis (OOA), and object-oriented database (OODB) ), object-oriented technology has almost penetrated the entire software field, and programmers' way of thinking has also undergone fundamental changes! In the eyes of some OO purification theorists, everything is an object! Although I don't quite agree with this view. But I think this method is most in line with people's thinking habits. It allows programmers to concentrate on thinking about business logic, and the computer can complete the conversion of object-oriented to machine instructions (completed by object-oriented compilers). The programmer's brain From then on, I was liberated! This is a revolution!
The core content of object-oriented is object, encapsulation, inheritance, polymorphism and message mechanisms. Polymorphism is to describe the diversity of the real world and is also the most important feature in object-oriented. It can be said that if you do not master polymorphism, you will Not really mastering object-oriented technology.
1What is polymorphism?
1.1 Concept
There are many different opinions on the concept of polymorphism, and the following are several representative statements:
“This ability to manipulate more than one type with a pointer or a reference to a base class speak of as polymorphism” (C++ PRimer, page 838). That is, the ability to operate objects of multiple classes (base class and its derived classes) using pointers/references to base classes is called polymorphism. It is considered from the perspective of language implementation.
"polymorphism provides another dimension of separation of interface from implementation, to decouple what from how" ("Think in Java" 3rd edtion), that is, polymorphism provides another kind of separation interface and implementation (i.e., to "what to do" and "how to do Do a scale of “separately”. It is considered from a design perspective.
“The ability to use the same expression to denote different Operations is referred to as Polymorphism” (Object-Oriented Methods Principles & Practice 3rd Edition, p. 16). Simply put, polymorphism is "same expression, different operations", or it can be said to "same command, different operations". This is from the perspective of object-oriented semantics.
The three statements explain the essence of polymorphism from different perspectives. The third statement is particularly accurate. The following focuses on analyzing the third statement.
Let me explain the meaning of this sentence first:
Same expression—function call
Different operations—There are different operations according to different objects.
For example, in a company, there are various employees with different responsibilities (programmers, salesmen, document managers, etc.) who do different things when they "go to work" (can also be regarded as a kind of business logic) , We abstract their respective work as "to work", and the relationship is as follows:
staff
/ | / ——Inheritance relationship
Programmer salesperson document manager
Once the working hours come every day, it is equivalent to issuing a command like this:
"Employees. Start Working" (same expression)
After each employee receives this command (the same command), he "starts to work", but what they do is their respective work, the programmer starts "Coding", the salesperson starts "contact business", and the cultural manager Let’s start “organizing documents”. That is, "the same expression (function call), different operations (execute according to different objects during the runtime period)."
From the perspective of language implementation of polymorphism, polymorphism is achieved by calling its virtual method by base class pointers or references to objects pointing to derived classes. The following is the implementation of the Object Pascal language
TEmployee=class //Abstract employees into an abstract class
public
procedure startWorking;virtual;abstract;
{Abstract functions (i.e. pure virtual functions in C++), do nothing, the actual meaning is to reserve an interface first. Overload implements it in its derived class. }
end;
TProgramer=class(TEmployee) //Programmer
public
procedure startWorking; override;
end;
TBusinessMan=class(TEmployee) //Salesperson
public
procedure startWorking; override;
end;
TDocManager=class(TEmployee) //Text Manager
public
procedure startWorking; override;
end;
procedure TProgramer.startWorking;
Begin
showmessage('coding');
end;
{ TbusinessMan }
procedure TbusinessMan.startWorking;
Begin
showmessage('Linking Business');
end;
{ TDocManager }
procedure TDocManager.startWorking;
Begin
showmessage('Managing Document');
end;
procedure TForm1.Button1Click(Sender: TObject);
const
eNum=3;
var
Employee:array of TEmployee;
i:integer;
Begin
setLength(Employee,eNum);
Employee[0]:=TProgramer.Create;
//Refer to the base class employee[0] to point to the TProgramer object just created
Employee[1]:=TBusinessMan.Create;
//Refer to the base class employee[1] to point to the TBusinessMan object you just created
Employee[2]:=TDocManager.Create;
//Refer to the base class employee[2] to point to the TDocManager object just created
for i:=0 to Length(Employee)-1 do
Employee[i].startWorking; //Dynamic binding of corresponding methods according to the actual object type during the run period.
{From the perspective of language implementation polymorphism, polymorphism is implemented by calling its virtual method by base class pointer or reference object pointing to derived classes. Employee [] refers to an array for the base class object, and its members point to different derived class objects respectively. When calling virtual methods, polymorphism is implemented}
end;
Give it a try
You can type in some of the above code (or demo programs), compile and run, and click the button to see the magic effect of polymorphism.
1.2 The meaning of polymorphism
The meaning of encapsulation and inheritance is that they implement code reuse, while the meaning of polymorphism is that it implements interface reuse (same expression). The benefit of interface reuse is that the program is easier to expand, code reuse is more convenient and more capable. Flexibility can truly reflect the real world.
For example, in order to better manage, programmers are divided into C++ programmers and Delphi programmers. …
staff
/ | / ——Inheritance relationship
Programmer salesperson document manager
/ / ——Inheritance relationship
C++ programmer Delphi programmer
After the programmer added the two derived classes of TCppProgramer and TDelphiProgramer, the call method still did not change, and it was still "employees. Start working", which was described with Object Pascal:
…
setLength(Employee,eNum+2);
Employee[ENum]:=TCppProgramer.create;
//Create a TcppProgramer object and point the base class reference employee[ENum] to it
Employee[eNum+1]:=TDelphiProgramer.Create;
…
{Sectors. Begin to work}
for i:=0 to Length(Employee)-1 do
Employee[i].startWorking; //It is still the same calling method (because the interface has not changed).
…
1.3 How to implement polymorphism in delphi?
The necessary conditions for achieving polymorphism are inheritance, virtual methods, dynamic binding (or lag-back binding). How to implement polymorphism in Delphi?
1.3.1 Inheritance
Inheritance refers to the "AKO (A Kind Of, is a)" relationship between a class, such as a programmer "is a" employee, indicating an inheritance relationship. In Delphi, only single inheritance is supported (no consideration of multiple inheritance implemented by interfaces). Although this does not have the flexibility of multiple inheritance, it brings us great benefits, so we can appear base classes at any time The object can be replaced by derived class objects (or vice versa). This is the so-called "polymorphic permutation principle". We can assign the address of the derived class object to the pointer/reference of the base class to implement multiple state provides prerequisites.
hint
In UML:
AKO: A Kind Of means inheritance relationship
APO: A Part Of represents a combination relationship
IsA: IsA represents the relationship between an object and a class to which it belongs.
1.3.2 Virtual methods, dynamic methods and abstract methods, VMT/DMT, static binding and dynamic binding
For all methods, there is no trace in the object. Its method pointer (entry address) is stored in the class, while the actual code is stored in the code segment. For static methods (non-virtual methods), the compiler directly determines the entry address of the object method based on the object's reference type at compile time, which is called static binding; for virtual methods, since it may be overloaded, at compile time The compiler cannot determine the actual class to which it belongs, so the entry address of the method can only be determined through the VMT table entry address (i.e. the first four bytes of the object) during the runtime. This is called dynamic binding (or lag-bundling).
Virtual method
A virtual method represents a method that can be overridden. If it is not declared as an abstract method, it requires a default implementation in the base class. In addition to storing its own virtual method pointer, the class also stores virtual method pointer of all base classes.
Declaration method:
procedure method name; virtual;
This is equivalent to telling the Delphi compiler:
This method can be overloaded in the derived class, and the virtual method will be still after overloading.
Do not determine the entry address of the method during the compilation period. During the runtime, the entry address of the method is determined through dynamic binding.
Provide a default implementation in the base class. If the method is not overridden in the derived class, the default implementation in the base class is used.
Dynamic method
Dynamic methods and virtual methods are essentially the same. Unlike virtual methods, dynamic methods only store their own dynamic method pointers in the class. Therefore, virtual methods use more memory than dynamic methods, but they execute faster. But this is completely transparent to users.
Declaration method:
procedure name;dynamic;
Abstract Methods
A special virtual method, which does not need to provide a default implementation in the base class, is just used for a calling interface, which is equivalent to a pure virtual function in C++. Classes containing abstract methods are called abstract classes.
Declaration method:
procedure name;virtual;abstract;
VMT/DMT
In Delphi, virtual method table (VMT) is actually not physically. It is to better explain polymorphism, and artificially gives it a logical definition. In fact, it is just a virtual in the class A collection of methods' addresses, which also includes virtual methods of its base class. The "Vmt entry address" stored in the first four bytes of the object is actually the address of the class it belongs to (see the Demo program). With the actual class, the virtual method address can be found.
Obj (object name) The class to which the actual object belongs
Vmt entry address
Data Members
Vmt entry address of class virtual method table
Data member template information
Static methods, etc.
Virtual method (VMT)
Dynamic Method (DMT)
Figure 3 The relationship between object names, objects and classes
DMT is similar to VMT, and is also a logical concept. The difference is that only the dynamic method pointer of the class is saved, but there is no address of the dynamic method of the base class, which saves some memory, but the speed is not as fast as that of virtual methods. It is a strategy of sacrificing time and exchanging space, and is generally not recommended.
Quote the above example to explain:
Employee[i].startWorking;
Employee[i] is an object reference of the base class Templeeyee. As the above program knows, it may point to a Tprogramer object, or it may point to a TbusinessMan, or it may be other objects, and these are uncertain. Dynamic, so the actual object cannot be known during compilation, so the method address cannot be determined. During the run period, of course, I know the "true face of Lushan" of the object. Based on the content of the first four bytes of the actual object, that is, the entry address of the virtual method table VMT, I find the function to be called, that is, polymorphism is realized.
1.3.3 Overload and polymorphism
Many netizens believe that function overloading is also a kind of polymorphism, but it is not. For "different operations", overloading cannot provide the same call method. Although the function name is the same, its parameters are different! The premise of implementing polymorphism is the same expression! For example, Employee[i].startWoring, overloaded calls have different parameters or parameter types. Overloading is just a language mechanism, and there are also overloading in C, but C has no polymorphism and C is not an object-oriented programming language. Unless the overloading function is also a virtual method, the compiler can determine the entry address of the function based on the parameter type, or static binding! Quote the father of C++, "Don't be stupid. If it is not dynamic binding, it is not polymorphism."
1.4 Discussion on polymorphic species
1.4.1 Two-level polymorphism
Object level: Use base class pointer/reference to point to its derived class object, and call virtual methods (or dynamic methods, abstract methods), which is the most commonly used one.
Class level: Use class references (references to classes rather than objects) to point to derived classes, call virtual class methods (or dynamic class methods, abstract class methods), and are commonly used in the polymorphisms created by objects (because the constructor is a kind of " For special "type methods, please refer to my other work "Analysis of the structure and destruction in Delphi", Section 2.1).
hint
Class reference is a reference variable of the class itself, not a class, nor an object reference. Just like the object name represents an object reference, the class name represents a class reference, because in Delphi, the class is also processed as an object. A class reference type is the type of a class reference, and the declaration method of a class reference type:
Class reference type name = class of class name
We can see many class reference declarations in the VCL source code, such as:
TClass=class of Tobject;
TComponentClass=class of Tcomponent;
TControlClass=class of Tcontrol;
Notice
In a class method, the self implicit in the method is a class reference, not an object reference.
1.4.2 Unsafe polymorphism
Polymorphism can also be implemented using derived class pointers/references to base class objects! Although this is a wrong way to use it:
procedure TForm1.btnBadPolyClick(Sender: TObject);
var
cppProgramer:TCppProgramer;//Define a cpp programmer reference, a reference to a derived class!
Begin
{*****************************statement******************* ******************
Polymorphism implemented with derived class pointers/references to base class objects. It is a pathological polymorphism!
This polymorphic method is like a very small thing (base class object) that is covered with a powerful one
The appearance of the product (derived class references) thus brings many potential insecurity factors (such as access exceptions), so
With almost no value. "File" an example aims to illustrate the nature of polymorphism in Delphi, the nature of polymorphism: use a legal (usually the base class) pointer/reference to operate the object, according to the actual object during the runtime, To execute different operation methods, or to a more vivid statement: the object itself decides its own operation method. The compiler only needs to issue a command to do what (what), and don't care about how (how), "how" Be responsible for the object. This enables the separation of interfaces and implementations, making interface reuse possible.
********************************************************* *********************************}
cppProgramer:=TCppProgramer(TProgramer.Create);
{In order to achieve this pathological polymorphism, the object reference is cast to the TCppProgramer type,
thus escapes the compiler's check}
cppProgramer.startWorking;
{TProgramer.startWorking called instead of TcppProgramer.startWorking
This is the polymorphism implemented with derived class pointers/references to the base class object. }
cppProgramer.Free;
cppProgramer:=TCppProgramer(TDocManager.Create);
cppProgramer.startWorking;
{The call is TDocManager.startWorking,
This is the polymorphism implemented with derived class pointers/references to the base class object. This method is extremely unsafe.
And there is no need}
cppProgramer.Free;
end;
Give it a try
In order to obtain this kind of perceptual understanding of polymorphism, it is recommended to try it. The above mentioned that this method of use will have potential insecure (such as access exceptions), and the above program runs without any errors. Think about why? Under what circumstances will an access exception occur? Write an example of an access exception and you will gain more. (Refer to the Demo program)
2 Applications of polymorphism in VCL
2.1 Construction and destructuring methods
Polymorphism of construction method
Since the constructor can be regarded as a "special" class method, all derived classes after the Tcomponent are redefined as virtual class methods, so to realize the polymorphism of the constructor, you must use class references. In Delphi There is a classic example, in each project file there is a code similar to the following:
application.CreateForm(TForm1, Form1);
Definition of its method:
procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
var// InstanceClass is a class reference.
Instance: TComponent;
Begin
Instance := TComponent(InstanceClass.NewInstance);
{NewInstance method declaration: class function NewInstance: TObject; virtual; (system unit 432 line) is a class method and also a virtual method. We call it a virtual class method. InstanceClass is a class reference that implements class-level polymorphism, thus realizing interface reuse for creating components}
TComponent(Reference) := Instance;
try
Instance.Create(Self);//Call the constructor to initialize it
except
TComponent(Reference):= nil;//Eliminate the "wild" pointer! good
raise;
end;
{If the created window and there is no main form yet, set the just created form as the main form}
if (FMainForm = nil) and (Instance is TForm) then
Begin
TForm(Instance).HandleNeeded;
FMainForm := TForm(Instance);//Set the main form
{ In fact, setting the main form in the project options (project->options) is actually the corresponding form statement in the project file before all form creation statements. }
end;
end;
2) For the polymorphism of the destructuring method, please refer to "Analysis of the structure and destruction in Delphi", Section 3.3
2.2 Tstrings
String array processing is very common in Delphi controls, usually with some Items attributes, which are particularly convenient for us to use (because they all use the same interface), thanks to the design of the string array architecture in Delphi. This is a successful design.
Since many controls use string arrays, such as ComboBox, TstringGrid, etc., but the string arrays in each control are different, Delphi abstracts the string arrays, thus many related classes appear . The base class Tstrings only provides an interface for various calls, and the specific implementation can be implemented entirely from its derived classes. Therefore, Tstrings is defined as an abstract class.
Let’s take a look at the definition of common methods of the base class TStrings class (see Classes unit line 442):
TStrings = class(TPersistent)
protected
...
function Get(Index: Integer): string; virtual; abstract;
procedure Put(Index: Integer; const S: string); virtual;
function GetCount: Integer; virtual; abstract;
…
public
function Add(const S: string): Integer; virtual; //The actual call is Insert
{Add a string S to the end of the string list}
procedure AddStrings(Strings: TStrings); virtual;
{Add Strings to the end of the String list}
procedure Insert(Index: Integer; const S: string); virtual; abstract;
{Abstract method, insert a new string S at the Index position}
procedure Clear; virtual; abstract;
{Clear all strings}
procedure Delete(Index: Integer); virtual; abstract;
{Delete a string at a certain location}
function IndexOf(const S: string): Integer; virtual;
{get the position of S in the string list}
function IndexOfName(const Name: string): Integer; virtual;
{Returns the position of the first string with the form Name=Value with the specified name part}
function IndexOfObject(AObject: TObject): Integer; virtual;
{get the position of the object with the object named AObject: in the string list}
procedure LoadFromFile(const FileName: string); virtual;
{Fills the list with the lines of text in a specified file}
procedure LoadFromStream(Stream: TStream); virtual;
{Fills the list with lines of text read from a stream}
procedure SaveToStream(Stream: TStream); virtual;
{Writes the value of the Text property to a stream object}
property Strings[Index: Integer]: string read Get write Put; default;
{References the strings in the list by their positions}
property Values[const Name: string]: string read GetValue write SetValue;
{Represents the value part of a string associated with a given Name, on strings with the form Name=Value.}
…
end;
From the definition of Tstrings, it can be seen that most of its Protected and Public methods are virtual methods or abstract methods. (Please add some, TstringList->TstringGridString)
2.3 Others (please add soul)
If you don't understand polymorphism yet, please remember the essence of polymorphism:
"Same expression, different operations" (it's that simple)
From the perspective of the implementation of the OOP language, polymorphism is to use the pointer/reference of the base class to operate (derived class) objects, and perform different operation methods according to the actual object during the run period; or to change to a more vivid statement: The object decides its own operation method. The compiler only needs to issue commands on what to do (what to do), and don't care about how to do it. "How to do it" is the object itself. This enables the separation of interfaces and implementations, making interface reuse possible.
In fact, polymorphism is simple! So what should you pay attention to when using polymorphism? Here are my two suggestions:
Analyze business logic, then abstract related things into "objects", and then encapsulate business logic using object methods. Declare some operations with polymorphism as virtual methods in the base class, and declare them as virtual Abstract Methods for those that are not necessary to implement in the base class, and then overload them in their derived classes. It (Override) is called with references/pointers of the base class when used, which naturally realizes polymorphism in the real world. Remember not to achieve polymorphism for the sake of polymorphism. This is a formal approach and has no meaning.
Since there is a natural "coupling" relationship between base classes and derived classes, modifying base classes will lead to "reaching the whole body", which will be very troublesome! Therefore, we must try to weaken the functional implementation of the base class, design it as an "abstract class" if necessary, and ensure a stable interface. This can be achieved by reserving some redundant virtual functions (or abstract functions).
Related questions
Discuss the polymorphism of Delphi: http://www.delphhibbs.com/delphhibbs/dispq.asp?lid=1753965
About polymorphism: http://www.delphhibbs.com/delphhibbs/dispq.asp?lid=1854895
What is polymorphism? What are the uses in daily programming? http://www.delphibbs.com/delphibbs/dispq.asp?lid=960465
What is the difference between overload and override? Please teach? http://www.delphibs.com/delphibs/dispq.asp?lid=296739
Issue that the pointer of the derived class points to the base class object http://www.delphibs.com/delphibs/dispq.asp?lid=2104106
(The last question was mentioned on DelphiBBS when I was deeply studying polymorphism, which caused heated discussion. I suggest you take a look)