wenmo8 发布的文章

建造者模式(Builder Pattern),旨在将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。



1

模式结构


UML 结构图:


图片


  • Builder(抽象建造者):为创建一个产品对象的各个部件指定抽象接口。

  • ConcreteBuilder(具体建造者):实现 Builder 的接口以构造和装配该产品的各个部件,定义并明确它所创建的表示,并提供一个检索产品的接口。

  • Director(指挥者):构造一个使用 Builder 接口的对象。

  • Product(产品):表示被构造的复杂对象。ConcreteBuilder 创建该产品的内部表示并定义它的装配过程,包含定义组成部件的类,包括将这些部件装配成最终产品的接口。



2

优缺点



优点:


  • 建造者独立,易于扩展

  • 便于控制细节风险



缺点:


  • 产品必须有共同点,范围有限制

  • 如果内部变化复杂,将会有很多的建造类



3

适用场景


  • 需要生成的对象具有复杂的内部结构

  • 需要生成的对象内部属性本身相互依赖



4

案例分析




Lenovo For Those Who Do - 联想



图片

联想旗下有众多系列的电脑,例如:ThinkPad、Yoga ...... 在科技创新的大潮下,产品层出不穷。电脑虽多,但结构基本一致,都是由 CPU、主板、内存、显卡等组成。如果为每台电脑都单独设计一个组装流程,管理的成本太高。这时,建造者模式就出现了,为所有系列指定一个统一的组装流程,所有的电脑都按照这个流程来组装。



5

代码实现



创建产品


这里的产品是 Computer,包含了 CPU、主板、内存、显卡等信息。


#ifndef PRODUCT_H
#define PRODUCT_H

#include <iostream>

using namespace std;

// 电脑
class Computer
{

public:
    void SetCpu(string cpu) { m_strCpu = cpu;}
    void SetMainboard(string mainboard) { m_strMainboard = mainboard; }
    void SetRam(string ram) { m_strRam = ram; }
    void SetVideoCard(string videoCard) { m_strVideoCard = videoCard; }

    string GetCPU() return m_strCpu; }
    string GetMainboard()  return m_strMainboard; }
    string GetRam() return m_strRam; }
    string GetVideoCard() return m_strVideoCard; }

private:
    string m_strCpu;  // CPU
    string m_strMainboard;  // 主板
    string m_strRam;  // 内存
    string m_strVideoCard;  // 显卡
};

#endif // PRODUCT_H



创建抽象建造者


电脑准备好之后,就可以定义组装流程了,由 Builder 来表示。


#ifndef BUILDER_H
#define BUILDER_H

#include "product.h"

// 建造者接口,组装流程
class IBuilder
{

public:
    virtual void BuildCpu() 0;  // 创建 CPU
    virtual void BuildMainboard() 0;  // 创建主板
    virtual void BuildRam() 0;  // 创建内存
    virtual void BuildVideoCard() 0;  // 创建显卡
    virtual Computer* GetResult() 0;  // 获取建造后的产品
};

#endif // BUILDER_H



创建具体建造者


接下来,就是让 ConcreteBuilder 就位。在这些组装者中,可以指定每台电脑要使用的具体部件。


#ifndef CONCRETE_BULIDER_H
#define CONCRETE_BULIDER_H

#include "builder.h"

// ThinkPad 系列
class ThinkPadBuilder : public IBuilder
{
public:
    ThinkPadBuilder() { m_pComputer = new Computer(); }
    void BuildCpu() override { m_pComputer->SetCpu("i5-6200U"); }
    void BuildMainboard() override { m_pComputer->SetMainboard("Intel DH57DD"); }
    void BuildRam() override  { m_pComputer->SetRam("DDR4"); }
    void BuildVideoCard() override { m_pComputer->SetVideoCard("NVIDIA Geforce 920MX"); }
    Computer* GetResult() override return m_pComputer; }

private:
    Computer *m_pComputer;
};

// Yoga 系列
class YogaBuilder : public IBuilder
{
public:
    YogaBuilder() { m_pComputer = new Computer(); }
    void BuildCpu() override  { m_pComputer->SetCpu("i7-7500U"); }
    void BuildMainboard() override { m_pComputer->SetMainboard("Intel DP55KG"); }
    void BuildRam() override { m_pComputer->SetRam("DDR5"); }
    void BuildVideoCard() override { m_pComputer->SetVideoCard("NVIDIA GeForce 940MX"); }
    Computer* GetResult() override return m_pComputer; }

private:
    Computer *m_pComputer;
};

