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();}
}
"所以说,建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。"