了解 MQL5 面向对象编程(OOP)
概述
在本文中,我们将分享编程中最重要的主题之一,其可顺滑且轻松地进行编码,并帮助我们应用 DRY(Do Not Repeat Yourself — 不要自我重复)的概念,作为开发人员或程序员的捷径。额外还能提升所有创建的软件和其它功能的安全性。我们将讨论面向对象编程(OOP),以及如何运用这一概念以 MQL5(MetaQuotes 语言)进行编码,首先要了解 OOP 的基础知识,然后通过查看一些应用程序来理解如何使用 MQL5。
由此,我们将通过以下三点来讨论这个有趣而重要的话题:
本文的主要标的是了解面向对象编程(OOP)的一般基础知识,以及它如何在软件创建中发挥作用。从中,我们将学到如何以 MQL5 应用这种方式,以便可以帮助我们通过应用这种令人难以置信的方式来创建更有效、更安全的软件。
对任何人来说,学习有关 OOP 方法的更多信息都非常实用且非常重要,有关该方法的资源普遍,特别是因为它适用于其它编程语言,如 C++、Java、Python、等等。对于 MQL5 语言,非常重要的资源不仅是在本文的主题当中,而且存在于 MQL5 文档的每一个主题当中,您可以浏览 MQL5 OOP 文档 链接查看 OOP 主题。
什么是 OOP
在本主题中,我们将大致了解 OOP 的基础知识,如此我们从定义 OOP 开始这个有趣的主题。OOP 是面向对象编程(Object-Oriented Programming)的缩写,它是计算机编程的范式,它有助于顺滑地创建和开发可重用的软件,无需重复工作和代码,这有助于我们应用 DRY(Do not Repeat Yourself —不要自我重复)的概念。
正如我们将看到的,OOP 帮助我们更贴近世界的本质,因为我们周围的一切都是由物体创造的,每个物体都有各自的性质和行为,这就是我们在软件中可以处理的数据。如果我们想更深入 OOP,我们需要知道我们将应对的对象和类,对象意即类的实例,类是对象的模板。在类的模板内部,我们详细定义了这个对象的行为。
OOP 的基本概念:
当我们在软件中应用 OOP 时,将遵守以下原则。
- 封装。
- 抽象。
- 继承。
- 多态。
1- 封装:
封装是能够在一个类中链接函数和数据的方法,类中的数据和函数可以是私密的,只能在类内访问,也可以是公开的,可以从类的外部访问。封装概念有助于隐藏类实现的复杂性,并令开发人员能够完全掌控其数据,有助于跟踪所有其它依赖的数值,且不会发生冲突。
由此,我们可以说,封装有助于保持我们的系统正常运行,并避免许多可能的错误,此外还为开发人员提供了高等级的控制,助其在测试和处理类数据时更顺畅、更容易,且不会影响或更改软件的整体代码。根据上述内容,它还有助于解决错误,并防止编码太复杂。
下图示意封装概念:
2- 抽象:
抽象是隐藏不必要的细节,且仅呈现基本细节的方法。它比封装概念更广泛,但它有助于达成相同的目标,即保护数据和实现函数,无需了解所有类的实现过程,仅需知道我要做什么才能完成实现。
为此,我们需要包含两个重要的方法:接口和实现。接口是类交互和彼此呼应的方法,而实现则是代码的所有细节、或类逻辑细节的方法。因此,抽象有助于提升软件的安全性,也有助于不重复编码过程、或从头开始重新编码,而仅需基于已创建的应用程序来开发和编码更多应用程序。
3- 继承:
顾名思义,继承概念意味着我们从旧类派生出一个新类,而新类继承了旧类的特征,在这种情况下,旧类称为父类或超类,新派生出的类称为子类。这个概念有助于应用 DRY(不要自我重复)的概念,且提供了可重用性的主要思想。
4- 多态:
顾名思义,如果我们将这个单词分成两个部分,我们会发现(Poly)意即“许多”或“多个”,(Morphism)意即形式,故多态概念意味着它能够帮助一个实体以多种形式表达,例如,如果我们有一个 Sum 方法,它可以表现出不同行为,好比一个能够获得(a)与(b)变量之和的方法;而另一个也可以得到变量的求和,但参数不同,如(a)、(b)和(c)。
简言之,多态性意味着一个接口和许多或多个方法。
我们可以总结一下我们提到的内容,即 OOP 是一种计算机编程模型,它专注于通过具有独特行为和性质的对象来组织软件的设计,这在大型复杂软件中是一种很实用的方法,尤其若是该软件的更新和操作类型众多。
OOP 属性和特征:
- 应用 OOP 方式的软件包括类和函数。
- 通过应用封装和抽象原则隐藏了数据,安全等级得以提高。
- 它有助于轻松处理复杂的项目,因为代码可以分成小的代码模块,这样就可以降低项目的复杂度。
- 更新和开发过程更容易。
- 通过应用继承原则实现代码的可重用性。
- 拥有在不冲突的情况下创建同一类的多个实例的能力。
我们可以找到许多有能力应用 OOP 方式的编程语言,最流行的是 C++、C#、Python、Java、JavaScript、PHP、等等。不光这些语言,还有 MQL5 语言我们也可以应用 OOP 方法,这就是我们要通过以下主题学习的内容。
MQL5 中的 OOP
在本主题中,我们将学习 MQL5 中的 OOP,以及如何运用它。就像我们提到的 OOP 的基础知识一样,我们创建一个类,就像一个对象的蓝图;类本身是一个集合,或包含变量和方法,它们与函数相同,但它在类中被称为方法,用于执行一套特定的任务。这里还值得一提的是,类中的变量和方法或函数称之为类成员。我们在此要说的是,在 MQL5 中应用 OOP 概念是一项良好且值得赞赏的努力,因为不光品质和安全性,还像我们提到的那样,它会节省大量精力工作。
如果我们需要在 MQL5 中应用 OOP 概念,我们需要了解如何在 MQL5 中运用以下内容:
- 类
- 访问修饰符
- 构造函数和析构函数
- 派生(子)类
- 虚拟函数
- 对象
类:
在 MQL5 中,如果我们需要创建一个类作为对象的蓝图,我们需要在全局范围内声明这个类,如同函数一样。我们可以通过 class 关键字后跟所需的唯一值来创建这个类,然后在两个大括号 “{}” 之间或内部,放置作为类成员的变量和方法,然后在第二个大括号 “}” 之后,我们放置一个分号 “;" 来终结类声明。顺便说一句,我们可以在程序或包含文件中使用这个类声明。
下面是这个类声明的示例:
class Cobject { int var1; // variable1 double var2; // variable1 void method1(); // Method or function1 };
正如我们在前面的例子中所见,我们的类有三个成员,它们是两个变量和一个方法或函数。
访问修饰符:
通过这些访问修饰符,我们可以判定从类外部我们可以用到哪些变量和函数,我们有三个访问关键字,分别是 public(公开)、private(私密)、和 protected(受保护)。
- public:表示可从类之外使用的成员。
- private:表示不能在类之外使用,且只供类内部函数使用的成员。该类的子类不能继承这些私密成员。
- protected:表示可由子类继承的成员,但对于非子类,它们本质上是私密的。
如果我们需要查看一个示例,我们可以看看下方这个:
class Cobject { private: int var1; // variable1 protected: double var2; // variable1 public: void method1(); // Method or function1 };
正如我们在前面的例子中所见,在类中我们有三个成员,前两个是变量:一个是私密的,另一个是受保护的;第三个是公开的函数。
构造函数和析构函数:
如果我们需要初始化类中的变量,我们要用到构造函数。如果我们不这样做,它将默认由编译器创建,但此默认构造函数将不可见。且据访问性惯例,它还必须是公开的。另一方面,析构函数是当类对象被注销时自动调用的函数。我们调用析构函数时,可以用波浪号(~)加上相同类名。无论析构函数是否存在,字符串、动态数组和对象都需要逆初始化,如此这般它们无论如何都会被逆初始化。
下面是构造函数的示例:
class CPrices { private: double open; // Open price double high; // High price double low; // Low price double close; // Close price public: //--- Default constructor CPrices(void); //--- Parametric constructor CPrices(double o,double h,double l, double c); };
派生(子)类:
正如我们之前学过的,继承概念是 OOP 最有价值和最实用的特征之一,因为我们可以据超类或父类创建一个子类,并且这个子类继承了父类的所有成员,但私密成员除外。之后,我们可以为该子类添加新的变量和函数。
如果我们需要看一个示例,我们可以看看下面的示例,如果我们有一个价格父类,我们能据其创建一个每日价格子类,如下所示:
class CDailyPrices : public CPrices { public: double open; // Open price double high; // High price double low; // Low price double close; // Close price };
正如我们所见,父类名称是 CPrices,而 CDailyPrices 是子类或派生类。现在,我们发现 CPrices 的所有公开成员和受保护成员都是 CDailyPrices 类的一部分,并且它们仍然是公开的。
虚拟函数:
如果我们想在子类中更新方法或函数的操作方式,我们可以通过在父类中声明虚拟(virtual)函数来做到这一点,然后在子类中定义函数。例如,如果在基类中我们有两个不同版本的函数。对于父类,我们用 virtual 关键字定义函数
class CVar { public: virtual void varCal(); };
然后,我们在子类中更新同名函数
class CVar1 : public CVar { public: int varCal(int x, int y); };
对象:
对象有唯一的标识符,如同我们在创建变量时所做的那样,我们对象标识符之前用类名作为其类型。我们可以根据项目需要创建许多属于我们类的对象,所有我们要做的就是为每个对象起一个独有的标识符。声明对象后,我们可以使用点(.)访问任何公开成员。
我们看一个示例,就能清晰地理解,如果我们创建一个类,该类含有交易次数(num_trades)的整数型变量
class CSystrades { public: int num_trades; };
然后我们需要创建一个属于这个类的对象,称为 system1,我们将执行如下相同的操作来做到这一点
CSystrades system1;
然后,我们可以为这个对象赋值(3),如下所示
system1.num_trades=3;
现在,我们围绕本主题,学习了 OOP 方式的一些最重要思想,了解如何在 MQL5 中应用它。
OOP 应用
在这个有趣的部分中,我们将介绍一些在我们的软件中运用 OOP 方式的简单应用,从而更好地理解我们如何使用它,以及它有多大用处。
priceClass 应用程序:
在这个简单的应用程序中,我们需要检查多个时间帧的价格,在这个示例中,我们将在这里展示三个时间帧(日线、周线和月线),我们还需要查看所有价格(开盘价、最高价、最低价、收盘价),我们需要在同一处查看它们,比如说在智能系统选项卡中。之后,我们可以在这个简单的示例中开发更多内容,以便实现更高级的软件。
首先,我们需要按照以下步骤声明类:
- 我们需要在全局范围内以 class 关键字声明一个 price 类,并在其内包含所有公开的共用成员。
- 使用 public 关键字。
- 创建五个变量(时间帧、开盘价、最高价、最低价、和收盘价)。
- 创建一个 void 函数来打印所有价格数据。
class CPrices { public: string timeFrame; double open; double high; double low; double close; void pricesPrint() { Print(timeFrame," Prices = Open: ",open," - ","High: ",high,"-","Low: ",low,"-","Close: ",close); } };
从类中创建日线、周线、和月线价格的对象
CPrices CDailyPrices; CPrices CWeeklyPrices; CPrices CMonthlyPrices;
在 OnInit 函数中,我们将为三个时间帧定义以下内容:
- 定义字符串时间帧。
- 调用 iOpen 函数定义开盘价。
- 调用 iHigh 函数定义最高价。
- 调用 iLow 函数定义最低价。
- 调用 iClose 函数定义收盘价。
- 调用打印函数或方法。
int OnInit() { //--- Daily time frame CDailyPrices.timeFrame="Daily"; CDailyPrices.open=(iOpen(Symbol(),PERIOD_D1,1)); CDailyPrices.high=(iHigh(Symbol(),PERIOD_D1,1)); CDailyPrices.low=(iLow(Symbol(),PERIOD_D1,1)); CDailyPrices.close=(iClose(Symbol(),PERIOD_D1,1)); CDailyPrices.pricesPrint(); //--- Weekly time frame CWeeklyPrices.timeFrame="Weekly"; CWeeklyPrices.open=(iOpen(Symbol(),PERIOD_W1,1)); CWeeklyPrices.high=(iHigh(Symbol(),PERIOD_W1,1)); CWeeklyPrices.low=(iLow(Symbol(),PERIOD_W1,1)); CWeeklyPrices.close=(iClose(Symbol(),PERIOD_W1,1)); CWeeklyPrices.pricesPrint(); //--- Monthly time frame CMonthlyPrices.timeFrame="Monthly"; CMonthlyPrices.open=(iOpen(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.high=(iHigh(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.low=(iLow(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.close=(iClose(Symbol(),PERIOD_MN1,1)); CMonthlyPrices.pricesPrint(); return(INIT_SUCCEEDED); }
之后,我们可以在工具箱中的 “Experts” 选项卡中找到执行该智能系统后打印的价格,如下所示:
正如我们在上图中所见,我们得到三行打印:
- 第一行打印开盘价、最高价、最低价和收盘价的日线价格。
- 第二行打印相同的价格,但属于周线数据。
- 第三行也打印相同的内容,但属于月线数据。
indicatorClass 应用程序:
我们需要利用 OOP 方式创建能够打印四种移动平均线类型(简单、指数、平滑、和线性加权平均)数值的软件,以下是创建该类软件的简单步骤:
使用 class 关键字声明指标 CiMA 类,并创建该类的四个常用变量公开成员,它们是 MAType 定义移动平均线类型、MAArray 定义移动平均线数组、MAHandle 定义每种类型的句柄、MAValue 定义每种移动平均线值。创建一个 void 方法或 valuePrint 函数,以及函数的主体,来打印每种移动平均线类型的数值。
class CiMA { public: string MAType; double MAArray[]; int MAHandle; double MAValue; void valuePrint() { Print(MAType," Current Value: ",MAValue); }; };
据该类为每种移动平均线创建以下对象:
- 平均值的名称
- 平均线的句柄
- 平均数组
//--- SMA CiMA CSma; CiMA CSmaHandle; CiMA CSmaArray; //--- EMA CiMA CEma; CiMA CEmaHandle; CiMA CEmaArray; //--- SMMA CiMA CSmma; CiMA CSmmaHandle; CiMA CSmmaArray; //--- LWMA CiMA CLwma; CiMA CLwmaHandle; CiMA CLwmaArray;
在 OnInit 函数中,我们将针对每种移动平均线类型执行以下步骤:
- 定义平均值的名称。
- 定义平均值的句柄。
- 调用 ArraySetAsSeries 设置数组顺序标志为 AS_SERIES。
- 调用 CopyBuffer 函数获取平均指标的缓冲区数据。
- 定义平均值,并调用 NormalizeDouble 函数对其进行常规化。
- 调用创建的打印方法或函数
int OnInit() { //--- SMA CSma.MAType="Simple MA"; CSmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_SMA,PRICE_CLOSE); ArraySetAsSeries(CSmaArray.MAArray,true); CopyBuffer(CSmaHandle.MAHandle,0,0,3,CSmaArray.MAArray); CSma.MAValue=NormalizeDouble(CSmaArray.MAArray[1],_Digits); CSma.valuePrint(); //--- EMA CEma.MAType="Exponential MA"; CEmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_EMA,PRICE_CLOSE); ArraySetAsSeries(CEmaArray.MAArray,true); CopyBuffer(CEmaHandle.MAHandle,0,0,3,CEmaArray.MAArray); CEma.MAValue=NormalizeDouble(CEmaArray.MAArray[1],_Digits); CEma.valuePrint(); //--- SMMA CSmma.MAType="Smoothed MA"; CSmmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_SMMA,PRICE_CLOSE); ArraySetAsSeries(CSmmaArray.MAArray,true); CopyBuffer(CSmmaHandle.MAHandle,0,0,3,CSmmaArray.MAArray); CSmma.MAValue=NormalizeDouble(CSmmaArray.MAArray[1],_Digits); CSmma.valuePrint(); //--- LWMA CLwma.MAType="Linear-weighted MA"; CLwmaHandle.MAHandle=iMA(_Symbol,PERIOD_CURRENT,10,0,MODE_LWMA,PRICE_CLOSE); ArraySetAsSeries(CLwmaArray.MAArray,true); CopyBuffer(CLwmaHandle.MAHandle,0,0,3,CLwmaArray.MAArray); CLwma.MAValue=NormalizeDouble(CLwmaArray.MAArray[1],_Digits); CLwma.valuePrint(); return(INIT_SUCCEEDED); }
编译并执行该段代码后,我们可以找到打印出的每种类型的移动平均线,共四行,如下所示:
正如我之前提到的,我们可以开发这些应用程序来实现更复杂和更高级的任务,但此处标的是学习和理解面向对象编程(OOP)的基础知识,以及如何以 MQL5 应用这种方式,因为当应用这种实用方式时,我们可以获得许多功能。
结束语
在本文中,我们大致学习了编程时一种非常重要的主题和方式的基础知识。通过学习如何帮助我们创建稳妥且高度安全的软件,我们了解到这种方式在设计软件时极其实用,通过将代码划分为小片代码模块来帮助降低创建软件的复杂性,从而令编码过程更容易,还有助于据同一个类创建许多实例,即使具有不同的行为,也不会发生任何冲突,所有这些都为更新软件时的安全性提供了更大的灵活性。
我们还学习了如何在 MQL5 中应用这一重要方式,进而获得所有这些令人难以置信的功能,之后学习了一些简单的应用程序,能够在 MetaTrader 5 中以 MQL5 运用 OOP 方式创建。
我希望这篇文章对您有所启迪,并帮助您学习 MQL5 编程中一个非常重要和关键的主题,也许它会令您更深入地见识到在运用 OOP 方式编码时,多么容易激发您的思路。
如果您喜欢这篇文章,并且想了解更多关于如何基于 MetaTrader 5 中最流行的技术指标,以 MQL5 创建交易系统,以及如何创建自定义技术指标,并在您的智能系统中使用它,您可以通过我的个人资料上的“出版物”板块去阅读我关于这些主题的其它文章,我希望您也发现它们的实用之处。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/12813