以前学习面向对象的时候,对这消息的概念一直很不解,对多态也是一知半解。最近在看书的过程中,逐渐明白了这两个概念。现将对两者的理解整理如下:
OOP使得对象具有交互能力的主要模型就是消息传递模型。对象被看成用传递消息的方式互相联系的通信实体,它们既可以接受、也可以拒绝外界发来的消息。一般情况下,对消接收它能够识别的消息,拒绝它不能识别的消息。对于一个对象而言,任何外部代码都不能以任何不可预知或事先不运行的方式与这个对象进行交互。
发送一条消息至少应给出一个接收 对象的名字和要发给这个对象的那条 消息的名字。通常,消息的名字就是这个对象中外界可知的某个方法的名字。在消息中,经常还有 一组参数(也就是那个方法所要求的参数),将外界的信息传给这个对象。
对于一个类来说,它关于方法界面的定义规定了实例的消息传递协议。
通俗地说,消息就是类中声明为public的方法(接口)。而消息传递就是在一个对象的某个方法中调用另一个对象中的某个方法。
Cardelli and Wegner把多态从语言设计的角度分为通用多态( universal )和特定多态( ad-hoc)。
通用多态又分为参数( Parametric )多态和包含( Inclusion)多态;特定多态包含重载(overloading )
多态和强制(coercion )多态。
具体分类如下图所示:
|-- parametric
|-- universal --|
| |-- inclusion
polymorphism --|
| |-- overloading
|-- ad hoc --|
|-- coercion
对于通用多态,相同的代码应用于不同的类型;而在特定多态里,不同的代码可由不同的类型执行。
我们编写彼此无关的具体类Car和Airplane(它们都有一个run()成员函数)。
// static_poly.h
#include <iostream>
//具体类Car
class Car
{
public:
void run() const
{
std::cout << "run a car/n";
}
};
//具体类Airplane
class Airplane
{
public:
void run() const
{
std::cout << "run a airplane/n";
}
};
// static_poly_1.cpp
#include <iostream>
#include <vector>
#include "static_poly.h"
// 通过引用而run任何vehicle
template <typename Vehicle>
void run_vehicle(const Vehicle& vehicle)
{
vehicle.run(); // 根据vehicle的具体类型调用对应的run()
}
int main()
{
Car car;
Airplane airplane;
run_vehicle(car); // 调用Car::run()
run_vehicle(airplane); // 调用Airplane::run()
}
现在Vehicle用作模板参数而非公共基类对象(事实上,这里的Vehicle只是一个符合直觉的记号而已,此外别无它意)。经过编译器处理后,我们最终会得到run_vehicle<Car>()和 run_vehicle<Airplane>()两个不同的函数。
我们可以定义一个抽象基类Vehicle和两个派生于Vehicle的具体类Car和Airplane:
// dynamic_poly.h
#include <iostream>
// 公共抽象基类Vehicle
class Vehicle
{
public:
virtual void run() const = 0;
};
// 派生于Vehicle的具体类Car
class Car: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a car/n";
}
};
// 派生于Vehicle的具体类Airplane
class Airplane: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a airplane/n";
}
};
客户程序可以通过指向基类Vehicle的指针(或引用)来操纵具体对象。通过指向基类对象的指针(或引用)来调用一个虚函数,会导致对被指向的具体对象之相应成员的调用:
// dynamic_poly_1.cpp
#include <iostream>
#include <vector>
#include "dynamic_poly.h"
// 通过指针run任何vehicle
void run_vehicle(const Vehicle* vehicle)
{
vehicle->run(); // 根据vehicle的具体类型调用对应的run()
}
int main()
{
Car car;
Airplane airplane;
run_vehicle(&car); // 调用Car::run()
run_vehicle(&airplane); // 调用Airplane::run()
}
此例中,关键的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针,因而无法在编译期决定使用哪一个版本的run()。在运行期,为了分派函数调用,虚函数被调用的那个对象的完整动态类型将被访问。这样一来,对一个Car对象调用run_vehicle (),实际上将调用Car::run(),而对于Airplane对象而言将调用Airplane::run()。
// overload_poly.cpp
#include <iostream>
#include <string>
// 定义两个重载函数
int my_add(int a, int b)
{
return a + b;
}
int my_add(int a, std::string b)
{
return a + atoi(b.c_str());
}
int main()
{
int i = my_add(1, 2); // 两个整数相加
int s = my_add(1, "2"); // 一个整数和一个字符串相加
std::cout << "i = " << i << "/n";
std::cout << "s = " << s << "/n";
}
根据参数列表的不同(类型、个数或兼而有之),my_add(1, 2)和my_add(1, "2")被分别编译为对my_add(int, int)和my_add(int, std::string)的调用。实现原理在于编译器根据不同的参数列表对同名函数进行名字重整,而后这些同名函数就变成了彼此不同的函数。比方说,也许某个编译器会将my_add()函数名字分别重整为my_add_int_int()和my_add_int_str()。
要注意的是并不是任意两个类型之间都可以进行强制多态。在不同类型之间实现强制多态,通常需要执行不同的转换操作。强制多态的原则是:将值集较小(即占用 存储空间较小)的类型,变换成值集包含了前者(即占用存储空间较大)的类型,反之,应当注意可能发生的对值的损伤(特别是在使用Casting时)。
有时,强制多态与重载多态(操作符重载)是混合出现的。例如,
对于表达式
1+2;
1.0+2;
1+2.0;
1.0+2.0;
中出现的多态,就会有多种解释:
·操作符+有四种过载多态;
·操作符+只有一种:double * double -> double, 要将参与运算的整数强制变换成浮点数;
·操作符+有两种过载多态:int * int -> int 和 double * double -> double,要将混合运算中的整数强制变换成浮点数。
以上的强制多态主要是通过操作符重载所表现出来的多态,另外,还有两种不同的方法实现强制多态,它们分别是,1)通过类型转换构造函数(type conversion constructor)(类A的构造函数接收一个类B的对象),2)通过类型转换操作符(type conversion operator)(类B中的类型转换操作符返回一个类A的对象)。
例子(C++)如下:
- 消息和消息传递
OOP使得对象具有交互能力的主要模型就是消息传递模型。对象被看成用传递消息的方式互相联系的通信实体,它们既可以接受、也可以拒绝外界发来的消息。一般情况下,对消接收它能够识别的消息,拒绝它不能识别的消息。对于一个对象而言,任何外部代码都不能以任何不可预知或事先不运行的方式与这个对象进行交互。
发送一条消息至少应给出一个接收 对象的名字和要发给这个对象的那条 消息的名字。通常,消息的名字就是这个对象中外界可知的某个方法的名字。在消息中,经常还有 一组参数(也就是那个方法所要求的参数),将外界的信息传给这个对象。
对于一个类来说,它关于方法界面的定义规定了实例的消息传递协议。
通俗地说,消息就是类中声明为public的方法(接口)。而消息传递就是在一个对象的某个方法中调用另一个对象中的某个方法。
- 多态
Cardelli and Wegner把多态从语言设计的角度分为通用多态( universal )和特定多态( ad-hoc)。
通用多态又分为参数( Parametric )多态和包含( Inclusion)多态;特定多态包含重载(overloading )
多态和强制(coercion )多态。
具体分类如下图所示:
|-- universal --|
| |-- inclusion
polymorphism --|
| |-- overloading
|-- ad hoc --|
|-- coercion
对于通用多态,相同的代码应用于不同的类型;而在特定多态里,不同的代码可由不同的类型执行。
- 参数多态
我们编写彼此无关的具体类Car和Airplane(它们都有一个run()成员函数)。
// static_poly.h
#include <iostream>
//具体类Car
class Car
{
public:
void run() const
{
std::cout << "run a car/n";
}
};
//具体类Airplane
class Airplane
{
public:
void run() const
{
std::cout << "run a airplane/n";
}
};
// static_poly_1.cpp
#include <iostream>
#include <vector>
#include "static_poly.h"
// 通过引用而run任何vehicle
template <typename Vehicle>
void run_vehicle(const Vehicle& vehicle)
{
vehicle.run(); // 根据vehicle的具体类型调用对应的run()
}
int main()
{
Car car;
Airplane airplane;
run_vehicle(car); // 调用Car::run()
run_vehicle(airplane); // 调用Airplane::run()
}
现在Vehicle用作模板参数而非公共基类对象(事实上,这里的Vehicle只是一个符合直觉的记号而已,此外别无它意)。经过编译器处理后,我们最终会得到run_vehicle<Car>()和 run_vehicle<Airplane>()两个不同的函数。
- 包含多态
我们可以定义一个抽象基类Vehicle和两个派生于Vehicle的具体类Car和Airplane:
// dynamic_poly.h
#include <iostream>
// 公共抽象基类Vehicle
class Vehicle
{
public:
virtual void run() const = 0;
};
// 派生于Vehicle的具体类Car
class Car: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a car/n";
}
};
// 派生于Vehicle的具体类Airplane
class Airplane: public Vehicle
{
public:
virtual void run() const
{
std::cout << "run a airplane/n";
}
};
客户程序可以通过指向基类Vehicle的指针(或引用)来操纵具体对象。通过指向基类对象的指针(或引用)来调用一个虚函数,会导致对被指向的具体对象之相应成员的调用:
// dynamic_poly_1.cpp
#include <iostream>
#include <vector>
#include "dynamic_poly.h"
// 通过指针run任何vehicle
void run_vehicle(const Vehicle* vehicle)
{
vehicle->run(); // 根据vehicle的具体类型调用对应的run()
}
int main()
{
Car car;
Airplane airplane;
run_vehicle(&car); // 调用Car::run()
run_vehicle(&airplane); // 调用Airplane::run()
}
此例中,关键的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针,因而无法在编译期决定使用哪一个版本的run()。在运行期,为了分派函数调用,虚函数被调用的那个对象的完整动态类型将被访问。这样一来,对一个Car对象调用run_vehicle (),实际上将调用Car::run(),而对于Airplane对象而言将调用Airplane::run()。
- 重载多态
// overload_poly.cpp
#include <iostream>
#include <string>
// 定义两个重载函数
int my_add(int a, int b)
{
return a + b;
}
int my_add(int a, std::string b)
{
return a + atoi(b.c_str());
}
int main()
{
int i = my_add(1, 2); // 两个整数相加
int s = my_add(1, "2"); // 一个整数和一个字符串相加
std::cout << "i = " << i << "/n";
std::cout << "s = " << s << "/n";
}
根据参数列表的不同(类型、个数或兼而有之),my_add(1, 2)和my_add(1, "2")被分别编译为对my_add(int, int)和my_add(int, std::string)的调用。实现原理在于编译器根据不同的参数列表对同名函数进行名字重整,而后这些同名函数就变成了彼此不同的函数。比方说,也许某个编译器会将my_add()函数名字分别重整为my_add_int_int()和my_add_int_str()。
- 强制多态
要注意的是并不是任意两个类型之间都可以进行强制多态。在不同类型之间实现强制多态,通常需要执行不同的转换操作。强制多态的原则是:将值集较小(即占用 存储空间较小)的类型,变换成值集包含了前者(即占用存储空间较大)的类型,反之,应当注意可能发生的对值的损伤(特别是在使用Casting时)。
有时,强制多态与重载多态(操作符重载)是混合出现的。例如,
对于表达式
1+2;
1.0+2;
1+2.0;
1.0+2.0;
中出现的多态,就会有多种解释:
·操作符+有四种过载多态;
·操作符+只有一种:double * double -> double, 要将参与运算的整数强制变换成浮点数;
·操作符+有两种过载多态:int * int -> int 和 double * double -> double,要将混合运算中的整数强制变换成浮点数。
以上的强制多态主要是通过操作符重载所表现出来的多态,另外,还有两种不同的方法实现强制多态,它们分别是,1)通过类型转换构造函数(type conversion constructor)(类A的构造函数接收一个类B的对象),2)通过类型转换操作符(type conversion operator)(类B中的类型转换操作符返回一个类A的对象)。
例子(C++)如下:
Coercion in C++ using a type conversion constructor
class B
{ public:
void hello() const
{ cout << "Exemplar of B" << endl;
}
};
class A
{ public:
A(const B&) {} // type conversion constructor
A() {} // standard constructor
void hello() const
{ cout << "Exemplar of A" << endl;
}
};
void test(const A& v)
{ v.hello();
};
// ...
A a;
B b;
test(a);
test(b); // b "changes" to an A here
Coercion in C++ using a type conversion operator
class A
{ public:
void hello() const
{ cout << "Exemplar of A" << endl;
}
};
class B
{ public:
void hello() const
{ cout << "Exemplar of B" << endl;
}
operator A() const
{ return A(); // return an A
}
};
void test(const A& v)
{ v.hello();
};
// ...
A a;
B b;
test(a);
test(b); // b "changes" to an A here
有一种说法是,通用多态是真正的多态,而特定多态却不是,它是某种表面上的多态,因为它的多态特征
会在某种情况下消失(?disappears at close range)。
注:JAVA目前只提供了跟C++完全一样的包含多态机制。因为JAVA里每个方法默认都是虚的(函数)。现在也
有人提议在Java里加入参数多态。
另外,对于多态,根据多态发生时间的不同可以分为静态多态和动态多态。其中,通过类继承和虚函数
机制生效于运行期的多态,称为动态多态(dynamic polymorphism);模板也允许将不同的特殊行为和单个泛化
记号相关联,由于这种关联处理于编译期而非运行期,因此被称为静态多态(static polymorphism)。
动态多态只需要一个多态函数,生成的可执行代码尺寸较小,静态多态必须针对不同的类型产生不同的模板实体,
尺寸会大一些,但生成的代码会更快,因为无需通过指针进行间接操作。静态多态比动态多态更加类型安全,
因为全部绑定都被检查于编译期。
容易知道,前面的包含多态属于动态多态,而参数多态和重载多态和强制多态则属于静态多态。
参考资料:
[1] C++多态技术 http://www.vckbase.com/document/viewdoc/?id=948
[2] OBJECT ORIENTED PROGRAMMING POLYMORPHISM Sanchit Karve http://www.dreamincode.net/forums/showtopic9872.htm
[3] Polymorphism In Object_Oriented Languages W. Eisenecker http://accu.org/index.php/journals/538