从一个加法器看简单的编程范式

从一个加法器的实现看简单的编程范式

编程范式

编程范式(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刚好思路相反,一个是确定积累,通过扩展派生类实现;而这里是增加基类不动派生类。

总结

实际上,这些编程范式的变化和设计模式相对应起来了,因为代码的优化当中就是朝着更简便、重复更少取得,而设计模式里就是主要面向开发的,所以更为契合。

原文地址:https://www.cnblogs.com/yunlambert/p/9602244.html