#endif // CONCRETE_BULIDER_H



创建指挥者


联想要生产电脑了,直接委派给组装者,开干。


#ifndef DIRECTOR_H
#define DIRECTOR_H

#include "builder.h"

// 构造指挥官
class Direcror
{

public:
    void Create(IBuilder *builder) {
        builder->BuildCpu();
        builder->BuildMainboard();
        builder->BuildRam();
        builder->BuildVideoCard();
    }
};

#endif // DIRECTOR_H



创建客户端


现在,一起来看看客户端,创建任何产品都非常干净。


#include "concrete_bulider.h"
#include "director.h"
#include <string>

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p){delete(p); (p)=NULL;} }
#endif

int main()
{
    Direcror *pDirecror = new Direcror();
    ThinkPadBuilder *pTPBuilder = new ThinkPadBuilder();
    YogaBuilder *pYogaBuilder = new YogaBuilder();

    // 组装 ThinkPad、Yoga
    pDirecror->Create(pTPBuilder);
    pDirecror->Create(pYogaBuilder);

    // 获取组装后的电脑
    Computer *pThinkPadComputer = pTPBuilder->GetResult();
    Computer *pYogaComputer = pYogaBuilder->GetResult();

    // 测试输出
    cout << "-----ThinkPad-----" << endl;
    cout << "CPU: " << pThinkPadComputer->GetCPU() << endl;
    cout << "Mainboard: " << pThinkPadComputer->GetMainboard() << endl;
    cout << "Ram: " << pThinkPadComputer->GetRam() << endl;
    cout << "VideoCard: " << pThinkPadComputer->GetVideoCard() << endl;

    cout << "-----Yoga-----" << endl;
    cout << "CPU: " << pYogaComputer->GetCPU() << endl;
    cout << "Mainboard: " << pYogaComputer->GetMainboard() << endl;
    cout << "Ram: " << pYogaComputer->GetRam() << endl;
    cout << "VideoCard: " << pYogaComputer->GetVideoCard() << endl;

    SAFE_DELETE(pThinkPadComputer);
    SAFE_DELETE(pYogaComputer);
    SAFE_DELETE(pTPBuilder);
    SAFE_DELETE(pYogaBuilder);
    SAFE_DELETE(pDirecror);

    getchar();

    return 0;
}


输出如下:

-----ThinkPad-----

CPU: i5-6200U

Mainboard: Intel DH57DD

Ram: DDR4

VideoCard: NVIDIA Geforce 920MX

-----Yoga-----

CPU: i7-7500U

Mainboard: Intel DP55KG

Ram: DDR5

VideoCard: NVIDIA GeForce 940MX


数以百计的 C/C++ 替代品将会出现,但 C/C++ 将永远与我们同在!

每个 CPU 都带有一种称为 ISA(指令集架构)汇编的电路语言。ISA 程序集是一种硬件语言,由基本数据操作、数学计算和结构化编程(即 jmp)的操作组成。但是,为每个计算需求编写汇编代码无疑是耗时的,因此过去的程序员发明了对人类友好的语言和编译器。

计算机科学家先驱 Dennis Ritchie 为 Unix 操作系统的研发需求实现了 C 语言。在这段时期,整个计算机技术领域都在进行基础的最初建设,所以几乎所有的程序员都使用 C 语言来构建早期的计算程序,比如编译器、操作系统、数据库软件和网络程序。后来,C++ 扩展了 C 语言,保留了 C 语言的性能特点,一门具有更多开发人员特性的新的编程语言诞生了。

在 20 世纪 20 年代,程序员实现了 C/ C++ 的备选品,如 Go、D、Rust 和 Carbon,它们具有 C/ C++ 从未提供的各种特性。但这些语言仍然只是 C/ C++ 的备选品,而不是替代品,原因如下:

1C 和 C++ 是基础语言

如果我们追根溯源当今活跃在我们生命中的每一款计算机程序,总会发现它们诞生自 C 或 C++。想想你现在在做什么,你可能在谷歌 Chrome 上读到这篇文章,Chrome 开源浏览器 (Blink 渲染引擎、V8 和浏览器应用程序) 是用 C++ 写的。假如你在 GNU/Linux 上运行 Chrome,Linux 内核是用 C 写的。MySQL,最流行的关系数据库管理系统,是用 C/ C++ 写的。所有流行的操作系统都为内核函数提供了核心 C 或 C++ API。

