从买电脑这件事扯到建造者设计模式

本篇文章电子版和配套代码下载地址(欢迎star):

https://github.com/hanshuaikang/design-pattern-java

建造者模式定义:

建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

优/缺点:

优点:

  • 建造者独立,易扩展。
  • 便于控制细节风险。

缺点:

  • 产品必须有共同点,范围有限制。
  • 如内部变化复杂,会有很多的建造类.

应用场景:

  • 需要生成的对象具有复杂的内部结构。
  • 建造者模工按流程一步步地创建出复杂对象.
  • 用户不知道对象的建造过程和细节就可以创建出复杂的对象「屏蔽了建造的具体细节」
  • 需要生成的对象内部属性本身相互依赖。

应用案例:

  • JAVA中的StringBuilder对象
  • 安卓开发中的 AlertDialog对象

...略。

微剧场:

阿呆昨天跟女朋友吵架了(什么?!阿呆什么时候有女朋友了?那就让他有一次吧先),于是呢今天拿着女朋友昨天给的钱打算去电脑城配一台新的电脑。

和女朋友吵架关电脑什么事儿啊?你这,,不会又想水文章吧,别慌嘛,事情的经过是这样的...

话说窗外阴风阵阵,雷电交加,阿呆此刻正在英雄联盟召唤师大峡谷刀光剑影中进行着激烈厮杀,殊不知,一双眼睛悄悄出现在了自己身后。

阿呆感到后背一阵凉意,影响操作,转身看有人站在身后,正要发怒,千钧一发之际,看到是大美,瞬间认怂。

这个,大美,你听我解释,你听我解释,我就玩了一把。

大美气势汹汹,说你多少次了,天天玩游戏,天天玩游戏,除了玩游戏你还能干点啥!嗯?

阿呆想自己此刻必然在劫难逃,这样下去怕是要鱼死网不破,看到自己破旧的电脑,新生妙计,急忙从桌子上拿起使出吃奶的力气将电脑摔个稀烂,摆出悲痛欲绝状,犹如绝配情侣却要忍痛割爱般大喊:

不玩了,不玩了就是!

大美没想到自己无意的一句嘟哝,竟然让阿呆做出如此过激的举动,看着地上被摔得稀巴烂的阿呆钟爱的电脑,心中不免心生愧疚,只得连忙安慰道:

也不是不让你玩,这样,我给你点钱你再去买个新的电脑吧,还要工作呢,记得下次可别这么玩游戏了。

阿呆摆出一脸委屈,内心疯狂窃喜,接过钱去头也不回的迈着欢快的步伐向电脑城扭秧歌般跑去。

来到电脑城,阿呆告诉老板自己想要的配置,不出一个时辰,一台崭新的地球人顶配笔记本已经交付到了阿呆手中。

这么一看这不就是工厂模式吗?有啥好讲的,取关。

且慢,我们先看代码:

且慢,看代码之前先看建造者模式之间的角色分工:

角色分工:

  • Builder 抽象的建造者,一般是接口或者抽象类。
  • ConcreateBuilder 具体的建造者。
  • Product 产品类。
  • Director 指挥者。指挥建造者去建造目标。

其中 Builder 和Director 都不是必须的,部分场景下可以省略。

代码实战:

首先,我们先新建我们的产品类:Computer

public class Computer {

private String cpu ; // cpu
   private String hardDisk ; //硬盘
   private String mainBoard ; // 主板
   private String memory ; // 内存
   private String keyboard;//键盘
   private String mouse;//鼠标
   

   /*
   getter and setter and toString
   */
   
}

声明Builder ,规范了安装电脑的步骤,至于你安装什么样的显卡,这个并不是建造者会关心的,建造者只关心对象的组装。


public interface Builder {

   void installMainBoard(String mainBoard) ;//安装主板
   void installCpu(String cpu) ;    // 安装 cpu
   void installhardDisk(String hardDisk) ;    // 安装硬盘
   void installMemory(String memory) ;    // 安装内存
   void installKeyBoard(String keyboard) ;    // 安装内存
   void installMouse(String mouse); //安装鼠标
   Computer createComputer() ;    // 组成电脑


}

声明ComputerBuilder为我们实际的建造者.

public class ComputerBuilder implements Builder{

