浅谈IoC——C++怎么实现IoC?

浅谈IoC——C++怎么实现IoC?

有系统学习过Spring框架的同学都知道,Spring的一个很重要也是很有设计美感的一个特性——IoC (Inversion of Control),即控制反转。所谓控制反转,就是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。

一切设计模式的思想都是为了提高内聚性、减少耦合性,这是软件工程追求的终极目标。在一个大型的软件系统中,面对复杂的业务逻辑,可以预知必然会有极其复杂的模块和对象之间的依赖关系,IoC的目的就是解决对象之间的耦合度过高的问题。

内聚性(Cohesion)是指在一个模块内部,各个元素彼此之间紧密相关的程度。一个拥有高内聚性的模块通常只负责一项任务或有一组密切相关的职责,其内部的功能紧密集中,易于理解和维护。高内聚性是优秀软件设计的特点之一。

耦合性(Coupling)描述的是模块之间依赖的紧密程度。耦合性低的系统具有模块间的独立性,一个模块的变更对其他模块的影响较小,这样的系统更易于测试和维护。

这里有一套非常形象的图片展示了IoC容器的作用

假设一个软件系统有四个对象,这四个对象互相依赖,相互耦合。假设存在一个齿轮的变动,尺寸变大或者变小,那么对于整个系统来说则是牵一发而动全身。

9803e91216a7

Ioc思想引入一个第三方容器来实现具有依赖关系的对象之间的解耦,四个齿轮不再直接耦合,而是依靠第三方齿轮进行传动。

d605eb394e30

由于IoC容器对于业务侧是透明的,因此在业务眼中,四个相互耦合依赖的对象变成了下面的样子。

7f02a8b1970a

IoC体现了好莱坞原则,即“不要打电话过来,我们会打给你”。这里的“我们”指的是外部容器或框架,而“你”则指的是应用程序中的对象。在IoC的设计理念中,对象的依赖关系和生命周期管理不是由对象自己来负责,而是由外部容器或框架来管理和控制。这种设计理念与好莱坞原则相契合,因为外部容器或框架会主动联系对象,告诉它们需要做什么,而不是对象自己去寻找或请求依赖项。这样,对象就只需要专注于自己的职责和功能实现,而不需要关心依赖关系的处理和管理。模板方法模式也体现了好莱坞原则的思想。在模板方法模式中,基类定义了算法的骨架,而将一些具体步骤的实现延迟到子类中。这样,子类只需要关注自己需要实现的具体步骤,而不需要关心整个算法的流程和控制。这也是一种将控制权从子类转移到基类的方式。

Spring是怎么实现IoC的?

Spring中的IoC底层是通过工厂模式+反射实现的,具体来说,它的实现步骤是这样的:

首先,我们通过以下代码初始化 IoC 容器:

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

之后会创建一个工厂类,工厂类中有一个创建 Bean 的方法 createBean。

createBean 中首先会通过读取配置文件,获取到全类名,如下所示:

<beans> 
  <bean id="myBean" class="com.example.MyBean" /> 
</beans>


之后通过反射,将获取到的全类名进行加载,创建对象存放到 IoC 容器中。

当有代码使用了 DI 时,从容器中找到(根据类名或类型查找)此实例进行使用,如下代码所示:

@Component
public class MyBean {
     
 @Autowired
 private MyBean myBean;
 public void doSomething() {
     
     System.out.println("Bean: " + myBean);
 }
}

IoC最核心的实现原理是反射!!!

反射(Reflection)是Java编程语言中的一种特性,它允许程序在运行时检查、分析和操作对象、类、接口、方法和字段等元数据。通过反射,可以实现在程序运行过程中动态地创建对象、调用方法以及获取和设置属性值等操作。

为什么Java可以支持反射?

1. 设计哲学:Java的设计哲学之一是“一切皆对象”,因此,连类本身也被视为对象。这意味着类也可以被实例化,并且可以像其他对象一样被操作,这就为反射提供了基础。

2. JVM支持:Java虚拟机(JVM)在设计和实现上为反射提供了支持。JVM在加载类时会为每个类生成一个Class对象,该对象包含了该类的所有元数据信息。通过这个Class对象,程序就可以访问和操作该类的信息。

3. 语言特性:Java提供了一组API来支持反射,如java.lang.reflect包中的类和方法。这些API允许程序在运行时获取类的名称、修饰符、父类、接口、字段、方法等信息,以及创建对象、调用方法、获取和设置属性值等。

