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

news2024/12/23 20:12:18

本系列的文章,来介绍编程中的设计模式,介绍的内容主要为《大话设计模式》的读书笔记,并改用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++实现了三个版本的代码,由浅到深地理解面向对象的设计思想以及简单工厂模式的使用。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为防火墙配置 SSL VPN

前言 哈喽,我是ICT大龙。本期给大家更新一次使用华为防火墙实现SSL VPN的技术文章。 本次实验只需要用到两个软件,分别是ENSP和VMware,本次实验中的所有文件都可以在文章的末尾获取。话不多说,教程开始。 什么是VPN 百度百科解…

Pycharm中import torch报错解决方案(Python+Pycharm+Pytorch cpu版)

pycharm环境搭建完毕后,编写一个py文件demo,import torch报错,提示没有。设置python解释器: 选择conda环境,使用现有环境,conda执行文件找到Anaconda安装路径下Scripts文件夹内的conda.exe,最后…

十大人工智能企业

​​​​​​链接:​​​​​​人工智能品牌排行-人工智能十大公司-人工智能十大品牌-Maigoo品牌榜

发光二极管十大品牌

日常电路设计中,LED是必用的元器件之一,辅助判定电路异常。 十大发光二极管品牌-LED灯珠生产厂家哪家好-LED发光二极管厂家前十-Maigoo品牌榜

MinIO 分布式文件系统 快速入门 这篇就够了

1.MinIO简介 MinIO 是一个开源的对象存储服务,它提供了一个可扩展的分布式文件系统,用于存储和检索任意类型的数据。MinIO 旨在为云原生应用程序提供快速、可靠和成本效益高的存储服务,并支持多种数据格式和协议,如Amazon S3 API。…

深度学习简单概述

概述 理论上来说,参数越多的模型复杂度越高、容量越大,这意味着它能完成更复杂的学习任务。但复杂模型的训练效率低,易陷入过拟合。随着云计算、大数据时代的到来,计算能力的大幅提高可以缓解训练的低效性,训练数据的…

音视频转为文字SuperVoiceToText

音视频转为文字SuperVoiceToText,它能够把视频或语音文件高效地转换为文字,它是基于最为先进的 AI 大模型,通过在海量语音资料上进行训练学习而造就,具备极为卓越的识别准确率。 不仅如此,它支持包括汉语、英语、日语…

springboot 3 oauth2认证this.authorizationService.save(authorization)生成token报错异常

springboot 3 oauth2认证this.authorizationService.save(authorization)生成token报错异常&#xff0c;使用springboot版本3.3.0。 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId>&…

高考志愿填报:大学学什么专业比较好呢?

准高三一枚&#xff0c;比较迷茫&#xff0c;求推荐一些专业以后比较好就业&#xff0c;发展前景较好的。听说互联网行业比较吃香&#xff0c;有想过以后做运营这一块&#xff0c;但是不知道应该在大学选什么专业&#xff0c;求推荐吧&#xff01; 学什么专业好&#xff1f; 这…

【BUG】已解决: No module named ‘torch._six

已解决&#xff1a;No module named ‘torch._six 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;目前是武汉城市开发者社区主…

番外篇 | 超越ReLU却鲜为人知,YOLOv5改进之崛起的最佳激活函数GELU!

前言:Hello大家好,我是小哥谈。作为决定神经网络是否传递信息的「开关」,激活函数对于神经网络而言至关重要。不过今天被人们普遍采用的ReLU真的是最高效的方法吗?最近在社交网络上,人们找到了一个看来更强大的激活函数:GELU,这种方法早在2016年即被人提出,然而其论文迄…

Python进阶-部署Flask项目(以TensorFlow图像识别项目WSGI方式启动为例)

本文详细介绍了如何通过WSGI方式部署一个基于TensorFlow图像识别的Flask项目。首先简要介绍了Flask框架的基本概念及其特点&#xff0c;其次详细阐述了Flask项目的部署流程&#xff0c;涵盖了服务器环境配置、Flask应用的创建与测试、WSGI服务器的安装与配置等内容。本文旨在帮…

UltraScale+系列模块化仪器,可以同时用作控制器、算法加速器和高速数字信号处理器

基于 XCZU7EG / XCZU4EG / XCZU2EG • 灵活的模块组合 • 易于嵌入的紧凑型外观结构 • 高性能的 ARM Cortex 处理器 • 成熟的 FPGA 可编程逻辑 &#xff0c;基于 IP 核的软件库 基于 Xilinx Zynq UltraScaleMPSoC 的 FPGA 技术&#xff0c;采用 Xilinx Zynq UltraScale&a…

把系统引导做到U盘,实现插上U盘才能开机

前言 有个小伙伴提出了这样一个问题&#xff1a;能不能把U盘制作成电脑开机的钥匙&#xff1f; 小白稍微思考了一下&#xff0c;便做了这样一个回复&#xff1a;可以。 至于为什么要思考一下&#xff0c;这样会显得我有认真思考他提出的问题。 Windows7或以上系统均支持UEF…

LLM中完全消除矩阵乘法,效果惊人!10亿参数在FPGA上运行功耗接近大脑!!

一直以来&#xff0c;矩阵乘法&#xff08;MatMul&#xff09;在神经网络操作中占据主导地位&#xff0c;主要因为GPU针对MatMul进行了优化。 老黄一举揭秘三代GPU&#xff01;打破摩尔定律&#xff0c;打造AI帝国&#xff0c;量产Blackwell解决ChatGPT全球耗电难题 这种优化使…

2024真机项目

项目需求&#xff1a; 1. 172.25.250.101 主机上的 Web 服务要求提供 www.exam.com 加密站点&#xff0c;该站点在任何路由可达 的主机上被访问&#xff0c;页面内容显示为 "Hello&#xff0c;Welcome to www.exam.com !"&#xff0c;并提供 content.exam.com/yum/A…

VSFTP安装部署

1、检查vsftpdL软件是否安装 rpm –q vsftpd 2、挂载安装盘rpm安装包 mkdir /mnt/iso mount –o loop linux.iso /mnt/iso #挂载光盘鏡像文件 3、安装vsftpd 另外&#xff0c;如果电脑可以正连网&#xff0c;可以使用yum –y install vsftpd进行安装 rpm -ivh /mnt/iso…

Python 很好用的爬虫框架:Scrapy:

了解Scrapy 爬虫框架的工作流程&#xff1a; 在scrapy中&#xff0c; 具体工作流程是这样的&#xff1a; 首先第一步 当爬虫引擎<engine>启动后&#xff0c; 引擎会到 spider 中获取 start_url<起始url> 然后将其封装为一个request对象&#xff0c; 交给调度器<…

ARM-V9 RME(Realm Management Extension)系统架构之系统安全能力的架构差异

安全之安全(security)博客目录导读 RME系统中的应用处理单元&#xff08;PE&#xff09;之间的架构差异可能会带来潜在的安全风险并增加管理软件的复杂性。例如&#xff0c;通过在ID_AA64MMFR0_EL1.PARange中为每个PE设置不同的值来支持不同的物理范围&#xff0c;可能会妨碍内…

使用vite从0开始搭建vue项目

使用Vite从0开始创建vue项目 第一步&#xff1a;创建项目目录 mkdir vue-demo -创建目录 cd vue-demo --进入项目 npm init -y --生成package.json文件 第二步&#xff1a;安装vite、typescript--ts、vue、vitejs/plugin-vue--对单文件组件、热重载、生产优化的支持 pnpm…