ITKeyword,专注技术干货聚合推荐

注册 | 登录

『C/C++』一个用C/C++分别实现接口与实现相分离的设计原则的例子

fallenink 分享于 2013-08-19

推荐:C++中接口与实现分离的技术

在用C++写要导出类的库时,我们经常只想暴露接口,而隐藏类的实现细节。也就是说我们提供的头文件里只提供要暴露的公共成员函数的声明,类的其他所有信息都不会

2019阿里云双12.12最低价产品入口(新老用户均可),
地址https://www.aliyun.com/minisite/goods

原文链接:一个用C/C++分别实现接口与实现相分离的设计原则的例子


良好的设计应该只暴露接口给用户,所有的实现细节对用户来说应该是隐藏的,也就是说用户只要给接口传递相应的参数就行了,不需要管内部是如何实现的,比如我们使用fopen,fseek,CreateWindow等函数会发现很好用,而不需要管fopen,fseek,CreateWindow函数内部代码是如何实现的,数据结构是如何组织的,也就是说绝对不能暴露任何的细节给用户,包括数据组织在内。


我现在用C和C++举一个例子,来说说C/C++分别是如何实现的,然后来看看哪种实现更好。


先来看C++用类实现的封装:


--------------------------- interface1.h ---------------------------

#ifndef INTERFACE1_H
#define INTERFACE1_H

class DATA
{
private:
    int _i;
    short _j;

public:
    DATA();
    ~DATA();

    void set(int i, short j);
    void get(int* i, short* j);    
};

#endif

--------------------------- interface1.cpp ---------------------------

#include "interface1.h"

DATA::DATA()
{
    _i = _j = 0;
}

DATA::~DATA()
{
    _i = _j = 0;
}


void DATA::set(int i, short j)
{
    _i = i;
    _j = j;
}

void DATA::get(int* i, short* j)
{
    *i = _i;
    *j = _j;
}

--------------------------- test.cpp ---------------------------

#include <stdio.h>
#include "interface1.h"

int main()
{
    DATA data;
    int i;
    short j;

    data.set(2, 3);
    data.get(&i, &j);
            
    printf("i = %d, j = %d\n", i, j);    

    return 0;
}

再来看 C 如何巧妙的封装以及隐藏实现细节:


---------------------------  interface.h  ---------------------------

#ifndef INTERFACE_H
#define INTERFACE_H

void* data_create();
void data_set(void* dummy, int i, short j);
void data_get(void* dummy, int* i, short * j);
void data_destroy(void* dummy);

#endif

---------------------------  interface.c  ---------------------------

#include <stdlib.h>

struct DATA
{
    int i;
    short j;
};

void* data_create()
{
    return malloc(sizeof(struct DATA));
}

void data_set(void* dummy, int i, short j)
{
    struct DATA* data = dummy;

    data->i = i;
    data->j = j;
}

void data_get(void* dummy, int* i, short * j)
{
    struct DATA* data = dummy;

    *i = data->i;
    *j = data->j;
}

void data_destroy(void* dummy)
{   
    free(dummy);
}

---------------------------  test.c  ---------------------------

#include <stdio.h>
#include "interface.h"

int main()
{
    int i;
    short j;

    void* data = data_create();

    data_set(data, 2, 3);

    data_get(data, &i, &j);    
    printf("i = %d, j = %d\n", i, j);

    data_destroy(data);

    return 0;
}

可以看的出来,C的实现只暴露了接口给用户,内部的实现细节都隐藏了起来,可是C++用类实现反而在头文件暴露了实现细节。
当然用C++也可以做到只暴露接口给用户,不过实现起来会比较复杂,而且需要消耗更多的内存(使用了虚函数)。


-------------------------------------- parent.h --------------------------------------

#ifndef PARENT_H
#define PARENT_H

class PARENT
{
public:
    virtual void set(int i, short j) = 0; 
    virtual void get(int* i, short* j) = 0;
};

PARENT* get_child();

#endif

-------------------------------------- parent.cpp --------------------------------------

#include "parent.h"
#include "child.h"

PARENT* get_child()
{
    return new CHILD;
}

-------------------------------------- child.h --------------------------------------

#ifndef CHILD_H
#define CHILD_H


#include "parent.h"