即使存在稳定的 C/ C++ 替代方案,许多程序员仍然喜欢用 C 或 C++ 进行系统编程。在大多数情况下,程序员选择 C++ 是因为它是与操作系统级 API 通信的最佳语言。例如,谷歌用 C++ 编写了 Flutter 引擎:

图片

Flutter 引擎使用 C/ C++ 应用 GTK 库函数,

截图由作者提

数十年来,计算领域的大多数核心组件都使用 C/ C++ 作为实现语言,C/ C++ 语言也长期维持着语言语法的标准。打造 C/ C++ 的替代品就像在所有的建筑工程完工之后改变房子的地基。

2C 和 C++ 完全控制我们所写的内容

在编写源代码时,C/ C++ 可以自由地处理程序资源。例如,C/ C++ 允许你直接分配 / 释放用于存储数据元素的物理内存。C/ C++ 提供了一种使用本机操作系统级线程的方法,而不是像 Go 那样管理单独的并发运行时。

C/ C++ 没有提供自动内存管理 (垃圾回收) 特性,因此程序员应该谨慎有效地防止内存泄漏。看看 Meta 的 Folly 库源代码是如何实现手动内存管理策略的:

图片

Meta 的 Folly 库使用了手动内存管理功能

自动内存管理和内存处理的限制无疑是使语言变得现代、高效和更抽象的好方法,但是这些特性会在语言运行时产生性能开销,并降低程序员的自由度。

C 和 C++ 不限制内存访问,提供手动的内存管理操作,让程序员按照自己的意愿控制程序,从而把自由给了程序员。当你用 C/ C++ 编写程序时,你的源代码将有效地执行你指示的操作,就是这样。

3C 和 C++ 确实又快又高效

一个特定程序的效率取决于两个主要因素:程序员使用的算法的时间复杂度和二进制程序的效率。毫无疑问,我们可以控制算法的复杂性,因为我们可以通过更新源代码来改变它们。另一方面,二进制文件是编译器生成的,因此我们无法轻易从这方面提高效率。

但是,我们可以选择一个能够生成快速有效的二进制文件的编译器。GNU 编译器生成特定于平台的二进制文件,而不嵌入专用的运行时环境。C 编程执行模型使用 crt0 汇编指令段中定义的最小启动代码。看看下面的例子,Linux 上的 crt0 部分:

图片

在 C 语言中 main 函数之前执行的启动代码

C++ 无疑是一门复杂的语言,但它不像 Python 和 Golang 那样提供更高的抽象。此外,它还为你提供了一种使用首选标准语言版本 (即 C++ 14) 进行编译的方法。因此,自现代 C++ 特性使 C++ 开发复杂化以来,你可以只使用 C++ 中最小的特性。C++ 已经有 30 多年的历史了,并且从早期计算时代开始就对其性能进行了优化。

4C 和 C++ 是学术友好型语言

程序员编程通常始于职业生涯的不同阶段。一些程序员在他们上学的时候就使用第一台计算机设备学习编程。然而,大多数程序员都是在大学期间提高他们的编程技能的。幸运的是,几乎所有的大学都是为了让学生有机会学习计算机程序如何与硬件组件一起工作而开始教授了 C 语言编程。我写了以下文章来进一步解释计算机程序是如何与硬件连接的:

编写优化代码前需要知道的 5 件事

https://www.infoq.cn/link?target=https%3A%2F%2Flevelup.gitconnected.com%2F5-things-to-know-before-you-write-optimized-code-3ca424110db1

这些事实有助于您以优化的性能给硬件和程序员留下深刻印象

https://www.infoq.cn/link?target=https%3A%2F%2Flevelup.gitconnected.com%2F5-things-to-know-before-you-write-optimized-code-3ca424110db1

后来,大多数大学教授 C++ 的数据结构和算法基础知识,而不使用 C++ 的复杂部分。大学生通常在学习了与算法相关的课程后,就会进入竞争激烈的编程领域。大多数有竞争力的程序员都喜欢 C++,因为它速度快,内置的最优数据结构可用性高,语法少。

Rust 无疑是一种很好的语言,具有内存安全、高性能和内置特性,但是 Rust 语法对于第一次编写代码的开发人员并不友好。对于工业用途来说,如果你的团队希望获得类似 C 语言的最小的高效代码和类似 python 的开发环境,Go 是一种很好的语言。但是,对于学术用途来说,Go 的抽象太过简单,并且不能与传统的伪代码保持一致,所以学术讲师永远不会用 Go 来替代 C/ C++。

下面的文章解释了为什么每个程序员都应该用 C 语言开始编程:

