原文链接:一个用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
#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; }
#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; }
#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
#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); }
#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; }
#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
#include "parent.h" #include "child.h" PARENT* get_child() { return new CHILD; }
#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
#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; }
#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背景图像的更改 }
公有接口是类的抽象组件,类函数定义是实现细节,把两者分离,具体做法就是类成员函数定义与公有接口分属不同的代码文件。
接口与实现的分离其实你一直在使用,最直接的例子就是标准库,平时你使用标准库的头文件,这个就是个接口,而标准库被封装在不同的地方,例如静态库,这个就是实现。两者是分离的。
接口和实现分离,模块间的依赖会自然转化为对接口数据类型的依赖