好菜每回味道不同--建造者模式
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可以连接到各种数据源,包括数据库、数据仓库、…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
vulnyx Blogger writeup
信息收集 arp-scan nmap 获取userFlag 上web看看 一个默认的页面,gobuster扫一下目录 可以看到扫出的目录中得到了一个有价值的目录/wordpress,说明目标所使用的cms是wordpress,访问http://192.168.43.213/wordpress/然后查看源码能看到 这…...
Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
