芝士wa
2024.4.11
Item31链接
引子
“你进入到你的程序中,并对一个类的实现进行了细微的改变。提醒你一下,不是类的接口,只是实现,仅仅是 private 的东西。然后你重建(rebuild)这个程序,预计这个任务应该只花费几秒钟。毕竟只有一个类被改变。你在 Build 上点击或者键入 make(或者其它等价行为),接着你被惊呆了,继而被郁闷,就像你突然意识到整个世界都被重新编译和连接!当这样的事情发生的时候,你不讨厌它吗?”
问题在于C++没有做好从实现中剥离接口的工作,有两种方法可以实现:Handles和Interface。
Handles方法
采用前向声明:
class Date; // forward declaration
class Address; // forward declaration
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;
...
};
这个分离的关键就是用对声明的依赖替代对定义的依赖。这就是最小化编译依赖的精髓。
更进一步,采用pointer to implement的方法,将函数调用方法写成Person类的数据成员,
Person.h
#include <string>
#include <memory>
class PersonImpl; // Person实现类的前向声明
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> pImpl; // 指向PersonImpl的智能指针
};
Person.cpp
#include "Person.h" // we're implementing the Person class,
// so we must #include its class definition
#include "PersonImpl.h" // we must also #include PersonImpl's class
// definition, otherwise we couldn't call
// its member functions; note that
// PersonImpl has exactly the same
// member functions as Person — their
// interfaces are identical
Person::Person(const std::string& name, const Date& birthday,
const Address& addr)
: pImpl(new PersonImpl(name, birthday, addr))
{}
std::string Person::name() const
{
return pImpl->name();
}
在Person.h中采用了前向声明,在Person.cpp中包含了Person的头文件和PersonImpl的头文件。
Interface方法
将Person类写成纯虚类,作为模板提供接口,
class Person {
public:
virtual ~Person();
virtual std::string name() const = 0;
virtual std::string birthDate() const = 0;
virtual std::string address() const = 0;
...
};
在派生类中实现Person,
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; // implementations of these
std::string birthDate() const; // functions are not shown, but
std::string address() const; // they are easy to imagine
private:
std::string theName;
Date theBirthDate;
Address theAddress;
};
这种设计方式遵循了面向对象编程中的"依赖倒置原则"(Dependency Inversion Principle)。根据这个原则,高层模块(接口类)不应该依赖于低层模块(派生类)的具体实现,而是应该依赖于抽象(接口)。由于接口类的定义不依赖于派生类,当需要修改或添加新的派生类时,不会影响到接口类和其他派生类的编译。只需编译和链接新的派生类即可,而不需要重新编译接口类或其他派生类。
这种减少文件之间相互依赖的设计有助于降低代码的耦合性(coupling),提高代码的可维护性和可扩展性。它允许独立地修改和扩展派生类,而不会对其他部分产生意外的影响。同时,它也提供了更好的代码组织和模块化,使得代码更易于理解和维护。