我见过许多用C++语言或者像Java这样的纯OO语言编写的程序,其中使用了类,但这些只是由程序组成的大型命名空间而已。

4 类的设计原则 - 图1

1 环依赖和依赖倒置

1.1 环依赖的例子

A类中包含B类的成员变量,B类也包含A类的成员变量,导致A和B相互依赖,称为“环依赖”。两个类不能分开,不能独立地使用。

  1. #ifndef HUAN_YI_LAI
  2. #define HUAN_YI_LAI
  3. class Customer;
  4. class Account
  5. {
  6. public:
  7. void setOwner(Customer* customer)
  8. {
  9. owner = customer;
  10. }
  11. private:
  12. Customer* owner;
  13. };
  14. class Customer
  15. {
  16. public:
  17. void setAccount(Account* account)
  18. {
  19. customerAccount = account;
  20. }
  21. private:
  22. Account* customerAccount;
  23. };
  24. #endif // !HUAN_YI_LAI

两个类的关系图如下:
image.png

1.2 如何修改?

构建一个抽象的Owner接口基类,将Customer与Account的的关联关系变为如下所示:
image.png

  1. #ifndef HUAN_YI_LAI
  2. #define HUAN_YI_LAI
  3. #include <string>
  4. class Owner
  5. {
  6. public:
  7. virtual ~Owner() = default;
  8. virtual std::string getName() const = 0;
  9. };
  10. class Account
  11. {
  12. public:
  13. void setOwner(Owner* customer)
  14. {
  15. owner = customer;
  16. }
  17. private:
  18. Owner* owner;
  19. };
  20. class Customer : public Owner
  21. {
  22. public:
  23. void setAccount(Account* account)
  24. {
  25. customerAccount = account;
  26. }
  27. virtual std::string getName() const override
  28. {
  29. // return something
  30. }
  31. private:
  32. Account* customerAccount;
  33. };
  34. #endif // !HUAN_YI_LAI

有了Owner的接口基类之后,我们在给Account设置Owner的时候就可以进行依赖注入,Account需要什么样的Owner,我们就可以通过setter注入什么Owner(可以是customer、User或其他子类)。最终我们通过依赖倒置和依赖注入将Customer和Accoutn进行了分离。

1.3 依赖倒置更直观的例子

image.png
Person类依赖了具体的工具,例如Person中有一个方法drive(Car),这样Person就对具体的交通工具产生了依赖,如果这个时候想要使用其它的交通工具如Bike,Bus,就需要修改Person类。
因此将原来的设计修改成了右边的样子,引入了抽象接口Transportation(交通工具),这样Person只需要依赖交通工具即可,至于到底选择何种工具,就交给IOC容器,进行依赖注入即可。

2 迪米特法则的例子

下面的例子中,Driver可以直接操作Car类,但是不能直接调用Engine、FuelPump等结构上更底层的类:
image.png