您的位置:首页 > 房产 > 家装 > 东莞广告公司电话_贵州省疫情最新情况_软件外包公司有哪些_深圳网络推广外包公司

东莞广告公司电话_贵州省疫情最新情况_软件外包公司有哪些_深圳网络推广外包公司

2024/10/5 18:26:44 来源:https://blog.csdn.net/Wzs040810/article/details/141256751  浏览:    关键词:东莞广告公司电话_贵州省疫情最新情况_软件外包公司有哪些_深圳网络推广外包公司
东莞广告公司电话_贵州省疫情最新情况_软件外包公司有哪些_深圳网络推广外包公司

日志系统第二弹:设计模式介绍

  • 一、六大原则
    • 1、单一职责原则
    • 2.开闭原则
    • 3.里氏替换原则
    • 4.依赖倒置原则
    • 5.接口隔离原则
    • 6.迪米特法则(最少知道原则)
  • 二、工厂模式
    • 1.介绍
    • 2.为何要有工厂模式
    • 3.简单工厂模式
      • 1.代码
      • 2.优缺点
    • 4.工厂设计模式
      • 1.介绍
      • 2.代码
      • 3.优缺点
    • 5.抽象工厂模式
      • 1.介绍
      • 2.代码
      • 3.优缺点
  • 三、建造者模式
    • 1.介绍
    • 2.为何要有建造者模式
    • 3.代码
  • 四、代理模式
    • 1.介绍
    • 2.代码
  • 五、C不定参宏函数的介绍
  • 六、C不定参函数的介绍
    • 1.C可变参数列表的介绍
    • 2.vsprintf系列函数的介绍
    • 3.使用可变参数列表
    • 4.实现myprintf
  • 七、C++不定参函数的介绍
  • 八、项目工具类
    • 1.获取系统当前时间
    • 2.判断文件是否存在
    • 3.获取文件父级路径
    • 4.创建目录

设计模式就是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结,是很多大佬在编程过程中总结下来的经验,类似于“武林秘籍”

我们的项目当中需要用到几个常见的设计模式:

  1. 单例模式
  2. 工厂模式
  3. 建造者模式
  4. 代理模式

其中,单例模式广为人知,我们就不赘述了

我们这篇博客要介绍工厂模式、建造者模式和代理模式

但是,在介绍设计模式之前,我们要先介绍程序设计的六大原则

一、六大原则

1、单一职责原则

定义:一个类应该只负责一项职责

目的:使责任划分清晰明了,便于责任归咎和整改

实现:当发现一个类承担了多项职责时,应该考虑将其分为多个类,每个类只负责一项职责

2.开闭原则

定义:软件实体(类、模块、函数等)对扩展开放,对修改关闭
即:软件实体在扩展新功能时不应该修改原有代码

目的:提高代码的灵活性和可维护性,降低因修改代码而引入的风险

实现:通过多态,实现在不修改原有代码的基础上扩展新的类

3.里氏替换原则

定义:子类对象能够安全地替换父类对象被调用,且不会影响程序的正确性

目的:要求子类继承父类时,遵循父类的规定,提高代码的稳定性和可维护性

4.依赖倒置原则

定义:高层模块不应该依赖底层模块,两者都应该依赖其抽象;抽象不应该依赖具体,而具体应该依赖抽象

实现:每个类都应该派生于抽象类,而不应该派生于具体类

5.接口隔离原则

定义:类之间的依赖的关系应该建立在最少的接口上,不提供没有必要的接口

应用:在定义接口时,应该根据客户端具体需求来定义接口,不提供没有必要的接口

6.迪米特法则(最少知道原则)

定义:一个对象应该对其他对象保持最少程度了解,即尽量减少对象之间的交互和依赖关系

目的:尽量减少对象和对象之间的交互,从而减少类之间的耦合

二、工厂模式

1.介绍

工厂模式是一种创建型设计模式,提供一种创建对象的最佳方式。
在工厂模式当中,我们创建对象时,不会对上层暴露创建逻辑,而是通过一个共同结构来指向新创建的对象,以此实现创建和使用的分离

2.为何要有工厂模式

很多时候,我们要用一个对象,不过我们需要先构造该对象。
这么做没有什么毛病,但是一旦对象修改了导致我们构造的地方也需要变动,那就需要该很多地方,非常不优雅
在这里插入图片描述
因此,大佬们决定在中间加一层,就形成了简单工厂模式:
在这里插入图片描述
这样的话具体细节被工厂类屏蔽了,改动代码时只需要改动工厂类即可

