设计模式之二:Builder模式
目录介绍
0.关于Builder模式案例下载
1.Builder模式介绍
2.Builder模式使用场景
3.Builder模式简单案例
- 3.1 Builder模式UML图(摘自网络)
- 3.2 在《Android源码设计模式》这本书上,介绍经典Builder模式中,包括
- 3.3 Product角色
- 3.4 Builder : 抽象Builder类,规范产品组建,一般是由子类实现具体的组建过程
- 3.5 ConcreteBuilder : 具体的Builder类
- 3.6 Director : 统一组装过程
4.Builder模式实际案例Demo
- 4.1 实际开发中,有时会遇到复杂的对象的代码
- 4.2 通过构造函数的参数形式去写一个实现类,或者通过set,get去实现
- 4.3 分析
- 4.4 将上面例子改成builder模式如下所示
- 4.5 最后运用,代码如下
- 4.6 关于线程安全问题
5.Android源码中的Builder模式实现
- 5.1 首先看看AlertDialog.Builder源代码,只是摘自部分代码
- 5.2 接下来看看build源代码
- 5.3 接下来看看show的代码
- 5.4 为什么AlertDialog要使用builder模式呢?
6.Builder模式总结
- 6.1 builder模式优点
- 6.2 builder模式缺点
7.关于其他说明
8.关于其他更多内容
1.Builder模式介绍
- 1.1 定义: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的展示。
- 1.2 Builder模式属于创建型,一步一步将一个复杂对象创建出来,允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程。
2.Builder模式使用场景
- 相同方法,不同执行顺序,产生不同事件结果
- 初始化对象特别复杂,参数众多,且很多参数都具有默认值时
3.Builder模式简单案例
- 3.2 在《Android源码设计模式》这本书上,介绍经典Builder模式中,包括
- Product : 产品抽象类
- Builder : 抽象Builder类,规范产品组建,一般是由子类实现具体的组建过程
- ConcreteBuilder : 具体的Builder类
- Director : 统一组装过程
- 3.3 Product角色
//设置抽象类
public abstract class BuilderUser {
protected String name;
protected String cardID;
protected int age;
protected String address;
public BuilderUser() {}
//设置名字
public void setName(String name) {
this.name = name;
}
//抽象方法
public abstract void setCardID();
//设置年龄
public void setAge(int age) {
this.age = age;
}
//设置地址
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "BuilderUser{" +
"name='" + name + '\'' +
", cardID='" + cardID + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
/**
* 具体的Product角色 UserCard
*/
public class UserCard extends BuilderUser {
public UserCard() {}
@Override
public void setCardID() {
cardID="10086"; //设置默认ID
}
}
- 3.4 Builder : 抽象Builder类,规范产品组建,一般是由子类实现具体的组建过程
/**
* 抽象Builder类
*/
public abstract class Builder {
public abstract void buildName(String name);
public abstract void buildCardID();
public abstract void buildAge(int age);
public abstract void buildAddress(String address);
public abstract BuilderUser create();
}
- 3.5 ConcreteBuilder : 具体的Builder类
/**
* 具体的Builder类
*/
public class AccountBuilder extends Builder{
private BuilderUser user = new UserCard();
@Override
public void buildName(String name) {
user.setName(name);
}
@Override
public void buildCardID() {
user.setCardID();
}
@Override
public void buildAge(int age) {
user.setAge(age);
}
@Override
public void buildAddress(String address) {
user.setAddress(address);
}
@Override
public BuilderUser create() {
return user;
}
}
/**
* Director角色,负责构造User
*/
public class Director {
Builder mBuilder =null;
public Director(Builder builder){
this.mBuilder =builder;
}
public void construct(String name,int age,String address){
mBuilder.buildName(name);
mBuilder.buildCardID();
mBuilder.buildAge(age);
mBuilder.buildAddress(address);
}
}
4.Builder模式实际案例Demo
- 4.1 实际开发中,有时会遇到复杂的对象的代码,比如
public class BuilderDemo {
private final String name; //必选
private final String cardID; //必选
private final int age; //可选
private final String address; //可选
private final String phone; //可选
}
- 4.2 通过构造函数的参数形式去写一个实现类,或者通过set,get去实现
BuilderDemo(String name);
BuilderDemo(String name, String cardID);
BuilderDemo(String name, String cardID,int age);
BuilderDemo(String name, String cardID,int age, String address);
BuilderDemo(String name, String cardID,int age, String address,String phone);
第一种在参数不多的情况下,是比较方便快捷的,一旦参数多了,代码可读性大大降低,并且难以维护,对调用者来说也造成一定困惑;
第二种可读性不错,也易于维护,但是这样子做对象会产生不一致的状态,当你想要传入全部参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实User对象并没有创建完成,另外,这个User对象也是可变的,不可变类所有好处都不复存在。
Builder模式同时满足上面两种情况,易于维护,并且创建方便
public class BuilderDemo {
private final String name; //必选
private final String cardID; //必选
private final int age; //可选
private final String address; //可选
private final String phone; //可选
public BuilderDemo(UserBuilder userBuilder){
this.name=userBuilder.name;
this.cardID=userBuilder.cardID;
this.age=userBuilder.age;
this.address=userBuilder.address;
this.phone=userBuilder.phone;
}
public static class UserBuilder{
private final String name;
private final String cardID;
private int age;
private String address;
private String phone;
public BuilderDemo build(){
return new BuilderDemo(this);
}
public UserBuilder(String name,String cardID){
this.name=name;
this.cardID=cardID;
}
public UserBuilder age(int age){
this.age=age;
return this;
}
public UserBuilder address(String address){
this.address=address;
return this;
}
public UserBuilder phone(String phone){
this.phone=phone;
return this;
}
}
}
new BuilderDemo.UserBuilder("yc","10086")
.age(24)
.address("beijing")
.phone("13667225184")
.build();
// 线程安全
public BuilderDemo build(){
BuilderDemo builderDemo = new BuilderDemo(this);
if(builderDemo.age > 100){
throw new IllegalStateException("Age out of range");
}
return builderDemo;
}
// 线程不安全,不要这样写
public BuilderDemo build(){
if(age > 100){
throw new IllegalStateException("Age out of range");
}
return new BuilderDemo(this);
}
5.Android源码中的Builder模式实现
- 5.1 首先看看AlertDialog.Builder源代码,只是摘自部分代码
public class AlertDialog extends AppCompatDialog implements DialogInterface {
//接收builder成员变量p中各个参数
final AlertController mAlert;
//下面是三个构造函数
protected AlertDialog(@NonNull Context context) {
this(context, 0);
}
protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
protected AlertDialog(@NonNull Context context, boolean cancelable,
@Nullable OnCancelListener cancelListener) {
this(context, 0);
setCancelable(cancelable);
setOnCancelListener(cancelListener);
}
//事件上这里调用了是mAlert的setView方法
public void setView(View view) {
mAlert.setView(view);
}
public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
int viewSpacingBottom) {
mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
}
public static class Builder {
//这个里面存放很多参数
private final AlertController.AlertParams P;
//这个是new AlertDialog.Builder(context,R.style.AppTheme)调用的方法
public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
context, resolveDialogTheme(context, themeResId)));
mTheme = themeResId;
}
//这部分代码省略很多,主要是set设置各种参数
public Builder setTitle(@Nullable CharSequence title) {
P.mTitle = title;
return this;
}
//这部分代码省略很多,主要是set设置各种参数
public Builder setView(int layoutResId) {
P.mView = null;
P.mViewLayoutResId = layoutResId;
P.mViewSpacingSpecified = false;
return this;
}
//构建dialog,传递参数
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
dialog.setOnCancelListener(P.mOnCancelListener);
dialog.setOnDismissListener(P.mOnDismissListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
return dialog;
}
//展示dialog
public AlertDialog show() {
final AlertDialog dialog = create();
dialog.show();
return dialog;
}
}
}
- 5.2 接下来看看build源代码
- 其中P是一个保存设置AlterDialog的参数类AlertParams的对象。很显然,Builder只是将AlterDialog的参数存放在一个参数对象里,做一个暂时的保存。在最后调用create方法的时候,创建一个AlterDialog对象,将暂存的参数P一次性应用到这个Dialog对象上。完成Dialog的创建。
- 当然,这只是创建AlertDialog,还没show出来。可以直接调用刚创建的AlertDialog的show()方法(实际为调用父类Dialog的show()),也可以在构建完Builder之后直接调用Builder的show方法
public AlertDialog create() {
//创建
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
//这一步很重要,下面会单独分析
P.apply(dialog.mAlert);
//设置是否可以取消,默认是true
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
dialog.setCanceledOnTouchOutside(true);
}
//设置取消监听
dialog.setOnCancelListener(P.mOnCancelListener);
//设置dialog关闭后监听
dialog.setOnDismissListener(P.mOnDismissListener);
//设置返回键监听
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
}
//返回对象,创建成功
return dialog;
}
public void show() {
//如果弹窗已经show出来了
if (mShowing) {
//如果顶级view存在则设置窗口window,并显示顶级View
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
//如果窗口还未被创建
if (!mCreated) {
dispatchOnCreate(null);
}
//获得Windowd的顶级View,并将其添加到对应的WindowManager中,然后发送show的消息
onStart();
//获取DecorView
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
//获取布局参数
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
//将mDecor添加到WindowManager中
mWindowManager.addView(mDecor, l);
mShowing = true;
//发送一个显示Dialog消息
sendShowMessage();
} finally {
}
}
在show函数中主要做了如下的事情:
(1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数
(2)然后接着调用onStart函数
(3)最后将Dialog的mDecor添加到WindowManager中(有点不明白这里,为什么要获取到最顶部的布局)
- 5.4 为什么AlertDialog要使用builder模式呢?
- AlterDialog有很多的参数,如我们上面列举的title,message,三个button及其回调,除此以外还有icon,View等属性。如果Android不采用Builder,而采用普通的构造函数来设计AlterDialog,可能要写出很多类似于如下的构造函数:
AlertDialog(String title);
AlertDialog(String message)
AlertDialog(int resId)
AlertDialog(int resId, String title, String message);
AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener);
AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener);
AlterDialog(int resId, String title, String message, String NegativeButtonString, OnClickListener listener);
AlterDialog(int resId, String title, String message, String PositiveButtonString, OnClickListener listener, String NegativeButtonString, OnClickListener listener);
....
6.Builder模式总结
- Builder模式有几个好处:
- 1. Builder的setter函数可以包含安全检查,可以确保构建过程的安全,有效。
- 2. Builder的setter函数是具名函数,有意义,相对于构造函数的一长串函数列表,更容易阅读维护。
- 3. 可以利用单个Builder对象构建多个对象,Builder的参数可以在创建对象的时候利用setter函数进行调整
- 当然Builder模式也有缺点:
- 1. 更多的代码,需要Builder这样的内部类
- 2. 增加了类创建的运行时开销 ,但是当一个类参数很多的时候,Builder模式带来的好处要远胜于其缺点。
7.关于其他说明
8.关于其他更多内容
脉脉:yc930211
邮箱:yangchong211@163.com
- Java进阶篇设计模式之二 ----- 工厂模式
前言 在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模 ...
- Java设计模式之二 ----- 工厂模式
在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模式,又 ...
- Java设计模式之二工厂模式
在上一篇中我们学习了单例模式,介绍了单例模式创建的几种方法以及最优的方法.本篇则介绍设计模式中的工厂模式,主要分为简单工厂模式.工厂方法和抽象工厂模式. 简单工厂模式 简单工厂模式是属于创建型模式,又 ...
- 设计模式之生成器(Builder)模式
意图 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以表示不同的表示. 适用性 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时. 当构造过程必须允许被构造的对象有不同的表 ...
- Java学习笔记——设计模式之二.策略模式
明确是王道 --Clean Code 先定义策略类 package cn.no2.strategy; public abstract class Strategy { //省略属性 //算法方法 pu ...
- Builder模式在Java中的应用
在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...
- Builder模式(建造者模式)
在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...
- Builder模式在Java中的应用(转)
在设计模式中对Builder模式的定义是用于构建复杂对象的一种模式,所构建的对象往往需要多步初始化或赋值才能完成.那么,在实际的开发过程中,我们哪些地方适合用到Builder模式呢?其中使用Build ...
- 设计模式(二): BUILDER生成器模式 -- 创建型模式
1.定义 将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式. 2.适用场景 1. 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式 ...
- Android开发中常见的设计模式(二)——Builder模式
了解了单例模式,接下来介绍另一个常见的模式--Builder模式. 那么什么是Builder模式呢.通过搜索,会发现大部分网上的定义都是 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建 ...
随机推荐
- Nodepad++格式化XML和JSON字符串
(一)格式化XML 1.安装XML Tools 在notepad++中点击菜单栏[插件]-[插件管理]. 在插件管理界面选择[XML Tools],点击[安装]. 2.打开XML文件 在notepad ...
- NC53370 Forsaken的三维数点
题目链接 题目 题目描述 Forsaken现在在一个三维空间中,空间中每个点都可以用 \((x,y,z)\) 表示.突然,三维空间的主人出现了,如果Forsaken想要继续在三维空间中呆下去,他就 ...
- NC15033 小G有一个大树
题目链接 题目 题目描述 小G想要把自己家院子里的橘子树搬到家门口(QAQ..就当小G是大力水手吧) 可是小G是个平衡性灰常灰常差的人,他想找到一个这个橘子树的平衡点. 怎么描述这棵树呢...就把它看 ...
- 【OpenGL ES】绘制立方体
1 前言 本文主要介绍使用 OpenGL ES 绘制立方体,读者如果对 OpenGL ES 不太熟悉,请回顾以下内容: 绘制三角形 绘制彩色三角形 绘制正方形 绘制圆形 在绘制立方体的过程中, ...
- diffstat命令
diffstat命令 diffstat命令根据diff的比较结果,统计各文件的插入.删除.修改等差异计量. 语法 diffstat [options] [files] 参数 -c: 输出的每一行都以# ...
- Spring boot内嵌tomcat日志配置
1.说明 最近项目启动有问题需要打印更详细的tomcat日志来做分析,所以用一下. 主要涉及到两类日志配置: access log tomcat log access log捕捉http请求 tomc ...
- 7zip 命令行压缩指定后缀名
接到一个需求,就是测试同学在测试软件的指定功能时,可能需要调试版本来查看输出信息,所以我们需要使用一个批处理文件来快速生成一个 debug 压缩包 7zip 给出了很多有用的命令行,我们可以使用它指定 ...
- Linux后台进程启停脚本模板
目录 启动脚本 停止脚本 在Linux上启动程序后台运行时,往往需要输入一堆复杂的命令,为了能快速编写一个完善的启动脚本,整理一个通用的启停脚本模板如下. 脚本支持从任意位置执行,不存在路径问题,启动 ...
- java个人博客
效果浏览 首页 详情页 Aboutme 后台管理 管理/添加博客 添加分类 管理员管理 友情链接 访问地址 前台地址http://localhost 后台地址:http://localhost/adm ...
- HTTP1.0/HTTP1.1/HTTP2.0的演进
HTTP1.0 短连接,每次请求都需要重新建立连接 不支持断点续传 HTTP1.1 支持长连接,同一个客户端连接可保持长连接,请求可在连接中顺序发出. 查看http请求头中有keepalive 参数 ...