alin的学习之路:工厂模式、base64

alin的学习之路:工厂模式、base64

1. 工厂模式

# 工厂模式的作用: 委托工厂类帮助程序猿创建相关的对象
	比如: 某个类的构造函数很复杂, 需要很多的参数, 假设这个类是一个数据库类: MySql, oracle
		- 构造对象需要准备的数据: 服务器IP, 端口, 用户名, 密码, 数据库名
		- 构造这些数据库对象比较麻烦, 而且容易出错, 我们可以把这些操作委托给某个类去完成即可 -> 工厂类
		- 提供效率, 降低程序出错的概率
  • 设计模式的特点和作用?
    • 让编写的程序更加容易扩展和维护
    • 设计模式的原则: 开放, 封闭
      • 开放: 在开发新的功能的时候, 在项目中添加新的代码, 这样的话对现有的旧代码影响性是很小的
      • 封闭: 开发新是功能的时候, 不允许在原有的基础上进行修改
        • 这样代码出现bug的概率会大幅度提升
  • 工厂模式是设计模式的一种, 工厂模式的作用?
    • 帮助程序猿创建相关的对象
      • 因此某种对象的创建过程可能是非常复杂的, 在实际操作过程中容易出错, 为了降低出错的概率, 可以将这个对象的创建过程封装起来, 这个类就称之为工厂类, 这种设计思路就称之为工厂模式
  • 工厂模式的特点:
    • 封装了对象的创建过程, 可以轻而易举的拿到一个可用对象, 开发难度和出差的概率都降低了
    • 解耦合 -> 耦合度低
      • 耦合度: 关联程度
    • 降低了程序的冗余, 增加了程序的可读性
    • 通过一个工厂类的工厂函数可以创建出多个不同的对象
      • 这些对象所对应的类的基类是同一个
      • 兄弟关系
  • 工厂模式的分类:
    • 简单工厂模式
    • 工厂模式
    • 抽象工厂模式 -> 使用的不多

1.2. 简单工厂模式

简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。
特点:

  • 缺点:每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个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();

1.3. 工厂模式

  • 工厂模式: 遵循 开放-封闭原则
    • 每创建一个对象, 就应该对应一个工厂类, 工厂类需要有很多个
    • 这些创建对象的工厂类之间是什么关系?
      • 兄弟关系
    • 这些创建对象的工厂类需要有相同的基类
      • 目的: 为了实现多态, 让代码的调用更加灵活
# 工厂模式的类的使用步骤: (也需要使用到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();

1.4 工厂模式总结

  • 工厂模式就是为了开放和封闭原则,提高代码的扩展性,降低耦合性,工厂类用于创建对象,使用多态。

  • 简单工厂模式:只有一个工厂类,通过判断传入的值来创建对应的对象,如果需要加类的话需要增加 switch case 后的判断。

  • 工厂模式:工厂类有很多,有一个基类和很多派生的类。

2. base64

平时调用一些函数, 会得到二进制的数据, 没有直接将其处理掉, 而且进行了中转, 有可能出现数据的混乱

  • 数据的中转
    • 数据从一个对象到另外一个对象
      • 比如: 数据的类型转换
    • 数据从一个进程传递给另外一个进程
    • 进程间通信(同一主机)
    • 数据从一台主机发送到另一台主机
      • 网络通信
  • 二进制数据在数据中转过程中如果不处理有可能出现数据的混乱
    • 二进制数据中有很多特殊字符, 这些特殊字符有可能会对某些函数的处理动作产生影响
      • 比如: 字符串处理函数, 这些函数一般认为遇到\0该字符串就结束了 -> 文本字符串都这样
        • 二进制字符串有可能在中间位置出现\0
    • 二进制字符串中有一些特殊的不可见字符
      • 自己查看一下ascii表, 空格以前的字符都是不可见的
  • 结论: 二进制数据在中转过程中, 需要将二进制数据变成纯文本字符串, 数据到达目的地之后再将其转换为原始数据就可以了
  • 转换方式:
    • base64
    • 转为16进制数字串
      • 哈希值都会转换为这种形式

2.1 概念

Base64是一种基于64个可打印字符表示二进制数据的表示方法。在Base64中的可打印字符包括字母A-Za-z、数字0-9,这样共有62个字符,加上+/, 共64个字符.

  • 使用base64描述非二进制数据也可以
  • base64这种算法是可逆的
    • 可以编码
    • 可以解码

alin的学习之路:工厂模式、base64_第1张图片

2.2 应用场景

在计算机中任何数据都是按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字节, =表示的是尾部填充的字节数
    */
    

    alin的学习之路:工厂模式、base64_第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!"
    

3. 类型转换

3.1 QByteArray类和char*

// 构造函数 , char* -> QByteArray
QByteArray::QByteArray(const char *data, int size = -1);
参数: size是字符串data的长度
    
// QByteArray -> char*
char *QByteArray::data();
const char *QByteArray::data() const;

3.2 QString和QByteArray

/*
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中的字符串是 本地 编码 (跟随系统)

3.3 QString 和 std::string

// QString -> string
std::string QString::toStdString() const;

// string -> QString
[static] QString QString::fromStdString(const std::string &str);

3.4 std::string 和 char*

// 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);

3.4 std::string 和 char*

// 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);

你可能感兴趣的:(工厂模式,base64,base64,设计模式)