3.简单工厂模式

1.代码

Talk is cheap,Show me the code

class Fruit
{
public:// 父类指针指向new出来的子类对象,父类的析构函数必须要加virtual// 否则delete时就会调用父类的析构函数,极大可能会导致 内存泄漏或其他潜在风险virtual ~Fruit() {}virtual void name() = 0;
};class Apple : public Fruit
{
public:virtual void name(){cout << "我是苹果\n";}
};class Banana : public Fruit
{
public:virtual void name(){cout << "我是香蕉\n";}
};enum FruitType
{APPLE,BANANA
};namespace ns_easy
{class FruitFactory{public:static shared_ptr<Fruit> create(FruitType type){if (type == APPLE)return make_shared<Apple>();else if (type == BANANA)return make_shared<Banana>();elsereturn shared_ptr<Fruit>(); // 空的智能指针}};
}int main()
{auto sp = ns_easy::FruitFactory::create(APPLE);sp->name();sp = ns_easy::FruitFactory::create(BANANA);sp->name();return 0;
}

有了简单工厂模式之后,构造苹果和香蕉就方便多了,但是一旦再次引入新水果(比如橙子),那就需要修改工厂类当中的create函数了

2.优缺点

特点:通过参数控制,便可以生产任何产品
优点:简单粗暴,直观易懂,使用一个工厂便可以生产一类产品
缺点:
1. 违背了开闭原则,想要新增产品就需要修改工厂源代码

因此为了遵循开闭原则,大佬们搞出了工厂设计模式

4.工厂设计模式

1.介绍

既然不能修改原工厂,那么我们就仿照水果和(苹果,香蕉。。)之间的关系,将原工厂改为抽象类,一个类型的水果就对应一个工厂
在这里插入图片描述

2.代码

Talk is cheap,Show me the code

class Fruit
{
public:// 父类指针指向new出来的子类对象,父类的析构函数必须要加virtual// 否则delete时就会调用父类的析构函数,极大可能会导致 内存泄漏或其他潜在风险virtual ~Fruit() {}virtual void name() = 0;
};class Apple : public Fruit
{
public:virtual void name(){cout << "我是苹果\n";}
};class Banana : public Fruit
{
public:virtual void name(){cout << "我是香蕉\n";}
};class Orange : public Fruit
{
public:virtual void name(){cout << "我是橙子\n";}
};namespace ns_method
{class FruitFactory{public:virtual ~FruitFactory(){}virtual shared_ptr<Fruit> create() = 0;};class AppleFactory : public FruitFactory{public:virtual shared_ptr<Fruit> create(){return make_shared<Apple>();}};class BananaFactory : public FruitFactory{public:virtual shared_ptr<Fruit> create(){return make_shared<Banana>();}};class OrangeFactory : public FruitFactory{public:virtual shared_ptr<Fruit> create(){return make_shared<Orange>();}};
}int main()
{std::shared_ptr<ns_method::FruitFactory> factory = std::make_shared<ns_method::AppleFactory>();auto fruit = factory->create();fruit->name();factory = std::make_shared<ns_method::BananaFactory>();fruit = factory->create();fruit->name();factory = std::make_shared<ns_method::OrangeFactory>();fruit = factory->create();fruit->name();return 0;
}

3.优缺点

特点:一个产品,一个工厂
优点:遵循了开闭原则
缺点:当产品特别多的时候,会造成代码臃肿

因此为了应对产品特别多的时候,大佬们发明了抽象工厂模式:

5.抽象工厂模式

1.介绍

依然是一个产品抽象类一个工厂,只不过这种方式更适用于多产品模式
在这里插入图片描述

2.代码

class Animal
{
public:virtual ~Animal() {}virtual void name() = 0;
};class Dog : public Animal
{
public:virtual void name(){cout << "我是狗\n";}
};class Cat : public Animal
{
public:virtual void name(){cout << "我是猫\n";}
};enum AnimalType
{DOG,CAT
};namespace ns_abstract
{class Factory{public:virtual shared_ptr<Fruit> createFruit(FruitType type) = 0;virtual shared_ptr<Animal> createAnimal(AnimalType type) = 0;};class FruitFactory : public Factory{public:virtual shared_ptr<Fruit> createFruit(FruitType type){if (type == APPLE)return make_shared<Apple>();if (type == BANANA)return make_shared<Banana>();if (type == ORANGE)return make_shared<Orange>();return shared_ptr<Fruit>();}virtual shared_ptr<Animal> createAnimal(AnimalType type){return shared_ptr<Animal>();}};class AnimalFactory : public Factory{public:virtual shared_ptr<Fruit> createFruit(FruitType type){return shared_ptr<Fruit>();}virtual shared_ptr<Animal> createAnimal(AnimalType type){if (type == DOG)return make_shared<Dog>();if (type == CAT)return make_shared<Cat>();return shared_ptr<Animal>();}};
}int main()
{std::shared_ptr<ns_abstract::Factory> factory = make_shared<ns_abstract::FruitFactory>();auto fruit = factory->createFruit(APPLE);fruit->name();fruit = factory->createFruit(BANANA);fruit->name();fruit = factory->createFruit(ORANGE);fruit->name();factory = make_shared<ns_abstract::AnimalFactory>();auto animal = factory->createAnimal(DOG);animal->name();animal = factory->createAnimal(CAT);animal->name();return 0;
}

3.优缺点

优点:适用于多产品,代码冗余度相对较小
缺点:违背开闭原则,增加新的产品类需要修改代码

三、建造者模式

1.介绍

建造者模式是一种创建型设计模式,主要用于解决对象的构建过于复杂的问题
使用一个个简单的对象一步一步构建成一个复杂的对象

2.为何要有建造者模式

在项目开发当中,有些类是由一堆零部件组装而成的,因此构造对象时过于复杂,不够优雅
比如:电脑的组装:
在这里插入图片描述
建筑者分为:装Windows电脑的,装Linux,装Mac OS电脑的人
他们都继承于同一个抽象建筑者类

然后搞一个指示者,负责按照正确的顺序调用建筑者的相应接口来完成特定任务
最后电脑就装好了
(如果零部件之间没有特定顺序要求的话,那么实际上是不需要这个指示者的,直接让建筑者提供一个总的build接口即可)

因此建造者模式需要:
1. 抽象产品类
2. 具体产品类
3. 抽象Builder类:创建所有零部件的抽象接口
4. 具体Builder类:实现抽象接口,构建对应负责的零部件
5. 指挥者Director类:统一组件过程,通过指挥者来构造产品

3.代码

// 每个电脑都必须要有一个OS,因此我们把OS定为纯虚函数,强制子类重写
class Computer
{
public:virtual ~Computer() {}void setBoard(const string &board){_board = board;}void setDisplay(const string &display){_display = display;}virtual void setOS() = 0;void show(){cout << "board:\t" << _board << "\n";cout << "display:\t" << _display << "\n";cout << "os:\t" << _os << "\n";}protected:string _board;string _display;string _os;
};class Winbook : public Computer
{
public:virtual void setOS(){_os = "Win 11";}
};class Builder
{
public:virtual ~Builder() {}virtual void setBoard(const string &board) = 0;virtual void setDisplay(const string &display) = 0;virtual void setOS() = 0;virtual shared_ptr<Computer> get() = 0;
};class WinbookBuilder : public Builder
{
public:WinbookBuilder() : _book(std::make_shared<Winbook>()) {}virtual void setBoard(const string &board){_book->setBoard(board);}virtual void setDisplay(const string &display){_book->setDisplay(display);}virtual void setOS(){_book->setOS();}virtual shared_ptr<Computer> get(){return _book;}private:shared_ptr<Winbook> _book;
};class Director
{
public:Director(const shared_ptr<Builder> &builder): _builder(builder) {}void build(const string &board, const string &display){_builder->setBoard(board);_builder->setDisplay(display);_builder->setOS();}shared_ptr<Builder> get(){return _builder;}private:shared_ptr<Builder> _builder;
};int main()
{Director director(make_shared<WinbookBuilder>());shared_ptr<Builder> builder = director.get();director.build("XXX主板", "XXX显示器");auto sp = builder->get();sp->show();return 0;
}

在这里插入图片描述

四、代理模式

1.介绍

代理模式是一种结构型设计模式,它为其他对象提供一种代理,以控制对该对象的访问

代理模式的主要目的是在客户端和目标对象之间增加一个中间层,起到增加功能、控制访问、减少系统间的耦合度的作用

代理模式的结构包括:一个目标对象和一个代理对象,目标对象和代理对象实现同一个接口
先访问代理对象,在通过代理对象来访问目标对象

代理模式分为静态代理和动态代理:
静态代理:在编译时就已经确定了代理类和被代理类之间的关系
动态代理:在运行时才能确定代理类和被代理类之间的关系

2.代码

以租房为例,房东在租房的时候需要发布招租启示,带人看房,负责维修等等,而房东为了图方便,可以把自己的房子委托给中介进行租赁

class RentHouse
{
public:virtual void rent()=0;
};class Landlord:public RentHouse
{
public:virtual void rent(){cout<<"把房子租出去\n";}
};class Intermediary:public RentHouse
{
public:virtual void rent(){cout<<"发布招租启示\n";cout<<"带人开房\n";_landlord.rent();cout<<"负责维修\n";}
private:Landlord _landlord;
};int main()
{Intermediary intermediary;intermediary.rent();return 0;
}

五、C不定参宏函数的介绍

我们的项目不仅仅会用到那四个设计模式,还会用到C和C++的不定参函数与C的不定参宏函数

因此,我们把他们介绍一下:
在学习C的时候,我们见过printf的函数原型:int printf(const char *format, ...);

...是可变参数列表,支持传入任意多个参数,首先我们先介绍它在C的宏函数当中的使用

我们就实现一个printf的宏函数吧

__VA_ARGS__是一个宏,用来展开...当中的所有参数

因此,我们就可以实现一个printf

#define PRINT(format,...) printf(format,__VA_ARGS__)
int main()
{PRINT("hello %s - %d\n","world",1);return 0;
}
wzs@iZ2ze5xfmy1filkylv86zbZ:~/blog/practice$ ./macro_args 
hello world - 1

打印日志时,我们通常把打印该日志的地方标记到日志当中,通常是以文件名:行号的方式来进行记录

需要用到__FILE__和__LINE__这两个宏,而C当中字符串是可以默认拼接的:

#define PRINT(format, ...) printf("%s:%d " format "\n", __FILE__, __LINE__, __VA_ARGS__)int main()
{PRINT("hello %s - %d", "world", 1);return 0;
}
wzs@iZ2ze5xfmy1filkylv86zbZ:~/blog/practice$ ./macro_args 
macro_args.c:7 hello world - 1

如果用户调用时,没有给…传入任何参数,那么宏替换之后,就会多出一个逗号,函数调用就会有问题
在这里插入图片描述
我们来看一下,宏替换之后会变成什么样子

gcc -E  macro_args.c > macro_args.i

在这里插入图片描述
针对这种情况,编译器提供了##__VA_ARGS__,这个##的作用就是当…为空时,他会删除前面的那个逗号
因此,正确的版本是这样的:

#define PRINT(format, ...) printf("%s:%d " format "\n", __FILE__, __LINE__, ##__VA_ARGS__)

然后我们重新gcc -E一下
在这里插入图片描述

六、C不定参函数的介绍

C的不定参函数的函数签名是这样的:

至少需要一个固定参数,最后用...来接受任意数量的参数
函数内部需要根据固定参数来解析可变参数列表当中的所有参数例如:
void func(int num,...);
void printf(const char* format,...);

在这里插入图片描述

1.C可变参数列表的介绍

在<stdarg.h>当中,定义了几个宏用来解决该问题

va_list:这是一个类型,用于声明一个变量,该变量将用于访问可变参数列表va_start(ap, last):宏,用于初始化 va_list 类型的变量 ap,以访问 last 参数之后的参数。
last 是可变参数列表之前的最后一个固定参数。va_arg(ap, type):宏,用于返回参数列表中下一个参数的值,并将其类型转换为 type。
每次调用 va_arg 都会使 ap 指向下一个参数。va_end(ap):宏,用于清理 va_list 变量 ap。
这是使用可变参数列表后应该进行的清理工作。

2.vsprintf系列函数的介绍

int vsprintf(char *str, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
int vasprintf(char **strp, const char *fmt, va_list ap);根据format格式化字符串和ap可变参数列表,将对应参数提取出来存放到str当中

vsprintf和vsnprintf的区别是:

vsnprint允许指定最大字符数目,以防止缓冲区溢出
而vsprintf则没有这样的限制,容易发生安全问题

vasprintf是允许我们传入一级指针的地址,他在函数内部会给我们malloc一段空间,将数据放到其中

需要我们自行free掉这个strp,是这三个当中最好用的

若失败,则返回-1

3.使用可变参数列表

先给大家演示一下可变参数列表的使用:

void func(int num, ...)
{va_list ap;va_start(ap, num);for (int i = 0; i < num; i++){// 指针偏移int val = va_arg(ap, int);printf("%d ",val);}printf("\n");va_end(ap);
}int main()
{func(3,1,2,3);func(0);func(1,1);return 0;
}wzs@iZ2ze5xfmy1filkylv86zbZ:~/blog/practice$ ./macro_args 
1 2 3 1 

4.实现myprintf

想要使用vasprintf函数,需要 #define _GNU_SOURCE

void myprintf(const char *format, ...)
{va_list ap;va_start(ap, format);char* buf=NULL;int ret=vasprintf(&buf,format,ap);if(ret!=-1){printf("%s\n",buf);free(buf);}va_end(ap);
}int main()
{myprintf("hello %s - %d","world",1);myprintf("hello %s","world");myprintf("hello");return 0;
}
wzs@iZ2ze5xfmy1filkylv86zbZ:~/blog/practice$ ./macro_args 
hello world - 1
hello world
hello

七、C++不定参函数的介绍

C++不定参函数是通过可变模板参数包来实现的,是通过递归的方法来进行解包的

注意:因为可变模板参数包需要在编译时完成递归,而if语句在编译时,两条路都要走
因此如果args模板包为空,递归调用自己的路还是要编译的
而myprintf不支持传入0个参数,因此我们需要给myprintf加一个接受0个参数的函数重载,来解决编译时报错的问题

void myprintf()
{cout << "\n";
}template <class T, class... Args>
void myprintf(const T &val, Args &&...args)
{// 先打印valcout << val;// 如果args模板包当中参数个数大于0,则递归分用if ((sizeof...(args)) > 0){myprintf(forward<Args>(args)...);}else{myprintf();}
}int main()
{myprintf();myprintf("hello");myprintf("hello ", "world");myprintf("hello ", "world-", 11);return 0;
}

否则就会:

cpp_args.cc: In function ‘void myprintf(const T&, Args&& ...):
cpp_args.cc:21:18: error: no matching function for call to ‘myprintf()21 |         myprintf();|                  ^
cpp_args.cc:10:6: note: candidate:template<class T, class ... Args> void myprintf(const T&, Args&& ...)10 | void myprintf(const T &val, Args &&...args)|      ^~~~~~~~
cpp_args.cc:10:6: note:   template argument deduction/substitution failed:
cpp_args.cc:21:18: note:   candidate expects at least 1 argument, 0 provided21 |         myprintf();

candidate expects at least 1 argument, 0 provided:
我期待至少一个参数,结果你给我0个…

八、项目工具类

我们的项目还需要几个小工具类:

1.获取系统当前时间

class DateHelper
{
public:static time_t now(){return time(nullptr);}
};

2.判断文件是否存在

class FileHelper{public:static bool exists(const std::string &pathname){struct stat st;// stat遵循POSIX标准(Portable Operating System Interface for UNIX 可移植操作系统接口)// 具有良好的跨平台移植性return stat(pathname.c_str(), &st) >= 0; // 第二个参数不能给nullptr,因为它是一个输出型参数}

3.获取文件父级路径

find_last_of是从后往前找第一个符合条件的字符,只要该字符位于传入的字符集合即可

static std::string getPath(const std::string &pathname)
{// ./a/b/c/d// 从后往前找第一个'/' 然后返回./a/b/c// 如果没有,则返回.// find_last_ofsize_t pos = pathname.find_last_of(R"(/\)"); // 支持(/)Linux、Mac OS和Win(\)if (pos == std::string::npos){return ".";}return pathname.substr(0, pos);
}

因为\是转义字符,想要表示单纯的\的话,需要二次转义\\
不够优雅,所以我们用了一下C++11的 字符串字面量R"(/\)"

4.创建目录

./a/b/c
从前往后依次创建 ./a ./a/b ./a/b/c即可,就是简单的string的find和substr还有mkdir的使用而已

static bool createDir(const std::string &pathname)
{// ./a/b/c// 从前往后依次创建 ./a  ./a/b  ./a/b/csize_t pos = pathname.find_first_of(R"(/\)");while (pos != std::string::npos){umask(0);std::string substr = pathname.substr(0, pos);int ret = mkdir(substr.c_str(), 0775);if (ret == -1 && errno != EEXIST){std::cout << "目录创建失,总目录:" << pathname << ",当前目录:" << substr << "\n";return false;}pos = pathname.find_first_of(R"(/\)", pos + 1);}// 创建整个目录return (mkdir(pathname.c_str(), 0775) != -1 || errno == EEXIST);
}

以上就是日志系统第二弹:设计模式介绍,C和C++不定参函数的介绍的全部内容

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com