[C++] 类的"实现"与"接口"分离

C++这门语言囊括了多种语言范式,并不是严格的OOP语言,所以在“实现与接口的分离”这一方面做得并不算好。 这里简述C++的类设计中把实现与接口分离的方法.

在C++的某个class的定义中,不仅声明了接口,还可以看到实现的具体细节,当然这个视角是针对类内部的,通常是在私有域private中,比如下面这个类:

class Person {
public:
Person(const std::string& name, cpnst Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
//实现的细节
std::string theName;
Date theB工rthDate;
Address theAddress;
};


要实现这么一个类,一般的做法就是在所在声明的头文件中include DateAddress两个类的声明文件include"date.h"/include"address.h",如果Date或者Address类发生了改动,那么连带Person类及包含Person头文件的后续文件都要重新编译。这样子的话Person类的使用者就会有极大的不便,Person/Date/Address中的任何一个类改动,Person客户端都必须重新编译。

我们可以借鉴类似Java中的做法,声明一个类时,出现的是一个指向该类型的指针,只要分配给该指针足够的空间,我们就可以隐藏其“实现”了。我打一个可能不是那么形象的例子,原始版本就是一本书,我们是这本书的使用者,书中的内容有改动时,出版社就需要重新印刷一份,现在我们不用拿着书了,我们手中只有一个目录,我们需要资料的时候直接按照目录向出版社拿内容,出版社可以随时更新他手头的资料内容,而我们只要专注于“索要”资料就行了。

但是这样子分离的还不够彻底,我们把Person类分离为两部分,纯粹的两部分,一部分叫实现类(定义为PersonImpl [Person Implementation]),另一部分叫接口类(即 Person)。
把具体的实现放在PersonImpl类中,然后通过Person类管理指针形式的PersonImpl对象,然后把接口暴露给客户,实现分离。

#include <string>
#include <memory>

class Personlmpl;  //实现类的声明
class Date;     //接口中需要用到的其他类的声明
class Address;

class Person {
public:
Person(const std::string& name, const Date& birthday,
const Address& addr);
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::shared_ptr<PersonImpl> plmpl;  //指针的形式,且遵循以类管理资源的原则
};

在PersonImpl类和Person类中,有着同名的成员函数,接口也是完全一样的
这样的设计,Person类的使用者就完全的与Person/Date/Address三种类的实现细节分离开了,只要专注于接口的使用就OK了。
并且在这样的设计下,如果Date,Address之类的Class有变动,Person类的使用者也不需要重新编译。

另一种方法也许更常见,就是把接口(Interface)都在一个接口类(抽象类)中描述,然后由它的派生类来具体实现,有着纯虚函数的类就是抽象类,抽象类是一种不能实例化的特殊类,所以经常可以用来描述接口:

class Person {
public:
virtual -Person();
virtual std::string name() const = 0;  // =0后缀,即为纯虚函数
virtual std::string birthDate() canst = 0;
virtual std::string address() const = 0;
};

正常的使用就是在派生类中具体实现:

class RealPerson: public Person {
public:
RealPerson(const std::string& name, const Date& birthday,
const Address& addr)
theName(name) , theBirthDate(birthday) , theAddress(addr)
{}
virtual -RealPerson() { )
std::string name() const;
std::string birthDate() const;
std::string address() const;
private:
std::string theName;
Date theBirthDate;
Address theAddress;
}

除此之外,抽象类也是“工厂设计模式”的一种比较优良的实践,我们可以通过一个在抽象类中定义一个create函数(通常是静态方法,用static关键字来修饰),用来动态生成继承体系中的各种实例,都可以通过一个create函数来实现,当然别忘了,为了满足多态的要求,返回的应该是一个指针(*)或引用(&)

class Person {
public:
static std::shared_ptr<Person> create(const std::string& name, const Date& birthday, const Address& addr)
{
return std::shared_ptr<Person>(new RealPerson(name, birthday,addr);  //此例中只展示了生成RealPerson实例,更真实的实践中通常取决于参数,环境等等
}

};

当然两种方法都是很好很实用的剥离“实现”与“接口”的实践,减少类与类之间的耦合度。
他们分别叫做handle classinterface class,可以从名字形象的推测出它们的含义。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 接口实现分离是一种常见的编程技术,常用于实现库或模块化设计。它的主要思想是将接口实现分开,使得接口可以独立于实现进行设计和修改,从而提高代码的可读性、可维护性和可扩展性。 具体来说,通过分离接口实现,我们可以在头文件中定义接口,而将实现放在源文件中。这样,当其他代码需要使用该接口时,只需要包含头文件即可,而不需要了解实现的细节。同时,如果需要修改接口,只需要修改头文件,而不需要修改源文件,从而减少了代码的耦合度。此外,这种分离还可以提高编译速度,因为只有当实现发生改变时,才需要重新编译源文件。 举个例子,假设我们要设计一个简单的计算器,它包含加、减、乘、除四种运算。我们可以将接口定义在一个名为 Calculator.h 的头文件中,如下所示: ``` class Calculator { public: virtual double add(double x, double y) = 0; virtual double subtract(double x, double y) = 0; virtual double multiply(double x, double y) = 0; virtual double divide(double x, double y) = 0; }; ``` 接口中定义了四个纯虚函数,表示四种运算。然后,我们可以将实现放在一个名为 Calculator.cpp 的源文件中,如下所示: ``` class SimpleCalculator : public Calculator { public: double add(double x, double y) { return x + y; } double subtract(double x, double y) { return x - y; } double multiply(double x, double y) { return x * y; } double divide(double x, double y) { return x / y; } }; ``` 在这个例子中,我们定义了一个名为 SimpleCalculator 的,它继承自 Calculator 接口,并实现了四种运算。通过这种方式,我们可以将接口实现分开,从而使得代码更加清晰、易读、易维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值