好菜每回味道不同--建造者模式
1.1 炒菜没放盐
中餐,老板需要每次炒菜,每次炒出来的味道都有可能不同。麦当劳、肯德基这些不过百年的洋快餐却能在有千年饮食文化的中国发展的那么好呢?是因为你不管何时何地在哪里吃味道都一样,而鱼香肉丝在我们中餐却可以吃出上完口味来。
依赖倒转原则?抽象不应该依赖细节,细节应该依赖于抽象,由于我们要吃的菜都依赖于厨师这样的细节,所以我们就很被动。
"好,那再想想,老麦老肯他们的产品,味道是由什么决定的?"
"我知道,那是由他们的工作流程决定的,由于他们制定了非常规范的工作流程,原料放多少,加热几分钟,都有严格规定,估计放多少盐都是用克来计量的。而这个工作流程是在所有的门店都必须要遵照执行的,所以我们吃到的东西不管在哪在什么时候味道都一样。这里我们要吃的食物都依赖工作流程。不过工作流程好像还是细节呀。"
"对,工作流程也是细节,我们去快餐店消费,我们用不用关心他们的工作流程?当然是不用,我们更关心的是是否好吃。你想如果老肯发现鸡翅烤得有些焦,他们会调整具体的工作流程中的烧烤时间,如果新加一种汉堡,做法都相同,只是配料不相同,工作流程是不变的,只是加了一种具体产品而已,这里工作流程怎么样?"
"对,这里工作流程可以是一种抽象的流程,具体放什么配料、烤多长时间等细节依赖于这个抽象。"
1.2 建造小人一
建造小人,要求要有头、身体、两手、两脚就可以了
package code.chapter13.builder1;
import java.awt.Graphics;
import javax.swing.JFrame;class Test extends JFrame {public Test() {setSize(400, 400);setDefaultCloseOperation(EXIT_ON_CLOSE);setLocationRelativeTo(null);}public void paint(Graphics g) {//瘦小人g.drawOval(150, 120, 30, 30); //头g.drawRect(160, 150, 10, 50); //身体g.drawLine(160, 150, 140, 200); //左手g.drawLine(170, 150, 190, 200); //右手g.drawLine(160, 200, 145, 250); //左脚g.drawLine(170, 200, 185, 250); //右脚//胖小人g.drawOval(250, 120, 30, 30); //头g.drawOval(245, 150, 40, 50); //身体g.drawLine(250, 150, 230, 200); //左手g.drawLine(280, 150, 300, 200); //右手g.drawLine(260, 200, 245, 250); //左脚g.drawLine(270, 200, 285, 250); //右脚}public static void main(String[] args) {new Test().setVisible(true);}
}
这样的话,有可能少画了一条腿或者一条胳膊,就像厨师有可能忘记放盐。
1.3 建造小人二
建两个类,一个廋人的类,一个胖子的类,不管谁都可以调用它
package code.chapter13.builder2;
import java.awt.Graphics;
import javax.swing.JFrame;class Test extends JFrame {public Test() {setSize(400, 400);setDefaultCloseOperation(EXIT_ON_CLOSE);setLocationRelativeTo(null);}public void paint(Graphics g) {//初始化瘦小人建造者类PersonThinBuilder gThin = new PersonThinBuilder(g);gThin.build();//画瘦小人//初始化胖小人建造者类PersonFatBuilder gFat = new PersonFatBuilder(g);gFat.build();//画胖小人}public static void main(String[] args) {new Test().setVisible(true);}
}//瘦小人建造者
class PersonThinBuilder {private Graphics g;public PersonThinBuilder(Graphics g){this.g=g;}public void build(){g.drawOval(150, 120, 30, 30); //头g.drawRect(160, 150, 10, 50); //身体g.drawLine(160, 150, 140, 200); //左手g.drawLine(170, 150, 190, 200); //右手g.drawLine(160, 200, 145, 250); //左脚g.drawLine(170, 200, 185, 250); //右脚}
}//胖小人建造者
class PersonFatBuilder {private Graphics g;public PersonFatBuilder(Graphics g){this.g=g;}public void build(){g.drawOval(250, 120, 30, 30); //头g.drawOval(245, 150, 40, 50); //身体g.drawLine(250, 150, 230, 200); //左手g.drawLine(280, 150, 300, 200); //右手g.drawLine(260, 200, 245, 250); //左脚g.drawLine(270, 200, 285, 250); //右脚}
}
如果再增加一个高个子的小人,也有可能不小心,最好的办法是规定,凡是建造小人,都必须要有头和身体,以及两手两脚。
1.4 建造者模式
如果你需要将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图时,我们需要应用于一个设计模式,'建造者模式(Builder)',又叫生成器模式。建造者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。如果我们用了建造者模式,那么用户就只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。
建造者模式(Builder),将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。[DP]
"那怎么用建造者模式呢?"
"一步一步来,首先我们要画小人,都需要画什么?"
"头、身体、左手、右手、左脚、右脚。"
"对的,所以我们先定义一个抽象的建造人的类,来把这个过程给稳定住,不让任何人遗忘当中的任何一步。"
"然后,我们需要建造一个瘦的小人,则让这个瘦子类去继承这个抽象类,那就必须去重写这些抽象方法了。否则编译器也不让你通过。"
"当然,胖人或高个子其实都是用类似的代码去实现这个类就可以了。"
"这样,我在客户端要调用时,还是需要知道头身手脚这些方法呀?没有解决问题。"小菜不解地问。
"别急,我们还缺建造者模式中一个很重要的类,指挥者(Director),用它来控制建造过程,也用它来隔离用户与建造过程的关联。"
"你看到没有,PersonDirector类的目的就是根据用户的选择来一步一步建造小人,而建造的过程在指挥者这里完成了,用户就不需要知道了,而且,由于这个过程每一步都是一定要做的,那就不会让少画了一只手,少画一条腿的问题出现了。"
"代码结构图如下。"

package code.chapter13.builder3;
import java.awt.Graphics;
import javax.swing.JFrame;class Test extends JFrame {public Test() {setSize(400, 400);setDefaultCloseOperation(EXIT_ON_CLOSE);setLocationRelativeTo(null);}public void paint(Graphics g) {PersonBuilder gThin = new PersonThinBuilder(g);PersonDirector pdThin = new PersonDirector(gThin);pdThin.CreatePerson();PersonBuilder gFat = new PersonFatBuilder(g);PersonDirector pdFat = new PersonDirector(gFat);pdFat.CreatePerson();}public static void main(String[] args) {new Test().setVisible(true);}
}//抽象的建造者类
abstract class PersonBuilder {protected Graphics g;public PersonBuilder(Graphics g){this.g = g;}public abstract void buildHead(); //头public abstract void buildBody(); //身体public abstract void buildArmLeft(); //左手public abstract void buildArmRight(); //右手public abstract void buildLegLeft(); //左脚public abstract void buildLegRight(); //右脚
}//瘦小人建造者
class PersonThinBuilder extends PersonBuilder {public PersonThinBuilder(Graphics g){super(g);}public void buildHead(){g.drawOval(150, 120, 30, 30); //头}public void buildBody(){g.drawRect(160, 150, 10, 50); //身体}public void buildArmLeft(){g.drawLine(160, 150, 140, 200); //左手}public void buildArmRight(){g.drawLine(170, 150, 190, 200); //右手}public void buildLegLeft(){g.drawLine(160, 200, 145, 250); //左脚}public void buildLegRight(){g.drawLine(170, 200, 185, 250); //右脚 }
}//胖小人建造者
class PersonFatBuilder extends PersonBuilder {public PersonFatBuilder(Graphics g){super(g);}public void buildHead(){g.drawOval(250, 120, 30, 30); //头}public void buildBody(){g.drawOval(245, 150, 40, 50); //身体}public void buildArmLeft(){g.drawLine(250, 150, 230, 200); //左手}public void buildArmRight(){g.drawLine(280, 150, 300, 200); //右手}public void buildLegLeft(){g.drawLine(260, 200, 245, 250); //左脚}public void buildLegRight(){g.drawLine(270, 200, 285, 250); //右脚}
}//指挥者
class PersonDirector{private PersonBuilder pb;//初始化时指定需要建造什么样的小人public PersonDirector(PersonBuilder pb){this.pb=pb;}//根据用户的需要建造小人public void CreatePerson(){pb.buildHead(); //头pb.buildBody(); //身体pb.buildArmLeft(); //左手pb.buildArmRight(); //右手pb.buildLegLeft(); //左脚pb.buildLegRight(); //右脚}
}
"哈,我明白了,那客户端的代码我来写吧。应该也不难实现了。"
"试想一下,我如果需要增加一个高个子和矮个子的小人,我们应该怎么做?"
"加两个类,一个高个子类和一个矮个子类,让它们都去继承PersonBuilder,然后客户端调用就可以了。但我有个问题,如果我需要细化一些,比如人的五官,手的上臂、前臂和手掌,大腿小腿这些,如何办呢?"
"问得好,这就需要权衡,如果这些细节是每个具体的小人都需要构建的,那就应该要加进去,反之就没必要。其实建造者模式是逐步建造产品的,所以建造者的Builder类里的那些建造方法必须要足够普遍,以便为各种类型的具体建造者构造。"
1.5 建造者解析
建造者模式(Builder)结构图

"现在你看这张图就不会感觉陌生了。来总结一下,Builder是什么?"
"是一个建造小人各个部分的抽象类。"
"概括地说,是为创建一个Product对象的各个部件指定的抽象接口。ConcreteBuilder是什么呢?"
"具体的小人建造者,具体实现如何画出小人的头身手脚各个部分。"
"对的,它是具体建造者,实现Builder接口,构造和装配各个部件。Product当然就是那些具体的小人,产品角色了,Director是什么?"
"指挥者,用来根据用户的需求构建小人对象。"
"嗯,它是构建一个使用Builder接口的对象。"
"那都是什么时候需要使用建造者模式呢?"
"它主要用于创建一些复杂的对象,这些对象内部子对象的建造顺序通常是稳定的,但每个子对象本身的构建通常面临着复杂的变化。"
"哦,是不是建造者模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。"
"来来来,我们来试着把建造者模式的基本代码推演一下,以便有一个更宏观的认识。"
1.6 建造者模式基本代码
Product类——产品类,由多个部件组成。
Builder类——抽象建造者类,确定产品由两个部件PartA和PartB组成,并声明一个得到产品建造后结果的方法GetResult。
ConcreteBuilder1类——具体建造者类。
ConcreteBuilder2类——具体建造者类。
Director类——指挥者类。
客户端代码,客户不需要知道具体的建造过程。
package code.chapter13.builder0;import java.util.ArrayList;public class Test {public static void main(String[] args){System.out.println("**********************************************"); System.out.println("《大话设计模式》代码样例");System.out.println(); Director director = new Director();Builder b1 = new ConcreteBuilder1();Builder b2 = new ConcreteBuilder2();//指挥者用ConcreteBuilder1的方法来建造产品director.construct(b1); //创建的是产品A和产品BProduct p1 = b1.getResult();p1.show();//指挥者用ConcreteBuilder2的方法来建造产品director.construct(b2); //创建的是产品X和产品YProduct p2 = b2.getResult();p2.show();System.out.println();System.out.println("**********************************************");}
}//产品类
class Product{ArrayList<String> parts = new ArrayList<String>();//添加新的产品部件public void add(String part){parts.add(part);}//列举所有产品部件public void show(){for(String part : parts){System.out.println(part);}}
}//抽象的建造者类
abstract class Builder {public abstract void buildPartA(); //建造部件Apublic abstract void buildPartB(); //建造部件Bpublic abstract Product getResult(); //得到产品
}//具体建造者1
class ConcreteBuilder1 extends Builder {private Product product = new Product();public void buildPartA(){product.add("部件A");}public void buildPartB(){product.add("部件B");}public Product getResult(){return product;}
}//具体建造者2
class ConcreteBuilder2 extends Builder {private Product product = new Product();public void buildPartA(){product.add("部件X");}public void buildPartB(){product.add("部件Y");}public Product getResult(){return product;}
}//指挥者
class Director{public void construct(Builder builder){builder.buildPartA();builder.buildPartB();}
}
"所以说,建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。"
相关文章:
好菜每回味道不同--建造者模式
1.1 炒菜没放盐 中餐,老板需要每次炒菜,每次炒出来的味道都有可能不同。麦当劳、肯德基这些不过百年的洋快餐却能在有千年饮食文化的中国发展的那么好呢?是因为你不管何时何地在哪里吃味道都一样,而鱼香肉丝在我们中餐却可以吃出上…...
RuoYi-Cloud下载与运行
一、源码下载 若依官网:RuoYi 若依官方网站 鼠标放到"源码地址"上,点击"RuoYi-Cloud 微服务版"。 跳转至Gitee页面,点击"克隆/下载",复制HTTPS链接即可。 源码地址为:https://gitee.com/y_project/RuoYi-Cloud.git 点击复制 打开IDEA,选…...
Vue2.x计算属性
1.计算属性 在Vue 插值表达式内实现一些操作其实非常便利,但如果表达式的逻辑过于复杂,会让插值过于臃肿且难以维护。这时可以考虑使用Vue的计算属性 1.1 不使用计算属性的例子 <!DOCTYPE html> <html><head><meta charset"…...
Vue中使用require.context()自动引入组件和自动生成路由的方法介绍
目录 一、自动引入组件 1、语法 2、使用 2.1、在compoents文件下随便创建index.js文件 2.2、mian.js引入该js 二、自动生成路由 1、示例: 2、使用 2.1、在router文件下随便创建autoRouter.js文件 2.2、在router文件下index.js文件中引入autoRouter.js文件…...
【炒股Zero To Hero】MACD金叉死叉到底是否有效,加上这个指标回报率增加197倍
移动平均收敛散度(MACD - Moving Average Convergence Divergence)是一种趋势跟踪动量指标,显示了证券价格的两个移动平均之间的关系。它用于识别趋势的方向和强度,属于技术分析中振荡器的一类。 MACD如何衡量股票及其趋势 有两…...
Linux网络名称空间和虚拟机有何区别
在Linux系统中,网络名称空间和虚拟机都是实现资源隔离和虚拟化的技术,但它们在设计理念、实现机制、资源消耗、使用场景等方面存在着显著的区别。本文旨在全方位、系统性地分析这两种技术的区别。🔍 1. 设计理念与实现机制 1.1. 网络名称空…...
【UE Niagara】蓝图获取粒子数据
目录 效果 步骤 一、创建粒子 二、创建蓝图接收Niagara参数 效果 步骤 一、创建粒子 1. 新建一个Niagara发射器,使用Empty模板,打开后先添加“Spawn Rate”模块,这里设置粒子生成速率为0.7 在“Initialize Particle”模块中设置粒子颜色…...
更改el-cascade默认的value和label的键值
后端返回的树结构中,label的key不是el-cascade默认的label,我需要改成对应的字段,但是一直没有成功,我也在文档中找到了说明,但是我没注意这是在props中改,导致一直不成功 这是我一开始错误的写法…...
2024邮件工单系统排行揭晓:出海必备新宠
2024年各大榜单结果纷纷出炉,一起来看看2024十大邮件工单系统最新排行吧! 2024十大邮件工单系统 1、Zoho Desk;2、FreshDesk;3、Service Desk Plus;4、Help Scout;5、Helpshift;6、HongDans&am…...
java题目17:以m行n列二维数组为参数进行方法调用,分别计算二维数组各列元素之和,返回并输出计算结果(MethodCalls17)
每日小语 伟大企业的一项特质是“利润之上的追求”。——段永平 思考 方法调用 方法调用是通过在代码中使用方法名和参数列表来实现的。 public class MethodExample {public static void main(String[] args) {// 调用方法add,并传入两个参数int sum add(3, 5…...
Python中Python-docx 包的run介绍
先对run做一个简单地介绍。每个paragraph对象都包含一个run对象的列表。举例: 这是一个简短的段落。 from docx import Document doc Document("1.docx") #上面这段话保存在1.docx中 print("这一段的run个数是:",len(doc.paragr…...
vue2升级到vue3的一些使用注意事项记录(三)
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码:…...
SwiftUI Swift 显示隐藏系统顶部状态栏
Show me the code // // TestHideSystemTopBar.swift // pandabill // // Created by 朱洪苇 on 2024/4/1. //import SwiftUIstruct TestHideSystemTopBar: View {State private var isStatusBarHidden falsevar body: some View {Button {withAnimation {self.isStatusBa…...
PowerJob 分布式任务调度简介
目录 适用场景 设计目标 PowerJob 功能全景 任务调度 工作流 分布式计算 动态容器 什么是动态容器? 使用场景 可维护性和灵活性的完美结合 实时日志&在线运维 PowerJob 系统组件 PowerJob 应用场景 PowerJob 的优势 PowerJob(原OhMyScheduler&…...
Java——数组练习
目录 一.数组转字符串 二.数组拷贝 三.求数组中元素的平均值 四.查找数组中指定元素(顺序查找) 五.查找数组中指定元素(二分查找) 六.数组排序(冒泡排序) 七.数组逆序 一.数组转字符串 代码示例: import java.util.Arrays int[] arr {1,2,3,4,5,6}; String…...
波士顿房价预测案例(python scikit-learn)---多元线性回归(多角度实验分析)
波士顿房价预测案例(python scikit-learn)—多元线性回归(多角度实验分析) 这次实验,我们主要从以下几个方面介绍: 一、相关框架介绍 二、数据集介绍 三、实验结果-优化算法对比实验,数据标准化对比实验࿰…...
在 Queue 中 poll()和 remove()有什么区别?
在Java的Queue接口中,poll()和remove()方法都用于从队列中删除并返回队列的头部元素,但是它们在队列为空时的行为有所不同。 poll()方法:当队列为空时,poll()方法会返回null,而不会抛出异常。这是它的主要特点&#x…...
实现鼠标在页面点击出现焦点及大十字星
近段时间,在完成项目进度情况显示时候,用户在操作鼠标时候,显示当鼠标所在位置对应时间如下图所示 代码实现步骤如下: 1.首先引用 jquery.1.7.js 2.再次引用raphael.js 3.然后引用graphics.js 4.最后引用mfocus.js 其中mfocu…...
如何在 7 天内掌握C++?
大家好,我是小康,今天我们来聊下如何快速学习 C 语言。 本篇文章适合于有 C 语言编程基础的小伙伴们,如果还没有学习过 C,请看这篇文章先入个门:C语言快速入门 引言: C,作为一门集面向过程和…...
FineBI概述
FineBI是一种商业智能(BI)软件,旨在帮助企业从数据中获取见解并做出更明智的业务决策。以下是FineBI的详细概述: 功能特性: 数据连接与整合:FineBI可以连接到各种数据源,包括数据库、数据仓库、…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
JVM 内存结构 详解
内存结构 运行时数据区: Java虚拟机在运行Java程序过程中管理的内存区域。 程序计数器: 线程私有,程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都依赖这个计数器完成。 每个线程都有一个程序计数…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
