单例模式就是:一个类只能有一个实例,并提供对该实例的全局访问点。通俗地说,就是一个类只能创建一个对象,并且在程序的任何地方都能够访问到该对象。
在c语言中通俗的话可以这么理解,在一个.c文件中创建一个静态数组或者结构体封装起来,用return或用指针传递的方式返回到需要访问的.c文件中。
结构: 实现:下面我以flash模块为例,代码实现单例模式。
适合应用场景:
如果程序中的某个类对于所有客户端只有一个可用的实例,
可以使用单例模式
如果你需要更加严格地控制全局变量,可以使用单例模式
原因是可以代替全局变量,在极端情况下会存在多个线程对一个数据操作存在数据不一致的情况,也不需要重复的创建和销毁变量,可以提高性能以及可以提高可读性。
以下为我在driver_flash.h中创建单例的结构体,和我需要操作的结构体
#define SWITCHBOT_CONFIG_DATE_MAX_LENGTH 64
typedef struct config_parameter_t
{
uint8_t flash_init[4];
uint8_t electric_alarm_threshold;
uint8_t electric_alarm_enable;
uint8_t burglar_alarm_threshold;
uint8_t burglar_alarm_enable;
uint8_t led_enable;
uint8_t work_mode;
uint8_t ota_update;
uint32_t ota_end;
uint32_t reserve;
uint32_t reserve1;
uint32_t reserve2;//aligmnt
uint32_t reserve3;
uint32_t reserve4;
uint32_t reserve5;
}config_parameter_t;
typedef struct _switchbot_config_t
{
uint8_t config[SWITCHBOT_CONFIG_DATE_MAX_LENGTH];
}switchbot_config_t;
在我的driver_flash.c中实现他的创建他的类,并且把改类封装成接口返回。
static switchbot_config_t* g_pt_config = NULL;
void *driver_config_get_handle(void)
{
return (void*)g_pt_config->config;
}
在系统初始化时候把flash中数据地址读出放入该实例中。
那么,当我在其他任何.c文件需要使用该flash变量存储或者赋值时,只需导入该drivers_flash.h文件即可调用,且可强转成需用户操作的结构体。
static void uart_enable_alarm_switch_sensitivity_command(uint8_t* buffer, uint32_t size)/
{
config_parameter_t* pt_config = (config_parameter_t*)driver_config_get_handle();
pt_config->burglar_alarm_enable = buffer[0];
}
优点:
- 你可以保证一个类只有一个实例。
- 你获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
- 违反了单一职责原则。该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。
- 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式
工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
问题场景:假设你正在开发一款物流管理应用。最初版本只能处理卡车运输,因此大部分代码都在位于名为卡车 的类中。一段时间后,这款应用变得极受欢迎。你每天都能收到十几次来自海运公司的请求,希望应用能够支持海上物流功能如果代码其余部分与现有类已经存在耦合关系,那么向程序中添加新类其实并没有那么容易。这可是个好消息。但是代码问题该如何处理呢?目前,大部分代码都与 卡车 类相关。在程序中添加 轮船 类需要修改全部代码。更糟糕的是,如果你以后需要在程序中支持另外一种运输方式,很可能需要再次对这些代码进行大幅修改。最后,你将不得不编写繁复的代码,根据不同的运输对象类,在应用中进行不同的处理。
代码实现:
typedef struct {
interface_handle_t * (*init)(void);
int32_t (*write)(interface_handle_t *handle, interface_buffer_handle_t *buf_handle);
interface_buffer_handle_t * (*read)(interface_handle_t *handle);
esp_err_t (*reset)(interface_handle_t *handle);
void (*deinit)(interface_handle_t *handle);
} if_ops_t;
typedef struct {
if_ops_t *if_ops;
} interface_context_t;
interface_context_t *if_context = NULL;
if_ops_t if_xr_ops = {
.init = xr_spi_init,
.write = xr_spi_write,
.read = xr_spi_read,
.reset = xr_spi_reset,
.deinit = xr_spi_deinit,
};
if_ops_esp_t if_esp_ops = {
.init = esp_spi_init,
.write = esp_spi_write,
.read = esp_spi_read,
.reset = esp_spi_reset,
.deinit = esp_spi_deinit,
};
interface_handle_t * xr_spi_init(void)
{
}
int32_t * xr_spi_write(void)
{
}
interface_buffer_handle_t * xr_spi_read(void)
{
}
esp_err_t * xr_spi_reset(void)
{
}
void * xr_spi_deinit(void)
{
}
interface_handle_t * esp_spi_init(void)
{
}
int32_t * esp_spi_write(void)
{
}
interface_buffer_handle_t * esp_spi_read(void)
{
}
esp_err_t * esp_spi_reset(void)
{
}
void * esp_spi_deinit(void)
{
}
interface_context_t *interface_insert_driver(uint8_t val)
{
printf("Using SPI interface");
if(val == esp_spi)
{
memset(&if_esp_context,0,sizeof(if_esp_context));
if_context.if_ops->init = esp_spi_init;
if_context.if_ops->write = esp_spi_write;
if_context.if_ops->read = esp_spi_read;
if_context.if_ops->reset = esp_spi_reset;
if_context.if_ops->deinit = esp_spi_deinit;
}
else if(val == xr_spi)
{
memset(&if_xr_context,0,sizeof(if_xr_context));
if_context.if_ops->init = xr_spi_init;
if_context.if_ops->write = xr_spi_write;
if_context.if_ops->read = xr_spi_read;
if_context.if_ops->reset = xr_spi_reset;
if_context.if_ops->deinit = xr_spi_deinit;
}
return &if_context;
}
int main()
{
uint8_t spi_mode;
interface_insert_driver(spi_mode);
}
适合应用场景:
1.需要创建的对象比较少。
在上面的例子中,我们只是创建了两个鼠标实现类,如果需要几十个上百个具体的实现类的情况,那么在工厂类的switch或者if else语句中就要写很多行代码,很笨重。
2.客户端不关心对象的创建过程
该特点同样适用与其他工厂模式。上面的例子中,就不需要了解DellMouse或者HpMouse实例的创建过程,直接在需要的地方传入不同参数调用就好了
- 可以对创建的对象进行“加工”,对客户端隐藏相关细节。只关注结果,无需了解过程
- 你可以避免创建者和具体产品之间的紧密耦合。
- 开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型
- 单一职责原则。你可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护
- 若创建逻辑复杂或创建对象过多,则会造成代码臃肿
- 新增、删除子类均会违反开闭原则
- 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中
关于单例模式和简单工厂模式已经写完了,写的可能有一些bug,有错误请各位及时提出,谢谢大家



