美文网首页
从一个加法器看简单的编程范式

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

作者: MrYun | 来源:发表于2018-09-07 02:28 被阅读37次

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

    编程范式

    编程范式(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.haomeiwen.com/subject/uelqgftx.html