Item31:最小化文件之间的编译依赖

芝士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),提高代码的可维护性和可扩展性。它允许独立地修改和扩展派生类,而不会对其他部分产生意外的影响。同时,它也提供了更好的代码组织和模块化,使得代码更易于理解和维护。

posted @   芝士wa  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示