# 工厂模式的作用: 委托工厂类帮助程序猿创建相关的对象
比如: 某个类的构造函数很复杂, 需要很多的参数, 假设这个类是一个数据库类: MySql, oracle
- 构造对象需要准备的数据: 服务器IP, 端口, 用户名, 密码, 数据库名
- 构造这些数据库对象比较麻烦, 而且容易出错, 我们可以把这些操作委托给某个类去完成即可 -> 工厂类
- 提供效率, 降低程序出错的概率
开放, 封闭
耦合度低
简单工厂模式
工厂模式
简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。
特点:
- 缺点:每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了“不改代码”的原则)
- 优点:去除了与具体产品的依赖, 实现简单。
# 简单工厂类的创建和使用步骤:
1. 创建一个新的类, 可以将这个类称之为工厂类(简单工厂模式, 需要的工厂类只有一个)
2. 在这个类中添加一个公共的成员函数, 通过这个函数来创建我们需要的对象(这个函数一般称之为工厂函数)
- 函数名叫什么无所谓
- 这是工厂类中的核心函数
3. 使用: 创建一个工厂类对象, 通过对象调用工厂函数, 就可以生产出一个指定的可用实例对象
// 场景: 有两个子类: RequestCodec 和 RespondCodec 他们的基类是 Codec
// 这个两个类是用于对数据进行序列化的, 这两个类处理的数据不一样
// 关于需要被创建对象的类
// 基类
class Codec
{
public:
Codec();
// 编码, 序列化
virtual std::string encodeMsg(){}
// 解码, 反序列化
virtual void* decodeMsg(){}
virtual ~Codec(){}
};
// 子类
class RequestCodec : public Codec
{
public:
string encodeMsg(){xxx}
void* decodeMsg(){xxx}
}
class RespondCodec : public Codec
{
public:
string encodeMsg(){yyy}
void* decodeMsg(){yyy}
}
///
class TestCodec : public Codec
{
public:
string encodeMsg(){xxee}
void* decodeMsg(){8909}
}
// 新添加一子类
class MyCodec : public Codec
{
public:
string encodeMsg(){2223}
void* decodeMsg(){44455}
}
简单工厂类实现:
// 编写一个工厂类, 用于创建上边的对数据进行编解码的对象
class Factory
{
public:
Factory();
~Factory();
// 工厂函数
// 需要通过这个工厂函数, 生产出:RequestCodec, RespondCodec
// 根据实际需求生产出不同的对象, 因此需要给工厂函数传递参数
// flag=1 -> RequestCodec
// flag=2 -> RespondCodec
// flag=3 -> TestCodec
// flag=4 -> MyCodec
Codec* createCodec(int flag)
{
Codec* c = NULL;
if(flag == 1)
{
c = new RequestCodec;
}
else if(flag == 2)
{
c = new RespondCodec;
}
else if(flag == 3)
{
c = new TestCodec;
}
else if(flag == 4)
{
c = new MyCodec;
}
return c;
}
};
/ 修改之后 //
class Factory
{
public:
Factory();
~Factory();
// 工厂函数
// 需要通过这个工厂函数, 生产出:RequestCodec, RespondCodec
// 根据实际需求生产出不同的对象, 因此需要给工厂函数传递参数
enum Type{RequestCodec, RespondCodec, TestCodec, MyCodec};
Codec* createCodec(Type flag)
{
Codec* c = NULL;
switch(flag)
{
case RequestCodec:
c = new RequestCodec;
break;
case RespondCodec:
c = new RespondCodec;
break;
case TestCodec:
c = new TestCodec;
break;
case MyCodec:
c = new MyCodec;
break;
}
return c;
}
};
简单工厂类的使用:
// 1. 创建一个工厂类对象
Factory* f = new Factory;
// 2. 调用工厂函数, 创建编解码对象
Codec* c = f->createCodec(Type::RequestCodec);
// 3. 数据编码
string msg = c->encodeMsg();
- 工厂模式: 遵循 开放-封闭原则
- 每创建一个对象, 就应该对应一个工厂类, 工厂类需要有很多个
- 这些创建对象的工厂类之间是什么关系?
- 兄弟关系
- 这些创建对象的工厂类需要有相同的基类
- 目的: 为了实现多态, 让代码的调用更加灵活
# 工厂模式的类的使用步骤: (也需要使用到c++中多态)
1. 创建一个工厂类, 角色: 其他工厂类的基类, 在这个类中添加一个虚函数, 这个虚函数就是工厂函数
- 为实现多态做准备
2. 基于上边的基类创建若干个子工厂类, 在子工厂类中重写父类的工厂函数
- 这个工厂类的子类功能是非常单一的, 这个类的工厂函数只生产某种特定类型的对象
- 类内部逻辑是相对简单的, 是不需要做判断的
3. 如果有新的对象需要被工厂类创建, 这时候必须要添加新的工厂类的子类, 使用这个子创建类来创建这个对象
- 对现有的代码不会做任何修改 -> 封闭
- 添加了新的类 -> 开放
4. 使用: 创建工厂类对象, 使用工厂类对象调用工厂函数, 这样就得到了需要的实例对象
工厂类的实现:
// 工厂类的基类
class BaseFactory
{
public:
BaseFactory();
// 如果这个类是基类, 并且要实现多态, 这个析构函数必须要写成虚函数
// 如果没有实现多态, 没有必须写成虚函数
virtual ~BaseFactory(){}
// 添加一个工厂函数
virtual Codec* createCodec()
{
}
};
// 工厂类的子类 --> 用于生产实际的某个对象
class RequestFactory : public BaseFactory
{
public:
RequestFactory();
~RequestFactory();
// 重写父类的工厂函数
Codec* createCodec()
{
return new RequestCodec;
}
};
class RespondFactory : public BaseFactory
{
public:
RespondFactory();
~RespondFactory();
// 重写父类的工厂函数
Codec* createCodec()
{
return new RespondCodec;
}
};
工厂类的使用
// 1. 创建工厂类对象
BaseFactory* f = new RequestFactory; // 多态
// 2. 调用工厂函数, 创建对应的编解码对象
Codec* c = f->createCodec(); // 多态
// 3. 编码
string str = c->encodeMsg();
工厂模式就是为了开放和封闭原则,提高代码的扩展性,降低耦合性,工厂类用于创建对象,使用多态。
简单工厂模式:只有一个工厂类,通过判断传入的值来创建对应的对象,如果需要加类的话需要增加 switch case 后的判断。
工厂模式:工厂类有很多,有一个基类和很多派生的类。
平时调用一些函数, 会得到二进制的数据, 没有直接将其处理掉, 而且进行了中转, 有可能出现数据的混乱
- 数据的中转
- 数据从一个对象到另外一个对象
- 比如: 数据的类型转换
- 数据从一个进程传递给另外一个进程
- 进程间通信(同一主机)
- 数据从一台主机发送到另一台主机
- 网络通信
- 二进制数据在数据中转过程中如果不处理
有可能
出现数据的混乱
- 二进制数据中有很多特殊字符, 这些特殊字符有可能会对某些函数的处理动作产生影响
- 比如: 字符串处理函数, 这些函数一般认为遇到
\0
该字符串就结束了 ->文本字符串都这样
- 二进制字符串有可能在中间位置出现
\0
- 二进制字符串中有一些特殊的不可见字符
- 自己查看一下
ascii
表, 空格以前的字符都是不可见的- 结论:
二进制数据在中转过程中, 需要将二进制数据变成纯文本字符串, 数据到达目的地之后再将其转换为原始数据就可以了
- 转换方式:
base64
- 转为16进制数字串
哈希值都会转换为这种形式
Base64
是一种基于64个可打印字符来表示二进制数据的表示方法。在Base64中的可打印字符包括字母A-Z
、a-z
、数字0-9
,这样共有62个字符,加上+和/, 共64个字符.
- 使用base64描述非二进制数据也可以
- base64这种算法是可逆的
- 可以编码
- 可以解码
在计算机中任何数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。 而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,
由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的
。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。
base64 应用
- http 通信的时候, 请求行中不允许出现特殊字符串
- http请求协议请求行的第二部分不允许出现特殊字符, 如果客户端是浏览器, 浏览器会自动的进行转码( Unicode 的方式)
- 如果客户端不是浏览器, 字符串的转换就得自己实现, 可以使用
base64
这种方式对特殊字符进行编码- 邮件收发, 如果携带了附件, 附件大部分都是二进制文件
- 附件: 音频, 视频, 特殊的二进制文件
- 发送附件之前, 进行base64编码, 接收附件之后, 进行base64解码
- 数据库的二进制数据存储
- 给数据库表中的字段指定类型的时候, 数据库中有一些类型是专门处理二进制数据的
- 如果这个字段类型处理是文本类型的字符串, 建议存储二进制数据的时候对其进行编码
- 数据库中有中文时候
- 中文乱码的原因:
- 使用的客户端接口, 接口中使用的默认编码格式不支持中文, 或者和数据库的编码格式不一致
- 数据库默认就不支持中文
- 如果不指定怎么解决, 可以对存储的数据进行编码
base64 算法 (了解)
- 把3个8位字节(3*8=24)转化为4个6位的字节(4*6=24)
- 对原始数据进行分组, 每3字节为一组, 将这个3个字节变成4字节
- 1个字节(byte) == 8位(bit)
- 3个字节 == 24bit
- 多出的1字节需要填充, 但是不是直接填充的
- 将24bit平均分成4分, 每一份有6个标志位
- 每一份要凑一个完整的字节缺
2bit
- 在头部进行填充, 填充2bit, 每个bit位的值为0 ==> 得到了一个完整的字节
- 在分组的过程中, 最后一组有可能不够三个字节, 需要填充, 填充的字节值为0, 为了能在解码的时候将填充的字节0去掉, 需要将其标记出来, 我们使用=来表示填充的字节0
- 填充1个字节
- 在base64编码的字符串尾部就有1个=
- 填充2个字节
- 在base64编码的字符串尾部就有2个=
MIGJAoGBAL3XBoEsDbO/FrwjkjyZYvGQhkc6GjcKE1BqmOEMSCq+lvsF4MGUt5i+
YDkbV8VGKoVE1KRdVYm88Xl7ilZcvD87DoKJPWnvegI8Ea9dTdgldgzCjFZtlc5q
M07mFQUpwN32g3yk/WdeH6dXFIQ7mBARpuiBQK53qs1TXlT1l5jdAgMG+FM=
/*
1. base64编码之后: 数据变长了, 每3个字节会多出一个字节
2. base64中是不包括=号, 为什么有呢?
- 这是最后一个分组的填充, 1组3字节
- 如果不够要么填充1字节, 要么填充2字节, =表示的是尾部填充的字节数
*/
在QT中如何进行字符串的base64转换
// 进行base64转换的时候, 需要找开源的库 -- openssl中
// 有些框架自带base64转换函数
// QT中base64转换, 没有专门的类, 因为太简单了
// QByteArray 类中有关于base64转换的函数
// 编码: 字符串 -> base64
QByteArray QByteArray::toBase64() const;
// 举例:
QByteArray text("Qt is great!");
text.toBase64(); // returns "UXQgaXMgZ3JlYXQh"
// 解码: base64字符串 -> 原始数据
[static] QByteArray QByteArray::fromBase64(const QByteArray &base64);
// 举例
QByteArray text = QByteArray::fromBase64("UXQgaXMgZ3JlYXQh");
text.data(); // returns "Qt is great!"
// 构造函数 , char* -> QByteArray
QByteArray::QByteArray(const char *data, int size = -1);
参数: size是字符串data的长度
// QByteArray -> char*
char *QByteArray::data();
const char *QByteArray::data() const;
/*
QByteArray: 相当于C语言中的字符数组, 但是是一个动态数组, 里边的每一个元素都是由一个 char 类型组成的
- 在这种类型中一个汉字占好几个字节, 对应好几个字符
- 在计算字符串长度的时候, 得到的是字节数
- 多字节字符集
QString: 通过QString构造得到的字符串是 utf8 编码的, 很多字符的集合
- 在这种类型中, 一个汉字算作一个字符, 一个字符占好几个字节
- 在计算字符串长度的时候, 单位是字符个数而不是字节个数
- 使用的是宽字节字符集
*/
// QByteArray -> QString
QString::QString(const QByteArray &ba); // 构造函数
// char* -> QString
QString::QString(const char *str); // 构造函数
// QString -> QByteArray
QByteArray QString::toUtf8() const; // 得到的QByteArray中的字符串是 utf8 编码
QByteArray QString::toLatin1() const; // 得到的QByteArray中的字符串是 西欧 编码 (不支持中文)
QByteArray QString::toLocal8Bit() const; // 得到的QByteArray中的字符串是 本地 编码 (跟随系统)
// QString -> string
std::string QString::toStdString() const;
// string -> QString
[static] QString QString::fromStdString(const std::string &str);
// std::string -> char*
const char* c_str() const;
const char* data() const;
// char* --> std::string
// from c-string
std::string (const char* s);
// from sequence
std::string (const char* s, size_t n);
// fill
std::string (size_t n, char c);
+
// QString -> string
std::string QString::toStdString() const;
// string -> QString
[static] QString QString::fromStdString(const std::string &str);
// std::string -> char*
const char* c_str() const;
const char* data() const;
// char* --> std::string
// from c-string
std::string (const char* s);
// from sequence
std::string (const char* s, size_t n);
// fill
std::string (size_t n, char c);