北京大连石家庄宁波佛山
作者:互联网
结构化程序设计
让我们实现一个加法器,其中已经保存了加数,现在需要将加数传递给加法器。如果你是C语言开发者,第一反应大概是,这个很简单,用一个结构保存加数,然后取一个加法函数
在代码中,结构Augend保存加法器的加数,具体是由iAugend保存。第9到12行给出了加法函数的定义。该函数接收两个参数,一个是Augend结构的指针,另一个是iAddend Iadd。
但是老板来了,对你说,这个加法器需要修改。现在需要给加数加上一个权值,之前的加法器需要保留,因为还有一些代码要保留。没办法,拿人家手短,吃人家嘴软,继续做“码农”。由于有些代码需要使用旧的加法器,所以我们仍然保留旧的加法器,这样我们就可以根据新的思想开发新的加法器。具体方法如下
您可以看到,除了结构和函数名之外,代码思想与前面的代码完全相同。很明显,WeightAugend保存了加数和权重,而WeightAdd是一个带权重的加法函数。好了,现在我们来分析一下按照结构化编程思想实现的加法器的缺陷。学过面向对象的人一口就能说出来,数据和操作这个数据的函数或者方法没有封装在一起。确切地说,这个加法器没有封装加数、权值和运算它们的加法运算。另一个缺陷是什么?由于引入加权加法器后需要对一些旧代码进行修改,很明显代码是不封闭的,也就是没有实现这个变化点的封装。
基于对象的编程
在对象的世界里,一切都可以看作对象。根据这种说法,我们需要实现的加法器显然是一个对象。用过C++的同学第一反应是写一个加法器类,用数据成员保存加数,然后写一个公共加法方法。一般写如下。
这里使用显式是为了防止隐式类型转换。同样,故事还没有结束,需要实现加权加法,但还是用旧代码实现了加法器类。我们无能为力。我们必须实现另一个加权加法器。根据葫芦画,发布了鲁西丁加德尔类。
权重添加器类提供了两个数据成员来分别保存加数和权重,并提供了一个带权重的添加方法。现在我们来比较一下基于对象的方法和结构化方法的区别,就是封装,数据和操作这个数据的方法封装在一起。所以在这里,它是加数或加权,加法或加权加法,封装在类加法器或加权加法器中。只要实例化一个对象,就可以使用它的加法方法。
在一个实际的项目中,往往有很多。h档和。c文件来有效地组织代码。按照这种思路,我们通常把结构Augend的定义放在一个。文件,而Add函数的定义和声明放在a. C和。分别h档。这样会给用户和加法器的实现带来一点麻烦。什么样的麻烦?加法器的用户需要不断检查Augend和Add函数的头文件,因为他需要使用结构的成员来调用Add函数;实现者还需要经常检查Augend头文件。如果结构成员多,操作的功能多,那麻烦就大了。我该怎么办?很自然,这个想法是把Augend结构和Add函数放在一起。这样管理也比较方便。
在这段代码中,前24行实例化了加法器。然后。加法函数的地址被分配给pFuncAdd字段,并执行加数的初始化操作。而地线29通过pFuncAdd字段调用加法函数。其实已经有点像成员函数调用了。加法器是对象,pFuncAdd是成员函数。然而,随着这种组织结构及其运作功能的广泛应用,一个问题很快就会被发现。太麻烦了!只要有函数指针字段,通常需要定义一个函数指针类型,在创建结构的每个实例之后,都需要完成函数指针的赋值操作,也就是绑定一个特定的函数。不仅太麻烦,还容易出错。我该怎么办?交给编译器,把函数的定义或者声明放到结构里,编译器自己分析。创建结构实例后,让编译器自动绑定函数。
基于对象的方法和结构化方法的区别。基于对象的方法可以实现封装,这是结构化方法所缺乏的。结构化编程的思想之前已经分析过了。添加加权加法器时,代码闭包被破坏,即这个变化点不能被封装。但是,基于对象的思维并不能封装这种变化点。最初使用的加法器类的旧代码,如果您想改为使用加权加法器类,您必须在创建对象时修改类型,在传递参数时修改类型,等等。这显然打破了代码的封闭。可以看出,基于对象的方法只是弥补了基于结构化方法的封装缺陷,但仍然留下了变化点封装不好的问题。
面向对象编程
基于对象和面向对象的区别是什么?从技术角度来说,很简单,面向对象中有更多的继承和虚函数。这有什么好处?
与之前的班级相比,大致相同,区别主要如下:
Add函数变成了虚函数,这个意图很明显,就是希望派生类重写
m_iAugend数据成员的访问权限受到保护,这也确保了派生类可以访问它
添加了虚拟析构函数。为什么越来越多?因为如果Adder没有虚析构函数,在删除Adder类型的对象指针时,但指针实际指向其派生类对象,则派生类的析构函数不会被调用。
加法器的面向对象版本已经写好了,但是老板说要写加权加法。但是思路要清晰得多。让加权加法器从加法器中导出。
很简单,主要是重写虚函数Add,引入加权数据成员。事情看似完美,不仅数据和方法封装在一起,代码也是封闭的。真的是这样吗?当然不是。请继续往下看。
基于接口的编程
假设需求又发生了变化,要求普通加法器的加数必须是非负整数,而加权加法器的加数可以是非负整数,也可以是负整数。目前加法器类的加数m_iAugend是int类型的,不能保证一定是非负整数。如果改成无符号int,就不能满足带权加法器的需要了。我该怎么办?其实都有一个误区。他们一直认为有权重的加法器应该在普通加法器的基础上进行扩展。其实除了是加法器,两者都没有继承关系,是平等的。所以我们可以认为这两个类是兄弟,是同一个基类的加法器,都提供加法运算,也就是加法函数。
其实IAdder是一个接口类,指定要实现为Adder的接口,也就是纯虚函数add。显然,加法器类和加权加法器类都需要从增量器类派生,并覆盖加法函数。
加法器类的加数m_iAugend为无符号int类型,满足加数为负整数的要求,但不影响加权加数类的加数。与面向对象方法相比,这两种方法都封装了数据和操作数据的功能。其次,两种方法都可以封装普通加法器和加权加法器。简要说明基于接口的方法如何做到这一点。
void func(IAdder *pAdder)
{
...
int i = pAdder->Add(5);
}
与前面的部分类似,func函数接受一个IAdder类型的指针,并通过加法器方法传递它。因此,无论您想使用加法器类的对象还是加权加法器类的对象,您只需要传递一个指向func函数的指针。显然,func函数可以同时使用Adder类和WeightingAdder类,而无需修改单个代码,满足封装变化点的要求。但是当遇到“普通加法器的加数必须是非负整数,而加权加法器的加数可以是非负整数,即负整数”这种变化点时,面向对象的方法就出问题了,根本原因在于继承。
可以看出,在面向对象中,权重添加器类继承了加法器类。继承是一种强耦合关系。这表明派生类耦合到接口和基类的实现。在基于接口的设计中,加法器类和加权加法器类已经从父子关系变成了兄弟关系,并且都是从IAdder继承的。都是继承关系。为什么能解决面向对象方法解决不了的问题?因为这个传承不是另一个传承。虽然在基于接口的方法中使用了继承,但这不是一种强耦合关系。加法器类和加权类都是从基类继承而来的。显然,它们都耦合到基类的接口和实现。然而,问题是IAdder类本质上只有一个纯虚函数Add,没有任何实现细节,甚至没有数据成员。所以耦合到其实现的说法是没有意义的。所以它的耦合度低于面向对象的类结构。这是一个基于接口的方法,可以封装类型变化点的原因。
基于接口编程的模板实现
利用虚拟函数实现了基于接口的加法器。在本节中,我们使用模板来实现这个版本的加法器。
分别给出了普通加法器和加权加法器的实现。并且在这两个类中,给出了函数AddImpl。从这两个函数可以看出,它们实现了具体的加法运算。IAdder类模板中的Add函数调用T类型的AddImpl函数..看来这个AddImpl函数和Adder或者WeightingAdder中的AddImpl函数有关系。是什么关系?我们通过模板的半演绎来解释。
假设加法器以下列方式使用
加法器加法器(5);
加法器。添加(4);
在第一行中,我们实例化了加法器类的一个对象,加法器类的基类是IAdder< Adder >。此时模板参数t实际上是Adder。当对象adder调用Add函数时,Add函数的代码相当于
int Add(int iAddend)
{
Adder pThis = (Adder)(this);
pThis->AddImpl(iAddend);
可以看出,上面代码中的t是用Adder代替的,因为此时的模板参数t是Adder。在代码中,这个指针被强转换成一个加法器指针。那么,这种转换安全吗?当然,当前对象是adder,类型是Adder,所以这个指针此时实际指向的是Adder类的对象,把自己变成了自己。当然安全。这个方法可以封装变化点吗?我们先举个例子
template
void f(IAdder
{
std::cout << pAdder->Add(4) << std::endl;
}
当需求发生变化时,比如增加一个加权加法器,可以从IAdder中导出一个加权加法器类,模板t等于加权加法器。对于上面的f函数,如果要用加法器,就给它传递一个加法器的对象指针,如果要用加权加法器,就给它传递一个加权加法器对象的指针。显然,f函数的代码不需要任何修改。所以改变点也实现了。
标签:加权,佛山,封装,函数,大连,石家庄,加数,加法器,加法 来源: https://www.cnblogs.com/daybydaye/p/14153000.html