class CHILD : public PARENT
{
private:
    int _i;
    short _j;


public:
    CHILD();
    ~CHILD();


    void set(int i, short j);
    void get(int* i, short* j);    
};


#endif

-------------------------------------- child.cpp --------------------------------------

#include "child.h"

CHILD::CHILD()
{
    _i = _j = 0;
}

CHILD::~CHILD()
{
    _i = _j = 0;
}


void CHILD::set(int i, short j)
{
    _i = i;
    _j = j;
}

void CHILD::get(int* i, short* j)
{
    *i = _i;
    *j = _j;
}

-------------------------------------- test.cpp --------------------------------------
#include <stdio.h>
#include "parent.h"

int main()
{    
    int i;
    short j;

    PARENT* parent = get_child();

    parent->set(2, 3);
    parent->get(&i, &j);
            
    printf("i = %d, j = %d\n", i, j);    

    return 0;
} 


另外:

关注这个帖子:http://bbs.csdn.net/topics/310212385,摘录几句,当思考下:


我们知道,类有接口(成员函数),有数据、状态变量等,这些都有实现代码。由于接口是要向外公开的,而实现是需要隐藏的(用户不需要知道),这样才能应对变化。比如接口的实现有变化,或者一个接口有多种可能的实现,我们就可以随意修改这些实现,而不影响用户的使用,因为用户看到的只是对外公开的接口,接口并没有变。

   将接口与实现分离的技术:

   (1)Interface class:将接口部分实现为abstract base class,在C++中,这个抽象类中含一个virtual析构函数和一组pure virtual函数,实现部分则由派生出来的各个子类来完成。C++/Java/C#中都有现成的abstract类机制,例子就不需要举了。

   (2)Handle class:也称为pimpl技术,就是把隶属对象的数据(即实现)从原对象中抽离出来,封装成一个独立的impl对象,在原对象中用一个指针成员指向它,一般使用智能指针。举一个例子,按钮组件通常都有一个背景图像作为数据,通常这个图像作为组件类的一个成员,为了分离实现,我们把这些数据抽离出来进行独立的封装:

struct PMImpl{ //封装数据对象的
    std::tr1::shared_ptr<Image> bgImage; //指向背景图像的智能指针
    int imageChanges;  //背景图像更改次数
};
 
class Button{ //按钮类
private:
    Mutex mutex; //互斥锁
    std::tr::shared_ptr<PMImpl> pImpl; //指向数据对象的智能指针
public:
    void changeBackground(std::istream &imgSrc);
    //...
};
 
void Button::changeBackground(std::istream &imgSrc){
    using std::swap;  //使用这个函数进行异常安全编程
    Lock m1(&mutex); //加锁,下面成为临界区
    std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); //创建临时的pNew,
                                              //并指向了原有的数据对象(含有背景图像)
    pNew->gbImage.reset(new Image(imgSrc)); //根据传进来的图像,更改背景图像
    ++pNew->imageChanges;  //更改次数加1
    swap(pImpl,pNew); //把更改后的数据交换到pImpl中,并且会释放互斥锁mutex,
                      //从而完成了Button背景图像的更改
}



接口主要是用于模块间的交互,实现就是具体的处理逻辑;如果接口过多的牵扯处理逻辑,代码的重用性会比较差;如果接口和实现分开,新增功能只需要关注具体的实现,不需要改动接口。


公有接口是类的抽象组件,类函数定义是实现细节,把两者分离,具体做法就是类成员函数定义与公有接口分属不同的代码文件。

接口与实现的分离其实你一直在使用,最直接的例子就是标准库,平时你使用标准库的头文件,这个就是个接口,而标准库被封装在不同的地方,例如静态库,这个就是实现。两者是分离的。


接口和实现分离,模块间的依赖会自然转化为对接口数据类型的依赖




推荐:为什么C++编译器不支持模板头文件和实现代码分离的编译

首先,C++标准中提到,一个编译单元[translation unit]是指一个.cpp文件以及它所include的所有.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,然后编译

原文链接:一个用C/C++分别实现接口与实现相分离的设计原则的例子 良好的设计应该只暴露接口给用户,所有的实现细节对用户来说应该是隐藏的,也就是说用户只要给接口传递相应的参数就行了,不需

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。