设计模式之美——建造者模式
.
.
.
前言:
我们在日常coding中经常遇到需要创建一个对象,有时候有些对象会非常复杂,由好几个对象组成。如果我们一个一个new出来,然后再组装难免有些不太灵活。
建造者模式就是为了解决这一痛点。
写这篇文章之前我看了很多讲解建造者模式的例子,可能是找的比较少,没找到特别透彻的例子。接下来我会尽我所能给大家说清楚,到底怎么才是建造者模式。(英文不太好,可能有拼写出错的地方,见谅)
.
.
.
1.建造者模式的使用流程:
我们以创建一个游戏角色为例:
首先这是一个角色类:
public class Human {
private String name;//名称
private int age;/年龄
private String heigth;//身高
private String country;//国籍
//相应的get(),set()方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getHeigth() {
return heigth;
}
public void setHeigth(String heigth) {
this.heigth = heigth;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
//重写的toString()方法
@Override
public String toString() {
return "Human [name=" + name + ", age=" + age + ", heigth=" + heigth + ", country=" + country + "]";
}
}
在游戏里中我们每个人的角色都是不一样的,所以我们要定义一个标准接口或者抽象类来构建这个角色。
public abstract class HumanBuilder {
abstract void buildName();//构建名字
abstract void buildAge();//构建年龄
abstract void buildHeight();//构建身高
abstract void buildCountry();//构建国籍
abstract Human buildHuman();//将构建好的角色返回
}
这样我们就有了构建一个角色的标准做法,接下来我们构建一个女性角色类,让她继承自我们上方的抽象类,并且重写相关方法:
public class MailBuilder extends HumanBuilder {
private Human human_mail;//创建一个没有“捏脸”的角色
public MailBuilder() {
human_mail = new Human();
}
//给这个角色设置名字
@Override
void buildName() {
human_mail.setName("feilong");
}
//给这个角色设置年龄
@Override
void buildAge() {
human_mail.setAge(20);
}
//给这个角色设置身高
@Override
void buildHerght() {
human_mail.setHeigth("160");
}
//给这个角色设置国籍
@Override
void buildCountry() {
human_mail.setCountry("china");
}
//将设置好的角色返回
@Override
Human buildHuman() {
// TODO Auto-generated method stub
return this.human_mail;
}
}
虽然我们有了女性角色的类,但是她现在还是一个没有捏脸的妹子(因为我们并没有调用这些设置属性的方法)。现在我们指挥系统给妹子捏脸:
public class Director {
public Human create(HumanBuilder mBuilder)
{
mBuilder.buildAge();
mBuilder.buildCountry();
mBuilder.buildHerght();
mBuilder.buildName();
return mBuilder.buildHuman();
}
}
现在我们已经将这个妹子设定成了我们喜欢的样子了。要将她变成“对象”才行。
Director director = new Director();
Human human = director.create(new MailBuilder());//这里human就是我们刚才建造出来的妹子
这就是整个建造者模式的思路。
Q&A
我当时看完很多例子之后有很多疑问:
疑问1:这样创建对象有什么好处呢?
答:
1.简便性,解耦性:我们在创建对象的时候,仅仅需要两行代码就拿到了我们需要的对象,让我们将更多的精力放在业务逻辑上,而不是创建对象的具体过程。
2.因为有相关的方法,所以我们可以精准地把控对象创建的流程。
3可扩展性:当我们需要一个男性角色的时候,只需要让他继承自HumanBuilder,实现相关方法即可。
疑问2:MailBuilder 的方法里都是具体的值,这样创建出来的对象不是写死了吗?
答:
确实存在这个问题,这也是建造者模式的缺点之一。建造者模式本身就是适合差异较小的对象的创建的。因为我们并没有特别的去关注创建对象的这个过程,而是关注于对象本身的使用。这里有两个方法解决这个问题:
方法1
.对于有较少参数需要动态化处理的时候,在我们的抽象类里,需要在动态化更改的地方加上参数:
abstract void buildName(String name);//构建名字
然后在实现类里也做相应的修改。
然后我们就会发现在Director 这个类调用create()方法的时候,会不知道参数从哪里拿。这里我们可以稍作修改:
public class Director {
HumanBuilder mBuilder;
public Director(HumanBuilder mBuilder) {
this.mBuilder = mBuilder;
}
public Human create(String name)
{
mBuilder.buildAge();
mBuilder.buildCountry();
mBuilder.buildHerght();
mBuilder.buildName(name);
return mBuilder.buildHuman();
}
}
然后我们调用的时候就可以这样写:
Human human = new Director(new MailBuilder()).create("feilong");
但这种方法其实有点破坏了建造者模式的理念,调用过程和创建过程耦合了。但灵活运用就好。
方法2:
给每一个想要的对象设计一个builder类。
2.建造者模式的另外一个是使用场景
当我们创建一个对象的时候,需要传入好多好多个参数,有时候传参传得自己都晕了。
那我们就可以掏出建造者模式啦!
看下面这个场景:
我们要组装一个电脑:
package computer;
public class Computer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Computer(String cpu, String screen, String memory, String mainboard) {
this.cpu = cpu;
this.screen = screen;
this.memory = memory;
this.mainboard = mainboard;
}
}
这里需要传入4个参数,才能创建一个对象。那如果是40个参数呢?或者是可选参数呢?
且往下看:
package computer;
public class NewComputer {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public NewComputer() {
throw new RuntimeException("can not init");
}
public NewComputer(Builder builder)
{
cpu = builder.cpu;
screen = builder.screen;
memory = builder.memory;
mainboard = builder.mainboard;
}
@Override
public String toString() {
return "NewComputer [cpu=" + cpu + ", screen=" + screen + ", memory=" + memory + ", mainboard=" + mainboard
+ "]";
}
//用内部Builder类来实现NewComputer 对象的创建。对外不暴露相关属性的set()方法。
public static final class Builder {
private String cpu;
private String screen;
private String memory;
private String mainboard;
public Builder() {}
public Builder cpu(String val)
{
cpu = val;
return this;
}
public Builder screen(String val)
{
screen = val;
return this;
}
public Builder memory(String val)
{
memory = val;
return this;
}
public Builder mainboard(String val)
{
mainboard = val;
return this;
}
public NewComputer build()
{
return new NewComputer(this);
}
}
}
这样我们在调用的时候就可以这样写:
NewComputer newComputer = new NewComputer.Builder()
.cpu("core_i9")
.memory("64G")
.mainboard("七彩虹")
.screen("samsung")
.build();
System.out.println(newComputer);
链式调用,是不是很舒服。但缺点就是这个对象经过初始化后就不可以再进行更改了。就像okhttp也是这样使用的。
总结:
建造者模式其实就是让我们忽略创建对象的过程,不去考虑其中错综复杂的参数。一切交由建造者builder来完成。但有时候其实不太适合创建灵活的对象。而链式调用中,使用起来很方便但却没法进行属性运行时动态修改。每种方法都有自己的长处和缺点,关键在于我们能不能灵活地去使用。
如果有疑问或者错误的地方可以下方留言交流,谢谢。