为什么每个开发人员都应该使用 C 语言开始编程

https://www.infoq.cn/link?target=https%3A%2F%2Fshalithasuranga.medium.com%2Fwhy-every-developer-should-start-programming-with-c-39b3a87392bf

你可以从任何一种语言开始编程——但是从 C 开始有更多的好处!

https://www.infoq.cn/link?target=https%3A%2F%2Fshalithasuranga.medium.com%2Fwhy-every-developer-should-start-programming-with-c-39b3a87392bf

5现代替代方案仍然需要 C,它们专注于不同的目标

如前所述,所有 POSIX 操作系统和非 POSIX 操作系统 (即 Windows) 都提供了一个 C 库来处理内核操作,因此从 C/ C++ 调用内核特性很容易,因为我们不需要编写特定于语言的绑定或第三方包装器。一些操作系统甚至预先包含 GNU C/ C++ 编译器和调试器来促进 C/ C++ 的开发。如果我们使用 Rust 和 Go,需要特定于语言的第三方绑定来与操作系统 API 通信。现代替代语言仍然提供了调用 C 代码的方法。例如,Go 提供 Cgo 特性来调用 C 代码。

几乎所有的 C/ C++ 替代方案都力求用与 C++ 截然不同的语言语法来提供缺少的 C++ 特性。

如果程序员在寻找 C/ C++ 的替代方案,他们通常会期望一个平稳且耗时较少的迁移过程。

此外,他们也不期望学习一门新语言来为他们不喜欢的 C/ C++ 特性找到解决方案。

程序员们针对低层次编程用例设计和改进了 C 语言,没有一种现代语言是完全针对 C 语言的目标而创建的。C++ 使 C 语言更具有未来感,并自低级编程阶段进行了提升。Rust、Go、D 和 Carbon 都是 C/ C++ 的备选品——而不是替代品,这些备选品都有自己的未来目标。

下面的文章解释了为什么每个程序员在他们的职业生涯中都需要学习 C++ 语言:

为什么每个程序员都应该在他们的职业生涯中学习 C++

https://www.infoq.cn/link?target=https%3A%2F%2Flevelup.gitconnected.com%2Fwhy-every-programmer-should-learn-c-during-their-careers-959e1bc2ea68

掌握 C++ 并不是一件容易的事,但是一旦你做到了,你将获得无价收益

https://www.infoq.cn/link?target=https%3A%2F%2Flevelup.gitconnected.com%2Fwhy-every-programmer-should-learn-c-during-their-careers-959e1bc2ea68

6结语

以前的程序员在 C/ C++ 进化时期书写了我们的计算机历史。他们用 C 和 C++ 构建操作系统内核、编程语言、数据库系统、移动操作系统和网络软件。多亏了 C/ C++,现在几乎所有的现代 Web 服务都能工作。例如,最流行的 Web 服务器软件,如 Apache HTTP 和 Nginx,就是使用 C/ C++ 作为实现语言。

以前的程序员几乎用 C/ C++ 编写了所有流行的内部网络浏览器和网络软件组件。Web 开发人员喜欢选择 Java 和 Node.js 用于 Web 服务,但 Java 和 Node.js 都是因 C/ C++ 而来的。

一些程序员认为像 Rust、Go、D 和 Carbon 这样的流行语言可以替代 C/ C++。同时,一些程序员考虑使用这些语言作为 C/ C++ 的备选品,认为它们在未来可能取代 C/ C++。这些现代语言是为特定的目的和需求而设计的——而不是取代 C/ C++。

微软创建了 TypeScript,但我们仍然使用 JavaScript。Jetbrains 创造了 Kotlin,但我们仍然使用 Java。数以百计的 C/ C++ 备选品出现了,但程序员仍将使用 C/ C++,因为重写面向 C/ C++ 的计算机历史是不现实的。

这并不意味着 C 和 C++ 是最好的语言——在某些方面 (例如复杂性、内存安全性等),备选语言可能比 C/ C++ 更好,但它们无法进入 C/ C++ 的领域,因为以前的程序员用 C 和 C++ 编写了整个现代计算机历史。

感谢你的阅读。


应用程序换到另一台机器运行时报错:“Ora-01017:用户名/口令无效; 登录被拒绝。”

检查各配置文件,确认用户名与密码无误,且通过plsql developer等工具都可正常连接,说明另有原因。

后经排查,发现新运行机器安装的是oracle 12.1c客户端,而服务器是12.2c,正是这引起了Ora-01017错误。