 private Computer computer = new Computer() ;

@Override
public void installMainBoard(String mainBoard) {
computer.setMainBoard(mainBoard);

}

@Override
public void installCpu(String cpu) {
computer.setCpu(cpu);

}

@Override
public void installhardDisk(String hardDisk) {
computer.setHardDisk(hardDisk);
}

@Override
public void installMemory(String memory) {
computer.setMemory(memory);
}

@Override
public void installKeyBoard(String keyboard) {
computer.setKeyboard(keyboard);
}

@Override
public void installMouse(String mouse) {
computer.setMouse(mouse);
}

@Override
public Computer createComputer() {
return computer;
}

}

声明我们的指挥者类,我们的地球人电脑实际上就是在这里按照一定顺序装配完成的,而ComputerBuilder负责完成具体的装配工作。

public class Director {

private Builder builder ;

public Director(Builder builder){
       this.builder = builder ;
  }

  public Computer createComputer(String cpu,String hardDisk,String mainBoard,String memory,String keyboard,String mouse){
       this.builder.installMainBoard(mainBoard);
       this.builder.installCpu(cpu) ;
       this.builder.installMemory(memory);
       this.builder.installhardDisk(hardDisk);
       this.builder.installKeyBoard(keyboard);
       this.builder.installMouse(mouse);
       return this.builder.createComputer() ;
  }

}

测试类:

public class Test {

public static void main(String[] args) {
Builder builder = new ComputerBuilder();
Director director = new Director(builder);
Computer computer = director.createComputer("I9", "三星", "华硕", "三星" ,"罗技", "罗技");
System.out.println(computer);

}

}

输出:

Computer [cpu=I9, hardDisk=三星, mainBoard=华硕, memory=三星, keyboard=罗技, mouse=罗技]

我们来看一下建造者模式一个具体的执行流程:

  1. 阿呆提出自己的需求,如要什么主板..
  2. 指挥者收到需求,并把需求转交给建造者(Builder)
  3. 建造者收到需求装配对象,然后返回给指挥者
  4. 指挥者把装好的地球人顶配电脑转交给阿呆

这么一看确实是和工厂模式没什么不一样的,注意了,如果我们阿呆挑选电脑配置这个行为是随机的,即处理器任何一款都可以,同理硬盘,内存也是,这样下来就会有几乎无数种组合了,而值得我们所有人注意的是,工厂模式中所创建的对象是固定的,即不管你调用多少次这个工厂,造出来的对象都一个鸟样。这意味这,面对这种情况我们就需要有无数个工厂对应不同的电脑配置组合,而这一切在我们建造者模式面前都是弟弟。

随便提需求,我传进去,建造者模式组装就OK了。而且我不仅可以造电脑,比如我不想买你家的键盘,我可以给个null,做到对细节的控制,同时建造者和工厂模式相同的是,建造者相对于产品是独立的,即我可以很方便的增加新的建造者,而不对其他的建造者产生影响,到这里,我们开头的优点

  • 建造者独立,易扩展。
  • 便于控制细节风险。

你看,呼应上了。

同样的,阿呆可以任意提需求,但是,这个任意是只在Computer这个范围内的,阿呆不能让负责电脑的指挥者造辆车出来,所以,不管阿呆如何提需求,组装出来仍然只能是一个Computer类,而且需求越复杂,我们建造者内部逻辑就更复杂,比如要求主板上刻个字这样的操作,所以正好对应了我们上文中提到的建造者模式的两大缺点:

  • 产品必须有共同点,范围有限制。
  • 如内部变化复杂,会有很多的建造类.

当然,建造者模式和工厂模式之间的较量才刚刚开始,至于它们两个具体不同在哪?且看下面分解

建造者模式VS工厂模式:

相同点:

  • 都属于创建型设计模式注:在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。

不同点:

  • 创建对象的细粒度不同工厂模式比较粗放,造出来都是一个鸟样,而建造者模式不一样了,你甚至可以决定很多产品的细节。
  • 应用场景不同,关注点不一样工厂模式关注的是这个对象,客户端无需知道这个对象的细节,只管找相应的工厂要装好的就行了。而建造者模式则关注对象创建的细节,客户端知道一部分产品组成的细节,比如上例中电脑显卡型号,硬盘型号等。

建造者模式一般用来创建比较复杂的对象,工厂模式则用于创建相对来说比较简单的对象。

