大话设计模式解读01-简单工厂模式

本系列的文章,来介绍编程中的设计模式,介绍的内容主要为《大话设计模式》的读书笔记,并改用C++语言来实现(书中使用的是.NET中的C#),本篇来学习第一章,介绍的设计模式是——简单工厂模式

1 面向对象编程

设计模式依赖与面向对象编程密不可分,因此在开始学习设计模式之前,先简单介绍下面向对象编程。

先来看一个小故事:

话说三国时期,曹操在赤壁带领百万大军,眼看就要灭掉东吴,统一天下,非常高性,于是大宴文武。

在酒席间,不觉吟到:“喝酒唱歌,人生真爽,…”,众文武齐呼:“丞相好诗!”,

于是一臣子速速命令印刷工匠进行刻版印刷,以便流传天下。

印刷工匠刻好样张,拿出来给曹操一看,曹操感觉不妥,

说道:“喝与唱,此话过俗,应该改为对酒当歌较好!”,

于是臣子就命令工匠重新来过,工匠眼看连夜刻版之功,彻底白费,心中叫苦不迭,只得照办。

印刷工匠再次刻好样张,拿出来给曹操过目,曹操细细一品,觉得还是不好,

说:“人生真爽太过直接,应该改为问句才够意境,因此应改为对酒当歌,人生几何”,

当臣子再次转告工匠之时,工匠晕倒…

那,问题出在哪里呢?

大概是三国时期还没有活字印刷术吧,所以要改字的时候,就必须整个刻板全部重新雕刻。

如果有了活字印刷术,其实只需要更改四个字即可,其余工作都未白做。

我们联想编程,从这个小故事中,来体会一下编程中的一些思想:

  • 可维护:要改字,只需更改需要变动的字即可
  • 可复用:这些字并不是只是这次有用,后续如果在其它印刷中需要用,可重复使用
  • 可扩展:如果诗中需要加字,只需另外单独刻字即可
  • 灵活性:字的排列可以横排,也可以竖排

面向对象编程,通过封装、继承和多态,把程序的耦合度降低

传统印刷术的问题就在于把所有字都刻在同一个版面上的耦合度太高。

使用设计模式可以使程序更加灵活,容易修改,并易于复用。

2 计算器实例

下面以一个计算器的代码实例,来体会封装的思想,以及简单工厂模式的使用。

题目:设计一个计算器控制台程序,输入为两个数和运算符,输出结果

功能比较简单,先来看第一个版本的实现。

2.1 版本一:面向过程

第一个版本采用面向过程的思想,从接收用户输入,到数据运算,以及最后的输出,都是按顺序在一个代码块中实现的:

int main()
{
    float numA = 0;
    float numB = 0;
    float result = 0;
    char operate;
    bool bSuccess = true;
    
    printf("please input a num A:\n");
    scanf("%f", &numA);
    printf("please input a operate(+ - * \\):\n");
    std::cin >> operate;
    printf("please input a num B:\n");
    scanf("%f", &numB);
    
    switch(operate)
    {
        case '+':
        {
            result = numA + numB;
            break;
        }
        case '-':
        {
            result = numA - numB;
            break;
        }
        case '*':
        {
            result = numA * numB;
            break;
        }
        case '/':
        {
            if (numB == 0)
            {
                bSuccess = false;
                printf("divisor cannot be 0!\n");
                break;
            }
            result = numA / numB;
            break;
        }
        default:
        {
            bSuccess = false;
            break;
        }
    }
    
    if (bSuccess)
    {
        printf("%f %c %f = %f\n", numA, operate, numB, result);
    }
    else
    {
        printf("[%f %c %f] calc fail!\n", numA, operate, numB);
    }
    
    return 0;
}

该程序的运行效果如下图所示:

上述代码实现本身没有什么问题,但是,如果现在要再实现一个带有UI界面的计算器,代码能不能复用呢?很显然不行,代码都是在一起的。

因此,为了便于代码复用,可以将计算部分的代码和显示部分的代码分开,降低它们之间的耦合度

2.2 版本二:对业务封装

版本二则是对计算部分的业务代码显示部分的控制台输入输出代码分开。

计算部分的业务代码,设计一个Operation运算类,通过其成员函数GetResult来实现加减乘除运算。

2.2.1 业务代码

class Operation
{
public:
    bool GetResult(float numA, float numB, char operate, float &result)
    {
        bool bSuccess = true;
        
        switch(operate)
        {
            case '+':
            {
                result = numA + numB;
                break;
            }
            case '-':
            {
                result = numA - numB;
                break;
            }
            case '*':
            {
                result = numA * numB;
                break;
            }
            case '/':
            {
                if (numB == 0)
                {
                    bSuccess = false;
                    printf("divisor cannot be 0!\n");
                    break;
                }
                result = numA / numB;
                break;
            }
            default:
            {
                bSuccess = false;
                break;
            }
        }
        
        return bSuccess;
    }
};

2.2.2 控制台界面代码

显示部分的控制台输入输出代码,还在main函数中。

int main()
{
    float numA = 0;
    float numB = 0;
    float result = 0;
    char operate;
    
    printf("please input a num A:\n");
    scanf("%f", &numA);
    printf("please input a operate(+ - * \\):\n");
    std::cin >> operate;
    printf("please input a num B:\n");
    scanf("%f", &numB);
    
    Operation Op1;
    bool bSuccess = Op1.GetResult(numA, numB, operate, result);
    
    if (bSuccess)
    {
        printf("%f %c %f = %f\n", numA, operate, numB, result);
    }
    else
    {
        printf("[%f %c %f] calc fail!\n", numA, operate, numB);
    }
    
    return 0;
}

版本二的运行效果演示如下:

上述的版本二的代码实现,就用到了面向对象三大特性中的封装

那,上述代码,是否可以做到灵活扩展?

比如,如果希望增加一个开根号的运算,如果改?

按照现有逻辑,需要修改Operation运算类,在switch中增加一个分支。但这样,会需要加减乘除的逻辑再次参与编译,另外,如果在修改开根号的代码时,不小心改动了加减乘除的逻辑,影响就大了。

因此,可以使用面向对象中继承和多态的思想,来实现各个运算类的分离。

2.3 版本三:简单工厂

版本三用到了封装、继承、多态,以及通过简单工厂来实例化出合适的对象。

2.3.1 Operation运算类(父类)

Operation运算类为一个抽象类,是加减乘除类的父类。

该类包含numA和numB两个成员变量,以及一个虚函数GetResult用于计算运算结果,各个子类中对其进行具体的实现。

// 操作类(父类)
class Operation
{
public:
    float numA = 0;
    float numB = 0;
    
public:
    virtual float GetResult()
    {
        return 0;
    };
};

2.3.2 加减乘除类(子类)

加减乘除子类通过公有继承Operation类,可以访问其共有成员变量numA和numB,并对GetResult方法进行具体的实现:

// 加法类(子类)
class OperationAdd : public Operation
{
public:
    float GetResult()
    {
        return numA + numB;
    }
};

// 减法类(子类)
class OperationSub : public Operation
{
public:
    float GetResult()
    {
        return numA - numB;
    }
};

// 乘法类(子类)
class OperationMul : public Operation
{
public:
    float GetResult()
    {
        return numA * numB;
    }
};

// 除法类(子类)
class OperationDiv : public Operation
{
public:
    float GetResult()
    {
        if (numB == 0)
        {
            printf("divisor cannot be 0!\n");
            return 0;
        }
        return numA / numB;
    }
};

2.3.3 简单运算工厂类

为了能方便地实例化加减乘除类,考虑使用一个单独的类来做这个创造实例的过程,这个就是工厂。

设计一个OperationFactory类来实现,这样,只要输入运算的符号,就能实例化出合适的对象。

// 简单工厂模式
class OperationFactory
{
public:
    Operation *createOperation(char operation)
    {
        Operation *oper = nullptr;
        switch(operation)
        {
            case '+':
            {
                oper = (Operation *)(new OperationAdd());
                break;
            }
            case '-':
            {
                oper = (Operation *)(new OperationSub());
                break;
            }
            case '*':
            {
                oper = (Operation *)(new OperationMul());
                break;
            }
            case '/':
            {
                oper = (Operation *)(new OperationDiv());
                break;
            }
            default:
            {
                break;
            }
        }
        
        return oper;
    }
};

使用版本三,如果后续需要修改加法运算,只需要修改OperationAdd类中的内容即可,不会影响到其它计算类。

2.3.4 控制台界面代码

显示部分的控制台输入输出代码,还在main函数中。

通过多态,返回父类的方式,实现对应运算的计算结果。

{
    float numA = 0;
    float numB = 0;
    float result = 0;
    char operate;
    
    printf("please input a num A:\n");
    scanf("%f", &numA);
    printf("please input a operate(+ - * \\):\n");
    std::cin >> operate;
    printf("please input a num B:\n");
    scanf("%f", &numB);
    
    OperationFactory opFac;
    Operation *oper = nullptr;
    oper = opFac.createOperation(operate);
    if (oper != nullptr)
    {
        oper->numA = numA;
        oper->numB = numB;
        result = oper->GetResult();
        printf("%f %c %f = %f\n", numA, operate, numB, result);
        
        delete oper;
    }
    else
    {
        printf("[%f %c %f] calc fail!\n", numA, operate, numB);
    }
    
    return 0;
}

版本三的运行效果演示如下:

版本三中,各个类之间的关系如下图所示:

  • 运算类是一个抽象类(类名用斜体表示),具有两个float类型的公有的(共有用**+号**)成员变量numA和numB以及一个GetResult公有方法
  • 四个计算类继承(继承空心三角+实线表示)运算类,并实现对应的GetResult方法
  • 简单工厂类依赖于(依赖箭头+虚线表示)运算类,通过createOperation方法实现运算类的实例化

3 总结

本篇主要介绍设计模式中的简单工厂模式,首先通过一个活字印刷的小故事来体会程序设计中的可维护、可复用、可扩展、灵活性的思想,并引入面向对象设计模式中的三大基本思想:封装、继承、多态,然后通过一个计算器的代码实现的例子,通过C++实现了三个版本的代码,由浅到深地理解面向对象的设计思想以及简单工厂模式的使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/676253.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

989.数组形式的整数加法

对于非负整数 X而言&#xff0c;x的数组形式是每位数字按从左到右的顺序形成 的数组。例如&#xff0c;如果 X1231&#xff0c;那么其数组形式为[1,2,3,1]。 给定非负整数 X的数组形式 A&#xff0c;返回整数 X 的数组形式, #include <stdio.h> #include <stdlib.h>…

【面试经典150题】删除有序数组中的重复项||

目录 一.题目解析二.解法一三.解法二 一.题目解析 首先我们先看一下题目描述&#xff1a; 删除数组中的重复项的升级版要求&#xff0c;一个升序数组序列中&#xff0c;相同的元素最多出现两次。 二.解法一 首项我们先来看一种比较繁琐坑比较多的解法&#xff1a; class S…

JCR一区级 | Matlab实现TCN-BiLSTM-MATT时间卷积双向长短期记忆神经网络多特征分类预测

JCR一区级 | Matlab实现TCN-BiLSTM-MATT时间卷积双向长短期记忆神经网络多特征分类预测 目录 JCR一区级 | Matlab实现TCN-BiLSTM-MATT时间卷积双向长短期记忆神经网络多特征分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.JMatlab实现TCN-BiLSTM-MATT时间卷积双…

Spring Boot 整合开源 Tess4J库 实现OCR图片文字识别

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Python | 试卷刷题and基础笔记

1.下列转义字符中&#xff0c; 属于“回车”含义的是 \n 换行 \r 回车 2.for循环遍历字典 在Python中&#xff0c;你可以使用for循环来遍历字典的键&#xff08;keys&#xff09;、值&#xff08;values&#xff09;或者键-值对&#xff08;items&#xff09;。下面是三种遍历…

RPA机器人未来的发展方向和趋势

在数字经济的大背景下&#xff0c;众多企业重新寻找自身的创新驱动力&#xff0c;数字化转型需求迎来爆发式增长。在强劲的数字化转型需求以及国家政策的推动下&#xff0c;RPA行业即将迎来更为有利的发展局面。Gartner预测&#xff0c;到2025年&#xff0c;超级自动化市场规模…

【JavaScript】---DOM操作1:获取元素

【JavaScript】—DOM操作1&#xff1a;获取元素 文章目录 【JavaScript】---DOM操作1&#xff1a;获取元素一、什么是DOM&#xff1f;1.1 概念1.2 图例演示 二、查找HTML元素2.1 getElementById()2.2 getElementsByTagName()2.3 getElementsByClassName()2.4 querySelector()2.…

成人本科毕业论文怎么写?分享自己的经验

撰写成人本科毕业论文是一个系统而深入的过程&#xff0c;以下是我个人的经验分享&#xff0c;希望能帮助你更好地完成这一任务&#xff1a; 1. 明确论文选题 兴趣与专长&#xff1a;选择自己感兴趣且有一定专长的领域&#xff0c;这样更容易深入研究。可行性&#xff1a;确保…

NocoDB开源的智能表格详解-腾讯文档本地替代品

文章目录 一、介绍二、docker-compose部署三、登录NocoDB四、NocoDB手册1. 创建项目2. 收集统计表2.1 添加字段2.2 编辑字段2.3 字段类型2.4 发布表格 3.创建表单3.1 创建表单3.2 分享表单3.3 填写检测单 4.创建看板5.创建画廊 一、介绍 可作为腾讯文档的本地电子表格替代品&a…

Springboot作业管理系统的设计与实现-计算机毕业设计源码98119

目 录 摘要 1 绪论 1.1研究背景 1.2研究现状 1.3springboot框架介绍 1.4论文结构与章节安排 2 作业管理系统系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 法律可行性分析 2.2 系统功能分析 2.2.1 功能性分析 2.2.2 非功能性分析 2…

论文摘要一般要写些什么内容?

论文摘要通常需要包含以下几个关键内容&#xff1a; 研究背景与目的&#xff1a;简要介绍研究的背景信息&#xff0c;包括研究领域的重要性、当前的研究现状以及存在的问题。然后&#xff0c;清晰地阐述研究的目的、研究问题或研究假设&#xff0c;让读者明白研究的出发点和意图…

python 各种画图(2D 3D)-1 _matplotlib 官方网站笔记

背景 需利用python进行3D可视化处理&#xff0c;用于分析python得到的数据的正确性。 知识学习 python高阶3D绘图---pyvista模块&#xff0c;mayavi模块&#xff0c;pyopengl模块&#xff0c;MoviePy模块基础使用-CSDN博客 python用于3D绘图的模块比较多&#xff0c;pyvist…

Apache Doris 基础 -- 数据表设计(表索引)

1、索引概述 索引用于帮助快速过滤或搜索数据。目前&#xff0c;Doris支持两种类型的索引:内置智能索引和用户创建的二级索引。 内置智能索引 排序键和前缀索引:Apache Doris基于排序键以有序的方式存储数据。它为每1024行数据创建一个前缀索引。索引中的键是当前1024行组的…

2024码蹄杯初赛 拔河(非二分解法)

AK选手前来补充一发邪典&#xff08;水数据&#xff09;写法 题面&#xff1a; 简单来说就是给你一个序列&#xff0c;让你选择一段连续区间&#xff0c;使得这个区间平均值最大&#xff0c;同时区间长度大于等于F。 很显然对于区间求和直接用前缀和优化到O(1)&#xff0c;但是…

代码随想录 day 26

回溯 组合总和 题意&#xff1a;一个无重复元素的整数数组&#xff1b;一个目标整数target&#xff1b; 找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff1b;并以列表形式返回。candidates 的同一个数字可以无限制重复被选取。 思路&#xff1a;因为…

半导体光子电学期末笔记2: 光子晶体 Photonic crystals

光子晶体概述 光子晶体定义和分类 [P4-5] 光子晶体是一种在一维、二维或三维空间内周期性排列的多层介质。这些结构通过在光子尺度上排列的重复单元&#xff0c;可以对光进行调控和控制。具体来说&#xff0c;光子晶体是指那些在空间上具有周期性排列的介质结构&#xff0c;它…

文心一言 VS 讯飞星火 VS chatgpt (273)-- 算法导论20.2 8题

八、假设设计了这样一个 proto-vEB 结构&#xff0c;其中每个簇数组仅有 u 1 4 u^\frac{1}{4} u41​ 个元素。那么每个操作的运行时间是多少&#xff1f;如果要写代码&#xff0c;请用go语言。 文心一言&#xff1a; 如果你修改了 van Emde Boas (vEB) 树中的簇大小&#xf…

贪心算法题实战详解

文章目录 例题1&#xff1a;活动安排问题例题2&#xff1a;货币找零问题例题3&#xff1a;分数背包问题&#xff08;部分背包问题&#xff09;例题4&#xff1a;最小生成树问题&#xff08;Prim算法&#xff09;例题5&#xff1a;哈夫曼编码例题6&#xff1a;活动选择问题例题7…

KAN(Kolmogorov-Arnold Network)的理解 3

系列文章目录 第一部分 KAN的理解——数学背景 第二部分 KAN的理解——网络结构 第三部分 KAN的实践——第一个例程 文章目录 系列文章目录前言KAN 的第一个例程 get started 前言 这里记录我对于KAN的探索过程&#xff0c;每次会尝试理解解释一部分问题。欢迎大家和我一起讨…

Spring 之 Lifecycle 及 SmartLifecycle

最近在看Eureka源码&#xff0c;本想快速解决这场没有硝烟的战役&#xff0c;不曾想阻塞性问题一个接一个。为正确理解这个框架&#xff0c;我不得不耐着性子&#xff0c;慢慢梳理这些让人困惑的点。譬如本章要梳理的Lifecycle和SmartLifecycle。它们均为接口&#xff0c;其中后…