解决方法:

1) 在服务器环境,修改sqlnet.ora,添加以下两行:

SQLNET.ALLOWED_LOGON_VERSION_SERVER=10
SQLNET.ALLOWED_LOGON_VERSION_SERVER=10

其中的数字代表了可允许连接的最小版本号。

2) 重启监听服务:lsnrctl restart

3) 重新设置一遍用户密码:

SQL> conn / as sysdba;
SQL>ALTER USER myuser IDENTIFIED BY mypassword;


MySQL中字段类型为 longtext 的字段值保存的是Blob (Binary large objects),所以在导出sql或者将sql查询导出为其他格式的数据时,需要提前将字段类型转换一下,转换方式:

  使用MySQL的CAST()函数或者CONVERT()函数。

语法: CAST(value as type);
       CONVERT(value, type);

示例:

SELECT
	CAST(t.longtextField AS CHAR) AS longtextField
FROM
	tableName t;
SELECT
	CONVERT(t.longtextField, CHAR) AS longtextField
FROM
	tableName t;


export命令

功能说明:设置或显示环境变量。

语  法:export [-fnp][变量名称]=[变量设置值]

补充说明:在shell中执行程序时,shell会提供一组环境变量。 export可新增,修改或删除环境变量,供后续执行的程序使用。

export的效力仅及于该此登陆操作。

参  数:

    -f  代表[变量名称]中为函数名称。

  -n  删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。

  -p  列出所有的shell赋予程序的环境变量。

      一个变量创建时,它不会自动地为在它之后创建的shell进程所知。而命令export可以向后面的shell传递变量的值。当一个shell脚本调用并执行时,它不会自动得到原来脚本(调用者)里定义的变量的访问权,除非这些变量已经被显式地设置为可用。

      export命令可以用于传递一个或多个变量的值到任何后继脚本。

 

在 linux 里设置环境变量的三种实现方法(export PATH):

1.直接使用 export 命令 (我们以 mysql 服务举例说明)

[root@liyao ~]# export PATH=$PATH:/usr/local/mysql/bin

查看是否已经设置好,可以使用命令 export 命令来查看,也可以直接$#变量名#来查看

zhongweichaomatoMacBook-Pro:~ zhongweichao$ $PATH

-bash: :/Users/zhongweichao/.local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/Users/zhongweichao/Develop/jboss-5.1.0.GA/bin

需要注意: 直接使用 export 设置的变量都是临时变量,也就是说退出当前的 shell ,为该变量定义的值便不会生效了。如何能让我们定义的变量永久生效呢?那就看我们的第二种定义的方式。

2. 修改 /etc/profile

[root@liyao ~]# vi /etc/profile

export PATH=$PATH:/usr/local/mysql/bin  # 在配置文件中加入此行配置

需要注意的是:修改完这个文件必须要使用 以下命令在不用重启系统的情况下使修改的内容生效。

[root@liyao ~]# source /etc/profile

或者是用 ‘.’:

[root@liyao ~]# . /etc/profile

查看:

[root@liyao ~]# echo $PATH

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/mysql/bin

# 配置已经生效

3. 修改 .bashrc 文件是在当前用户 shell 下生效

# vi /root/.bashrc?在里面加入:

export PATH=$PATH:/usr/local/mysql/bin

修改这个文件之后同样也需要使用 source 或者是 . 使配置文件生效。

再来使用 echo $PATH看下变量是否生效

[root@liyao ~]# echo $PATH

/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/mysql/bin

shell与export命令

      用户登录到Linux系统后,系统将启动一个用户shell。在这个shell中,可以使用shell命令或声明变量,也可以创建并运行 shell脚本程序。运行shell脚本程序时,系统将创建一个子shell。此时,系统中将有两个shell,一个是登录时系统启动的shell,另一 个是系统为运行脚本程序创建的shell。当一个脚本程序运行完毕,它的脚本shell将终止,可以返回到执行该脚本之前的shell。从这种意义上来 说,用户可以有许多 shell,每个shell都是由某个shell(称为父shell)派生的。 

      在子 shell中定义的变量只在该子shell内有效。如果在一个shell脚本程序中定义了一个变量,当该脚本程序运行时,这个定义的变量只是该脚本程序内 的一个局部变量,其他的shell不能引用它,要使某个变量的值可以在其他shell中被改变,可以使用export命令对已定义的变量进行输出。 export命令将使系统在创建每一个新的shell时定义这个变量的一个拷贝。这个过程称之为变量输出。