当然除了Java之外,许多其他编程语言也支持反射机制。当然,原生的C/C++是不支持反射的。

这是因为反射要求语言运行时具备能够在执行期间查询和修改程序元数据的能力、需要额外的信息存储以及动态类型处理机制、以及可能会导致效率降低,与C/C++的设计哲学并不相符,C和C++设计哲学强调的是高效性和对硬件的紧密控制。C/C++通常用于系统编程和性能敏感的应用,设计时注重的是静态类型检查和编译期优化,以在编译时期就解决尽可能多的问题。这意味着很多结构和信息,如类型信息,在编译完成后都会丢失或不保存到可执行文件中。相比之下,支持反射的语言如Java、C#等,则在运行时维护着丰富的类型信息,需要更高的资源消耗但提供了更灵活的编程模型。

C++的元编程能力相对较弱,无法像Java或C#那样通过运行时类型信息(RTTI)来获取和操作类的元数据。虽然C++提供了一些模板元编程的技术,但这些技术在实现反射机制时通常显得不够灵活和强大。

C++怎么实现IoC?

C++作为静态编译类型的语言,无法在程序执行期间查询和修改程序元数据的能力,显然是不支持运行时反射的。但是可以利用一些技术模拟反射,也就是编译时反射。

编译时反射 (Compile-time Reflection) ,这种反射在编译时确定类型信息,不需要动态类型识别 (RTTI)。C++实现编译时反射主要依靠两个技术——宏(Macro)模板元编程(Template Metaprogramming, TMP),例如,Boost库中的boost::reflect库就使用了宏和模板元编程来实现类的元数据定义和处理。

宏 Macro

宏可以通过#define预处理指令来定义。在这个例子中,PI被定义为常量3.14159。在程序中使用PI时,预处理器会在编译前将其替换为3.14159

#define PI 3.14159

宏也可以定义函数式的。这个宏定义了一个名为SQUARE的宏,它接受一个参数x,并返回x的平方。使用宏时要注意,由于宏只是简单的文本替换,所以可能会导致预期之外的副作用,比如操作符优先级问题或者多次求值问题(例如,如果x是一个有副作用的表达式)。

#define SQUARE(x) ((x) * (x))

在创建比较复杂的宏时,可以用 ## 

# 的用法是负责将其后面的东西转化为字符串,比如

#include<iostream>

#define TO_STRING(str) #str

int main() {
  std::cout << TO_STRING(this is a string) << std::endl;
  return 0;
}

## 是连接符,将前后两个东西连接成一个词。比如:

#include<iostream>

#define IntVar(i) int_##i

int main() {
  int int_1 = 1, int_2 = 2;
  std::cout << IntVar(1) << ", " << IntVar(2) << std::endl;
  return 0;
}

宏实现一个简单的IoC

现在定义了一个handler类

class Handler {
 public:
  Handler() {}
  Handler(const std::string& task) : task(task) {}
  void handler() { std::cout << "Handler " << task << std::endl; }
  void setTask(const std::string& task) { this->task = task; }

 private:
  std::string task;
};

将类注册到IoC容器的宏

// 将类注册到工厂的宏
#define REGISTER_CLASS(T)                     \
  struct CLASS_FACTORY_##T {                  \
    static T createInstance() { return T(); } \
  };

从IoC的工厂获取对象实例的宏

// 创建对象实例的宏
#define CREATE_INSTANCE(T) CLASS_FACTORY_##T::createInstance()

接下来将Handler类注册到IoC容器

REGISTER_CLASS(Handler)

业务代码获取handler并执行

auto handler = CREATE_INSTANCE(Handler);
  handler.setTask("tsk1");
  handler.handler();

大部分情况下我们交给IoC容器托管的都是单例类,也就是说整个运行过程中只需要一个实例即可

注册单例类和携带初始值的单例类

// 单例模式的实现
#define REGISTER_CLASS_SINGLETON(T, CLASS_NAME)                        \
  static T instance_##CLASS_NAME;                                      \
  struct SINGLETON_CLASS_FACTORY_##T {                                 \
    static T& getSingletonInstance() { return instance_##CLASS_NAME; } \
  };

// 注册单例类并设置初始值
#define REGISTER_CLASS_SINGLETON_WITH_VALUE(T, CLASS_NAME, VALUE) \
  static T instance_##CLASS_NAME;                                 \
  struct SINGLETON_CLASS_FACTORY_##CLASS_NAME {                   \
    static T& getSingletonInstance() {                            \
      instance_##CLASS_NAME = VALUE;                              \
      return instance_##CLASS_NAME;                               \
    }                                                             \
  };

