OOP 中需要多态性吗?
What is the need Polymorphism in OOP?
我有一个关于 OOP 概念的愚蠢问题,我们选择多态性的原因是什么??
C++ 中的简单代码:
class Shape{
public:
virtual void draw(){ cout<<"Shape"<<endl;};
};
class Traingle: public Shape
{
public: void draw(){cout<<"Triangle"<<endl;}
};
class Rectangle: public Shape
{
public: void draw (){cout<<"Rectangle"<<endl;}
};
int main(){
Shape *ptr= new Traingle();
ptr->draw();
delete ptr;
return 7;
}
这里ptr->draw()函数会调用Triangle绘制,如果指向Rectangle则调用Rectangle绘制,即后期绑定。
创建 Base class 指针并将其指向不同的 class 有什么必要?我们可以在没有任何虚函数的情况下创建单独的 class 对象,并在需要时调用。
喜欢
int main(){
Traingle tObj;
tObj->draw();
Rectangle rObj;
rObj->draw();
}
这基本上做同样的事情;
为什么基本上是多态?为什么是虚拟的?
它有什么需要,或者使用它有什么不同 属性?
真实案例会有所帮助!!
想象一个基地 class Shape
。它公开了一个 GetArea
方法。想象一下 Square
class 和 Rectangle
class,以及 Circle
class。无需创建单独的 GetSquareArea
、GetRectangleArea
和 GetCircleArea
方法,您只需在每个派生的 class 中实现一个方法。您不必知道您使用的是 Shape
的哪个子 class,您只需调用 GetArea
即可获得结果,而不管它是哪种具体类型。
看看这段代码:
#include <iostream>
using namespace std;
class Shape
{
public:
virtual float GetArea() = 0;
};
class Rectangle : public Shape
{
public:
Rectangle(float a) { this->a = a; }
float GetArea() { return a * a; }
private:
float a;
};
class Circle : public Shape
{
public:
Circle(float r) { this->r = r; }
float GetArea() { return 3.14f * r * r; }
private:
float r;
};
int main()
{
Shape *a = new Circle(1.0f);
Shape *b = new Rectangle(1.0f);
cout << a->GetArea() << endl;
cout << b->GetArea() << endl;
}
这里要注意的一件重要事情是 - 您不必知道所使用的 class 的确切类型,只需知道基本类型,您就会得到正确的结果。这在更复杂的系统中也非常有用。
恕我直言,使用多态性的最佳示例是容器:
std::vector<Shape*> shapes;
shapes.push_back(new Triangle());
shapes.push_back(new Rectangle());
您可以遍历此容器而无需关心对象的实际类型:
for (int i=0;i<shapes.size();i++){
shapes[i]->draw();
}
简单来说,多态就是可以把不同的形状放在同一个盒子里,把盒子里的所有东西都异类地对待。
扩展您的示例:
#include <iostream>
#include <memory>
#include <vector>
class Shape{
public:
virtual void draw(){ std::cout<<"Shape"<< std::endl;};
};
class Traingle: public Shape
{
public:
void draw() override
{
std::cout<<"Triangle"<< std::endl;
}
};
class Rectangle: public Shape
{
public:
void draw() override
{
std::cout<<"Rectangle"<< std::endl;
}
};
int main(){
std::vector<std::unique_ptr<Shape>> box_of_shapes;
box_of_shapes.emplace_back(new Traingle);
box_of_shapes.emplace_back(new Rectangle);
for (const auto& pshape : box_of_shapes)
{
pshape->draw();
}
return 0;
}
Rectangle
是一个 Shape
(因为它是从Shape
公开派生的)因此它可以由处理的向量持有(指向)Shape
。
但是,因为 draw
方法是虚拟的,所以即使调用者只是将其视为 Shape
.
,Rectangle
也会正确绘制
预期输出:
Triangle
Rectangle
多态性允许对相关类型的对象进行相同处理,从而允许重用代码。
考虑到您可能需要许多行为不同的子类:
struct Shape1: public Shape { /* .. */ }; // triangle
struct Shape2: public Shape { /* .. */ }; // rectangle
// ...
struct ShapeN: public Shape { /* .. */ }; // projection of rhombic triacontahedron
考虑到您可能需要处理由 Shape
指针数组指向的对象。
使用多态性,您需要一个向量和一个带有虚函数调用的循环:
std::vector<Shape*> v = get_shape_vector();
for(Shape* s : v)
s->draw();
如果没有多态性,您将不得不为每种类型管理一个单独的数组并单独处理它们:
std::vector<Shape1> v1 = get_shape1_vector();
std::vector<Shape2> v2 = get_shape2_vector();
// ...
std::vector<ShapeN> vN = get_shapeN_vector();
for(Shape1& s : v1)
s.draw();
for(Shape2& s : v2)
s.draw();
// ...
for(ShapeN& s : vN)
s.draw();
使用多态的 3 行代码比不使用多态的 3*N 行代码更容易维护。
考虑到您可能需要修改流程。也许您想在绘制之前添加一个函数调用。当您拥有多态性时,这很简单:
void pre_draw(Shape*);
for(Shape* s : v) {
pre_draw(s);
s->draw();
}
没有多态性,你需要定义几十个函数,并修改每一个循环:
void pre_draw1(Shape1&);
void pre_draw2(Shape2&);
// ...
void pre_drawN(ShapeN&);
for(Shape1& s : v1) {
pre_draw1(s);
s.draw();
}
for(Shape2& s : v1) {
pre_draw2(s);
s.draw();
}
// ...
for(ShapeN& s : v1) {
pre_drawN(s);
s.draw();
}
请考虑稍后添加形状。使用多态性,您只需定义新类型和虚函数。您可以简单地将指向它的指针添加到数组中,它们将像所有其他兼容类型的对象一样被处理。
struct ShapeN1: public Shape { /* .. */ }; // yet another shape
如果没有多态性,除了定义新类型外,您还必须为其创建一个新数组。您需要创建一个新的 pre_draw
函数。并且您需要添加一个新循环来处理它们。
void pre_drawN1(ShapeN1&);
// ...
std::vector<ShapeN1> vN1 = get_shapeN1_vector();
// ...
for(ShapeN1& s : vN1) {
pre_drawN1(s);
s.draw();
}
事实上,您需要遍历整个代码库,找到处理每种形状类型的地方,并在其中添加新类型的代码。
现在,N 可能很小也可能很大。 N越大,越避免重复多态性。但是,无论您的子类有多少,在添加新子类时不必查看整个代码库是一个很大的好处。
神奇的词是解耦。假设您正在创建一个应用程序来管理超市的存储。您几乎从一开始就知道您迟早会需要一种方法来存储和检索数据。但是,您还不知道哪种技术是最好的(例如,它可以是关系数据库或 NoSql 数据库)。
所以,你创建了一个接口(抽象class):
struct Serialize{
virtual void save(Product product) = 0;
... other method here ...
};
现在,您可以继续开发应用程序的其他部分,只需实现内存中版本的 Serialize。
class InMemorySerialize : public Serialize {
... implement stuff here ...
};
依赖于 Serialize 的代码不关心使用哪个具体 class,唯一真正需要更改以修改 serialize 实现的地方是具体 class 的构造.但是 class 的构造很可能只在一个地方并且非常接近 main 函数(查看 strategy pattern 了解更多信息)。
//in the main (switching these lines you will use different implementations)
//the rest of your code will not change
unique_ptr<Serialize> serializer(new InMemorySerialize());
//unique_ptr<Serialize> serializer(new OnFileSerialize(myfolder));
此外,实现接口是实现 Open/Close principle 的最简单方法之一。如果你想支持序列化到文件而不是在内存中,你将创建一个新的 class ,它将在一个单独的文件中。因此,您愿意在不触及现有代码的情况下增加应用程序的功能。
最后一点,如果您依赖接口而不是具体对象,测试会容易得多。您可以轻松实现测试所需的行为。
我有一个关于 OOP 概念的愚蠢问题,我们选择多态性的原因是什么??
C++ 中的简单代码:
class Shape{
public:
virtual void draw(){ cout<<"Shape"<<endl;};
};
class Traingle: public Shape
{
public: void draw(){cout<<"Triangle"<<endl;}
};
class Rectangle: public Shape
{
public: void draw (){cout<<"Rectangle"<<endl;}
};
int main(){
Shape *ptr= new Traingle();
ptr->draw();
delete ptr;
return 7;
}
这里ptr->draw()函数会调用Triangle绘制,如果指向Rectangle则调用Rectangle绘制,即后期绑定。
创建 Base class 指针并将其指向不同的 class 有什么必要?我们可以在没有任何虚函数的情况下创建单独的 class 对象,并在需要时调用。 喜欢
int main(){
Traingle tObj;
tObj->draw();
Rectangle rObj;
rObj->draw();
}
这基本上做同样的事情;
为什么基本上是多态?为什么是虚拟的?
它有什么需要,或者使用它有什么不同 属性? 真实案例会有所帮助!!
想象一个基地 class Shape
。它公开了一个 GetArea
方法。想象一下 Square
class 和 Rectangle
class,以及 Circle
class。无需创建单独的 GetSquareArea
、GetRectangleArea
和 GetCircleArea
方法,您只需在每个派生的 class 中实现一个方法。您不必知道您使用的是 Shape
的哪个子 class,您只需调用 GetArea
即可获得结果,而不管它是哪种具体类型。
看看这段代码:
#include <iostream>
using namespace std;
class Shape
{
public:
virtual float GetArea() = 0;
};
class Rectangle : public Shape
{
public:
Rectangle(float a) { this->a = a; }
float GetArea() { return a * a; }
private:
float a;
};
class Circle : public Shape
{
public:
Circle(float r) { this->r = r; }
float GetArea() { return 3.14f * r * r; }
private:
float r;
};
int main()
{
Shape *a = new Circle(1.0f);
Shape *b = new Rectangle(1.0f);
cout << a->GetArea() << endl;
cout << b->GetArea() << endl;
}
这里要注意的一件重要事情是 - 您不必知道所使用的 class 的确切类型,只需知道基本类型,您就会得到正确的结果。这在更复杂的系统中也非常有用。
恕我直言,使用多态性的最佳示例是容器:
std::vector<Shape*> shapes;
shapes.push_back(new Triangle());
shapes.push_back(new Rectangle());
您可以遍历此容器而无需关心对象的实际类型:
for (int i=0;i<shapes.size();i++){
shapes[i]->draw();
}
简单来说,多态就是可以把不同的形状放在同一个盒子里,把盒子里的所有东西都异类地对待。
扩展您的示例:
#include <iostream>
#include <memory>
#include <vector>
class Shape{
public:
virtual void draw(){ std::cout<<"Shape"<< std::endl;};
};
class Traingle: public Shape
{
public:
void draw() override
{
std::cout<<"Triangle"<< std::endl;
}
};
class Rectangle: public Shape
{
public:
void draw() override
{
std::cout<<"Rectangle"<< std::endl;
}
};
int main(){
std::vector<std::unique_ptr<Shape>> box_of_shapes;
box_of_shapes.emplace_back(new Traingle);
box_of_shapes.emplace_back(new Rectangle);
for (const auto& pshape : box_of_shapes)
{
pshape->draw();
}
return 0;
}
Rectangle
是一个 Shape
(因为它是从Shape
公开派生的)因此它可以由处理的向量持有(指向)Shape
。
但是,因为 draw
方法是虚拟的,所以即使调用者只是将其视为 Shape
.
Rectangle
也会正确绘制
预期输出:
Triangle
Rectangle
多态性允许对相关类型的对象进行相同处理,从而允许重用代码。
考虑到您可能需要许多行为不同的子类:
struct Shape1: public Shape { /* .. */ }; // triangle
struct Shape2: public Shape { /* .. */ }; // rectangle
// ...
struct ShapeN: public Shape { /* .. */ }; // projection of rhombic triacontahedron
考虑到您可能需要处理由 Shape
指针数组指向的对象。
使用多态性,您需要一个向量和一个带有虚函数调用的循环:
std::vector<Shape*> v = get_shape_vector();
for(Shape* s : v)
s->draw();
如果没有多态性,您将不得不为每种类型管理一个单独的数组并单独处理它们:
std::vector<Shape1> v1 = get_shape1_vector();
std::vector<Shape2> v2 = get_shape2_vector();
// ...
std::vector<ShapeN> vN = get_shapeN_vector();
for(Shape1& s : v1)
s.draw();
for(Shape2& s : v2)
s.draw();
// ...
for(ShapeN& s : vN)
s.draw();
使用多态的 3 行代码比不使用多态的 3*N 行代码更容易维护。
考虑到您可能需要修改流程。也许您想在绘制之前添加一个函数调用。当您拥有多态性时,这很简单:
void pre_draw(Shape*);
for(Shape* s : v) {
pre_draw(s);
s->draw();
}
没有多态性,你需要定义几十个函数,并修改每一个循环:
void pre_draw1(Shape1&);
void pre_draw2(Shape2&);
// ...
void pre_drawN(ShapeN&);
for(Shape1& s : v1) {
pre_draw1(s);
s.draw();
}
for(Shape2& s : v1) {
pre_draw2(s);
s.draw();
}
// ...
for(ShapeN& s : v1) {
pre_drawN(s);
s.draw();
}
请考虑稍后添加形状。使用多态性,您只需定义新类型和虚函数。您可以简单地将指向它的指针添加到数组中,它们将像所有其他兼容类型的对象一样被处理。
struct ShapeN1: public Shape { /* .. */ }; // yet another shape
如果没有多态性,除了定义新类型外,您还必须为其创建一个新数组。您需要创建一个新的 pre_draw
函数。并且您需要添加一个新循环来处理它们。
void pre_drawN1(ShapeN1&);
// ...
std::vector<ShapeN1> vN1 = get_shapeN1_vector();
// ...
for(ShapeN1& s : vN1) {
pre_drawN1(s);
s.draw();
}
事实上,您需要遍历整个代码库,找到处理每种形状类型的地方,并在其中添加新类型的代码。
现在,N 可能很小也可能很大。 N越大,越避免重复多态性。但是,无论您的子类有多少,在添加新子类时不必查看整个代码库是一个很大的好处。
神奇的词是解耦。假设您正在创建一个应用程序来管理超市的存储。您几乎从一开始就知道您迟早会需要一种方法来存储和检索数据。但是,您还不知道哪种技术是最好的(例如,它可以是关系数据库或 NoSql 数据库)。
所以,你创建了一个接口(抽象class):
struct Serialize{
virtual void save(Product product) = 0;
... other method here ...
};
现在,您可以继续开发应用程序的其他部分,只需实现内存中版本的 Serialize。
class InMemorySerialize : public Serialize {
... implement stuff here ...
};
依赖于 Serialize 的代码不关心使用哪个具体 class,唯一真正需要更改以修改 serialize 实现的地方是具体 class 的构造.但是 class 的构造很可能只在一个地方并且非常接近 main 函数(查看 strategy pattern 了解更多信息)。
//in the main (switching these lines you will use different implementations)
//the rest of your code will not change
unique_ptr<Serialize> serializer(new InMemorySerialize());
//unique_ptr<Serialize> serializer(new OnFileSerialize(myfolder));
此外,实现接口是实现 Open/Close principle 的最简单方法之一。如果你想支持序列化到文件而不是在内存中,你将创建一个新的 class ,它将在一个单独的文件中。因此,您愿意在不触及现有代码的情况下增加应用程序的功能。
最后一点,如果您依赖接口而不是具体对象,测试会容易得多。您可以轻松实现测试所需的行为。