等等,不对,我记得我见到过的建造者模式不是这样的,明明就是那种,你看

   AlertDialog.Builder builder = new AlertDialog.Builder(this);

这里并没有出现什么指挥者角色啊,难道这个不是建造者模式吗?可上面你明明说是的啊。

根据我们之前的经验,实际开发中并不一定总是采用设计模式的标准实现,建造者模式也是如此,既然上文建造者模式角色那一小节有说Builder和Director并不是必须的,那必然有一种建造者模式的实现是省略了这些的,答案是有的,实际上呢,在要求不是那么严格的情况下,建造者模式有更为简洁的写法,这里我将它称之为:

建造者模式极速版!(请读者自行脑补哆啦A梦配音)

建造者模式(极速版):

代码如下:

package code.builder.fast;

public class Computer {

private String cpu ; // cpu
   private String hardDisk ; //硬盘
   private String mainBoard ; // 主板
   private String memory ; // 内存
   private String keyboard;//键盘
   private String mouse;//鼠标
   
   //定义成private,使其不能被直接创建出来
   private Computer() {}
   
   
public String getCpu() {
return cpu;
}

public void setCpu(String cpu) {
this.cpu = cpu;
}

public String getHardDisk() {
return hardDisk;
}

public void setHardDisk(String hardDisk) {
this.hardDisk = hardDisk;
}

public String getMainBoard() {
return mainBoard;
}

public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}

public String getMemory() {
return memory;
}

public void setMemory(String memory) {
this.memory = memory;
}

public String getKeyboard() {
return keyboard;
}

public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}

public String getMouse() {
return mouse;
}

public void setMouse(String mouse) {
this.mouse = mouse;
}


@Override
public String toString() {
return "Computer [cpu=" + cpu + ", hardDisk=" + hardDisk + ", mainBoard=" + mainBoard + ", memory=" + memory
+ ", keyboard=" + keyboard + ", mouse=" + mouse + "]";
}

   
//Builder 静态内部类
   public static class ComputerBuilder {
 
 
  //创建computer实例
       private Computer computer = new Computer();
       //必须的参数
       public ComputerBuilder(String cpu,String hardDisk,String mainBoard,String memory) {
      computer.setCpu(cpu);
      computer.setHardDisk(hardDisk);
      computer.setMainBoard(mainBoard);
      computer.setMemory(memory);
      }
       
       //以下是额外可附加的配置
       public ComputerBuilder bulidKeyBoard(String keyboard) {
      computer.setKeyboard(keyboard);
      return this;
      }
       
       public  ComputerBuilder buildMouse(String mouse) {
      computer.setMouse(mouse);
      return this;
      }
       
       //对象返回
       public Computer create() {
           if (computer==null){
               throw  new IllegalStateException("computer is null");
          }
           return computer;
      }

 
  }
   
   

}

测试:

public class Test {
	
	public static void main(String[] args) {
		
		Computer computer = new Computer.ComputerBuilder("i9", "三星", "华硕", "金士顿")
				.buildMouse("罗技")
				.bulidKeyBoard("雷蛇")
				.create();
		
		System.out.println(computer);
	
		
	}

}

输出:

Computer [cpu=i9, hardDisk=三星, mainBoard=华硕, memory=金士顿, keyboard=雷蛇, mouse=罗技]

当然,和工厂模式,单例模式一样,上述建造者模式极速版也只是其中的一种写法,设计模式并没有标准的写法,但是万变不离其宗,其所体现出来的思想往往是相同的,所以小伙伴们如果见到有不同写法的建造者模式也不必惊讶,最终还是要符合建造者模式定义的。

把握住变化中不变的那一部分,等于把握住了全部

总结:

本篇文章较为详细的阐述了建造者模式的思想以及具体的代码实现,建造者模式虽然不是大量使用的设计模式,但是也往往很容易就见到它的身影,掌握之后不管是对于开发还是阅读源码都会有所帮助,同样的,用大量废话尽量帮助看到这篇文章的朋友搞清楚建造者模式和工厂模式之间的差别,最后,本篇文章电子版笔记已经开源至github,需要的朋友可以前去下载,欢迎star。

我是韩数,我们下篇文章再见!

1 条评论

文艺中二理工男,技术极客程序员

1 条评论

清歌

念安

回复

发表评论