获取单例类的宏

// 获取单例实例的宏
#define GET_SINGLETON_INSTANCE(T) \
  SINGLETON_CLASS_FACTORY_##T::getSingletonInstance()

业务代码如下,最后输出为tsk1和tsk2,说明二者共用一个实例

auto& handler = GET_SINGLETON_INSTANCE(Handler);
  handler.setTask("tsk1");
  handler.handler();
  auto& handler1 = GET_SINGLETON_INSTANCE(Handler);
  handler1.setTask("tsk2");
  handler.handler();

如果需要给单例类handler的tsk字段注入初始值该怎么办呢?注册一个std::string类型,并携带初始值

// 注册单例类并设置初始值
using std::string;
REGISTER_CLASS_SINGLETON_WITH_VALUE(string, task, "default task")

在handler类定义中注入

// 测试类
class Handler {
 public:
  Handler() {}
  Handler(const std::string& task) : task(task) {}
  void handler() { std::cout << "Handler " << task << std::endl; }
  void setTask(const std::string& task) { this->task = task; }

 private:
  std::string task = GET_SINGLETON_INSTANCE(task);
};

输出Handler default task

完整参考代码

#include <iostream>
#include <string>

// 将类注册到工厂的宏
#define REGISTER_CLASS(T)                     \
  struct CLASS_FACTORY_##T {                  \
    static T createInstance() { return T(); } \
  };

// 创建对象实例的宏
#define CREATE_INSTANCE(T) CLASS_FACTORY_##T::createInstance()

// 注册类型到工厂的宏
REGISTER_CLASS(int)

// 单例模式的实现
#define REGISTER_CLASS_SINGLETON(T, CLASS_NAME)                        \
  static T instance_##CLASS_NAME;                                      \
  struct SINGLETON_CLASS_FACTORY_##T {                                 \
    static T& getSingletonInstance() { return instance_##CLASS_NAME; } \
  };

// 注册单例类并设置初始值
#define REGISTER_CLASS_SINGLETON_WITH_VALUE(T, CLASS_NAME, VALUE) \
  static T instance_##CLASS_NAME;                                 \
  struct SINGLETON_CLASS_FACTORY_##CLASS_NAME {                   \
    static T& getSingletonInstance() {                            \
      instance_##CLASS_NAME = VALUE;                              \
      return instance_##CLASS_NAME;                               \
    }                                                             \
  };

// 获取单例实例的宏
#define GET_SINGLETON_INSTANCE(T) \
  SINGLETON_CLASS_FACTORY_##T::getSingletonInstance()

// 注册单例类
REGISTER_CLASS_SINGLETON(int, num)

// 注册单例类并设置初始值
using std::string;
REGISTER_CLASS_SINGLETON_WITH_VALUE(string, task, "default task")

// 测试类
class Handler {
 public:
  Handler() {}
  Handler(const std::string& task) : task(task) {}
  void handler() { std::cout << "Handler " << task << std::endl; }
  void setTask(const std::string& task) { this->task = task; }

 private:
  std::string task = GET_SINGLETON_INSTANCE(task);
};

REGISTER_CLASS(Handler)
REGISTER_CLASS_SINGLETON(Handler, handler)

int main() {
  auto num = CREATE_INSTANCE(int);
  num = 10;
  std::cout << num << std::endl;
  auto& num1 = GET_SINGLETON_INSTANCE(int);
  num1 = 20;
  std::cout << num1 << std::endl;
  auto& num2 = GET_SINGLETON_INSTANCE(int);
  num2 = 30;
  std::cout << num1 << std::endl;

  auto& handler = GET_SINGLETON_INSTANCE(Handler);
  handler.setTask("tsk1");
  handler.handler();
  auto& handler1 = GET_SINGLETON_INSTANCE(Handler);
  handler1.setTask("tsk2");
  handler.handler();

  auto handler2 = CREATE_INSTANCE(Handler);
  handler2.handler();

  return 0;
}
------本页内容已结束,喜欢请分享------

文章作者
能不能吃完饭再说
隐私政策
PrivacyPolicy
用户协议
UseGenerator
许可协议
NC-SA 4.0


© 版权声明
THE END
喜欢就支持一下吧
点赞21赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片