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

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

编程范式

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

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • mean to add the formatted="false" attribute?.[ 46% 47325/...
    ProZoom阅读 2,689评论 0 3
  • 23种设计模式 “对象性能”模式 面向对象很好的解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况...
    孙浩_9bfd阅读 509评论 0 0
  • 基本概念 1a general-purpose programming language用于创建计算机程序。艺术类...
    伍帆阅读 1,298评论 0 1
  • 状态模式 一、描述 概念:允许一个对象在其内部状态改变时改变它的行为。 对象看起来似乎修改了它的类。 问题: 每个...
    卡尔曼阅读 221评论 0 0
  • 无尽的路,在你的脚下延伸,在我的身旁远去。晨曦的蓝天下,风柔柔,山青青,你的脚步为何这样急促? 我追逐,却无力,惟...
    老树_阅读 1,477评论 32 153