从一个加法器的实现看简单的编程范式
编程范式
编程范式(Programming Paradigm)是某种编程语言典型的编程风格或者说是编程方式。随着编程方法学和软件工程研究的深入,特别是OO思想的普及,范式(Paradigm)以及编程范式等术语渐渐出现在人们面前。面向对象编程(OOP)常常被誉为是一种革命性的思想,正因为它不同于其他的各种编程范式。类似面向过程编程、面向对象编程、函数式编程等都称为编程范式。
从一个加法器写起
需要实现一个加法器:在这个加法器中,已经保存了被加数;现在需要传递加数给这个加法器,以让其返回加法计算结果。
变化:现在需要给被加数添加一个权重值;但是以前的加法器仍需保留,因为还有一部分代码会使用它。
方案一
首先实现加法器,利用一个结构体来保存被加数,再写一个加法函数,通过struct SLAugend *pSLAugend
来访问结构体,进而通过pSLAugend->iAugend
访问被加数,访问结构体的方式则是以指针访问、地址传参的形式进行。
#include <iostream>
using namespace std;
struct SLAugend
{
int iAugend;
};
int Add(struct SLAugend *pSLAugend, int iAddend)
{
return pSLAugend->iAugend + iAddend;
}
int main()
{
struct SLAugend augend;
augend.iAugend = 2;
cout << Add(&augend, 5) << endl;
return 0;
}
可能会觉得这样的代码有些赘余,明明不用通过指针的形式访问结构体的,可以直接通过Add2(augend.iAugend,5)
来得出结果;确实如此,不过按这样说一个加法器实际上都用不到结构体呢。。。这里只是为了便于理解编程范式所举的例子,而且这样子的好处在之后的代码中就显现出来了,对于变化后的代码:
#include <iostream>
using namespace std;
struct SLAugend
{
int iAugend;
};
int Add(struct SLAugend *pSLAugend, int iAddend)
{
return pSLAugend->iAugend + iAddend;
}
struct SLWeightingAugend
{
int iAugend;
int iWeight;
};
int WeightingAdd(struct SLWeightingAugend *pSLWeightingAugend, int iAddend)
{
return pSLWeightingAugend->iWeight * pSLWeightingAugend->iAugend + iAddend;
}
int main()
{
struct SLWeightingAugend augend;
augend.iAugend = 2;
augend.iWeight = 3;
cout << WeightingAdd(&augend, 5) << endl;
return 0;
}
我们增加了一个新结构体和新的加法函数,主函数基本没变化(只是增加了权重变量的值和结构体名称),emm,这段代码确实复杂而且赘余!
方案2
通过结构化思想所建立的代码不够完善,我们尝试利用基于对象的思想进行代码的重构,可以编写一个加法器的类,数据成员做为私有变量保存着,然后再写一个公有的加法方法:
#include <iostream>
using namespace std;
class CLAdder
{
public:
explicit CLAdder(int iAugend)
{
m_iAugend = iAugend;
}
int Add(int iAddend)
{
return m_iAugend + iAddend;
}
private:
int m_iAugend;
};
int main()
{
CLAdder adder(2);
cout << adder.Add(4) << endl;
return 0;
}
这段代码就比较清晰了,构造函数CLAdder会自动执行赋值的操作,而调用类的方法也是非常简单的:adder.Add(4)
。然后我们再看看变化之后的代码:
#include <iostream>
using namespace std;
class CLWeightingAdder
{
public:
CLWeightingAdder(int iAugend, int iWeight)
{
m_iAugend = iAugend;
m_iWeight = iWeight;
}
int Add(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
private:
int m_iAugend;
int m_iWeight;
};
int main()
{
CLWeightingAdder adder(2, 3);
cout << adder.Add(5) << endl;
return 0;
}
仍然对部分老代码进行了修改,忽视了代码的封闭性,所以也不算上是好的设计。
方案3
好在面向对象思想里面为我们提供了一个“继承”与“虚函数”的概念,我们直接对变化进行处理,先看代码:
#include <iostream>
using namespace std;
class CLAdder
{
public:
explicit CLAdder(int iAugend)
{
m_iAugend = iAugend;
}
virtual ~CLAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend + iAddend;
}
protected:
int m_iAugend;
};
class CLWeightingAdder : public CLAdder
{
public:
CLWeightingAdder(int iAugend, int iWeight) : CLAdder(iAugend)
{
m_iWeight = iWeight;
}
virtual ~CLWeightingAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
protected:
int m_iWeight;
};
int main()
{
CLAdder adder(2);
cout << adder.Add(4) << endl;
CLWeightingAdder wadder(3, 4);
cout << wadder.Add(4) << endl;
return 0;
}
CLAdder这个类里面提供了一个虚函数Add,然后通过class CLWeightingAdder : public CLAdder
继承了基函数,在里面对虚函数进行重载,加上权重的功能。这段代码做到了代码的封闭性,也能封装变化点。
方案4
假定我们需求改变:1.需要实现一个加法器:在这个加法器中,已经保存了被加数;现在需要传递加数给这个加法器,以让其返回加法计算结果。2.普通加法器的被加数,必须是非负的整数,而带权重的加法器的被加数,没有任何限制.3,实现变化
换一个新的角度去思考,我们可以基于接口的思想,即定义一个加法接口的抽象类,然后让普通加法器和带权重的加法器从这个抽象类中派生出来,先看代码:
#include <iostream>
using namespace std;
class ILAdder
{
public:
ILAdder()
{
}
virtual ~ILAdder()
{
}
virtual int Add(int iAddend) = 0;
};
class CLAdder : public ILAdder
{
public:
explicit CLAdder(unsigned int iAugend)
{
m_iAugend = iAugend;
}
virtual ~CLAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend + iAddend;
}
private:
unsigned int m_iAugend;
};
class CLWeightingAdder : public ILAdder
{
public:
CLWeightingAdder(int iAugend, int iWeight)
{
m_iWeight = iWeight;
m_iAugend = iAugend;
}
virtual ~CLWeightingAdder()
{
}
virtual int Add(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
private:
int m_iAugend;
int m_iWeight;
};
void f(ILAdder *pAdder)
{
cout << pAdder->Add(4) << endl;
}
int main()
{
CLAdder adder(2);
f(&adder);
CLWeightingAdder wadder(3, 4);
f(&wadder);
return 0;
}
由于题目的需求更改为普通加法器的被加数,必须是非负的整数,而带权重的加法器的被加数,没有任何限制,所以需要对被加数的数据形式进行限定,可以将数据定义从基类中分离出来,在每个派生类中进行数据的定义与虚函数Add的重载,就能实现任务。
01.png方案5
由于要涉及不同的编程范式,呢么我们不如再延伸一点,给予接口的思想模板实现。“模板”这个概念在C++中有着非同小可的意义,是泛型编程的重点,比如著名的STL库,肯定需要适配用户的不同需要,则时候通过模板就能满足这一点。让我们看看这一段代码:
#include <iostream>
using namespace std;
template<typename T>
class ILAdder
{
public:
ILAdder()
{
}
virtual ~ILAdder()
{
}
int Add(int iAddend)
{
T *pThis = (T *)(this);
return pThis->AddImpl(iAddend);
}
};
class CLAdder : public ILAdder<CLAdder>
{
public:
explicit CLAdder(unsigned int iAugend)
{
m_iAugend = iAugend;
}
virtual ~CLAdder()
{
}
int AddImpl(int iAddend)
{
return m_iAugend + iAddend;
}
private:
unsigned int m_iAugend;
};
class CLWeightingAdder : public ILAdder<CLWeightingAdder>
{
public:
CLWeightingAdder(int iAugend, int iWeight)
{
m_iWeight = iWeight;
m_iAugend = iAugend;
}
virtual ~CLWeightingAdder()
{
}
int AddImpl(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
private:
int m_iAugend;
int m_iWeight;
};
template<typename T>
void f(ILAdder<T> *pAdder)
{
cout << pAdder->Add(4) << endl;
}
int main()
{
CLAdder adder(2);
f(&adder);
CLWeightingAdder wadder(3, 4);
f(&wadder);
return 0;
}
其中class CLAdder : public ILAdder<CLAdder>
与class CLWeightingAdder : public ILAdder<CLWeightingAdder>
是将模板参数T设置为CLAdder与CLWeightingAdder。这是因为在ILAdder这个类当中有pThis->AddImpl(iAddend)
,调用AddImpl函数进行加法,但是ILAdder并不知道会用哪一个,所以需要通过模板参数定位T *pThis = (T *)(this);
,定位后就会到相应的派生类当中执行加法程序。
方案6
还可以靠增加基类来扩展加法器,就是说执行加法程序的是一个派生类,而继承的是不同的基类来实现变化的需求,先看代码:
#include <iostream>
using namespace std;
template<typename T>
class CLAdder : public T
{
public:
CLAdder() : T()
{
}
virtual ~CLAdder()
{
}
int Add(int iAddend)
{
T *pThis = (T *)(this);
return pThis->AddImpl(iAddend);
}
};
class CLNormalImpl
{
public:
CLNormalImpl()
{
m_iAugend = 0;
}
void Set(unsigned int iAugend)
{
m_iAugend = iAugend;
}
virtual ~CLNormalImpl()
{
}
int AddImpl(int iAddend)
{
return m_iAugend + iAddend;
}
private:
unsigned int m_iAugend;
};
class CLWeightingImpl
{
public:
CLWeightingImpl()
{
m_iWeight = 0;
m_iAugend = 0;
}
void Set(int iAugend, int iWeight)
{
m_iWeight = iWeight;
m_iAugend = iAugend;
}
virtual ~CLWeightingImpl()
{
}
int AddImpl(int iAddend)
{
return m_iAugend * m_iWeight + iAddend;
}
private:
int m_iAugend;
int m_iWeight;
};
template<typename T>
void f(CLAdder<T> *pAdder)
{
cout << pAdder->Add(4) << endl;
}
int main()
{
CLAdder<CLNormalImpl> adder;
adder.Set(2);
f(&adder);
CLAdder<CLWeightingImpl> wadder;
wadder.Set(3, 4);
f(&wadder);
return 0;
}
class CLAdder : public T
是继承一个模板参数,而模板参数是从主函数中获得的,和方案4刚好思路相反,一个是确定积累,通过扩展派生类实现;而这里是增加基类不动派生类。
总结
实际上,这些编程范式的变化和设计模式相对应起来了,因为代码的优化当中就是朝着更简便、重复更少取得,而设计模式里就是主要面向开发的,所以更为契合。
网友评论