“小菜,讲了半天,肚子饿得厉害,走,去吃夜宵去。”大鸟摸着肚子说道。
“你请客?”
“我教了你这么多,你也不打算报答一下,还要我请客?搞没搞错。”
“啊,说得也是,这样吧,我请客,你埋单,嘻嘻!”小菜傻笑道。“我身上没带钱。”
“你这个菜穷酸,行了,我来埋单吧。”大鸟等不及了,拿起外套就往外走。
“等等我,我把电脑关一下……”
小区门口大排档前。
“老板,来两份炒饭。”大鸟对大排档的老板说.
“大鸟太小气,就请吃炒饭呀,”小菜埋怨道,“我要吃炒面。”
“再说废话,你请客了哦!”大鸟瞪了小菜一眼,接着对老板说,“那就一份炒饭一份炒面吧。”
十分钟后。
“这炒饭炒得什么玩意儿,味道不够,鸡蛋那么少,估计只放了半个。”大鸟吃得很不爽,抱怨道。
“炒面好好吃哦,真香。”小菜故意嘟嚷着,把面吸得嗦嗦直响。
“让我尝尝,”大鸟强拉过小菜的盘子,吃了一口,“好像味道是不错,你小子运气好。老板。再给我来盘炒面!”
五分钟后。
“炒面来一了一,客官,请慢用。”老板仿佛古时的小二一般来了一句。
“啊,老板,这炒面没放盐……”大鸟叫道。
在回去的路上,大鸟感概道:“小菜,你知道为什么麦当劳、肯德基这些不过百年的洋快餐能在有千年饮食文化的中国发展得这么好吗?”
“他们比较规范吧,味道好吃,而且还不出错,不会出现像你今天这样,蛋炒饭不好吃,炒面干脆不放盐的情况。”
“你说得没错,麦当劳、肯德基的汉堡,不管在哪家店里吃,什么时间去吃,至少在中国,味道基本都是一样的。而我们国家。比如那道‘鱼香肉丝’。几乎是所有大小中餐饭店都有的一道菜,但却可以吃出上万种口味来,这是为什么?”
“厨师不一样呀,每个人做法不同的。”
“是的,因为厨师不同,他们学习厨艺方法不同,有人是科班出身,有人是师傅带徒弟,有人是照书下料,还有人是自我原创,哈,这样你说同样的菜名‘鱼香肉丝’,味道会一样吗?”
“还不只是这些,同一个厨师,不同时间烧出来同样的菜也不一样的,盐多盐少,炒的火候时间的长短,都是不一样的。”
“说得好,那你仔细想想,麦当劳、肯德基比我们很多中式快餐成功的原因是什么?”
“就感觉他们比较规范,具体原因也说不上来。”
“为什么你的炒面好吃,而我再要的炒面却没有放盐?这好吃不好吃由谁决定?”
“当然是烧菜的人,他感觉好,就是一盘好面。要是心情不好。或者粗心大意,就是一盘垃圾。”小菜肯定地说。
“哈,说得没错,今天我就吃了两盘垃圾,其实这里面最关键的就在于我们是吃得爽还是吃得难受都要依赖于厨师。你再想想我们设计模式的原则?”
“啊,你的意思是依赖倒转原则?抽象不应该依赖细节,细节应该依赖子抽象,由于我们要吃的菜都依赖于厨师这样的细节,所以我们就很被动。”
“好,那再想想,老麦老肯他们的产品,味道是由什么决定的?”
“我知道,那是由他们的工作流程决定的,由于他们制定了非常规范的工作流程,原料放多少,加热几分钟。都有严格规定,估计放多少盐都是用克来计量的。而这个工作流程是在所有的门店都必须要遵照执行的,所以我们吃到的东西不管在哪在什么时候味道都一样。这里我们要吃的食物都依赖工作流程。不过工作流程好像还是细节呀。”
“对,工作流程也是细节,我们去快餐店消费,我们用不用关心他们的工作流程?当然是不用,我们更关心的是是否好吃。你想如果老肯发现鸡翅烤得有些焦,他们会调整具体的工作流程中的烧烤时间,如果新加一种汉堡,做法都相同,只是配料不相同,工作流程是不变的,只是加了一种具体产品而已,这里工作流程怎么样?”
“对,这里工作流程可以是一种抽象的流程,具体放什么配料、烤多长时间等细节依赖于这个抽象。”
“给你出个题目,看看你能不能真正体会到流程的抽象。我的要求是你用程序画一个小人,这在游戏程序里是灰常常见的,现在简单一点,要求是小人要有头、身体、两手、两脚就可以了。”
“废话,人还会有多手多脚啊,那不成了蜈蚣或螃蟹。这程序不难啊,我回去就写给你看。”
十分钟后。
“大鸟,程序写出来了,简单了点,但功能实现了。”
public class DrawPerson extends Frame { public void paint(Graphics g) { Color c = g.getColor(); g.setColor(Color.BLACK); g.fillOval(50, 50, 30, 30);// 头 g.fillRect(60, 80, 10, 50);// 身体 g.drawLine(60, 80, 40, 130);// 左手 g.drawLine(70, 80, 90, 130);// 左手 g.drawLine(60, 130, 45, 180);// 左腿 g.drawLine(70, 130, 85, 180);// 右腿 g.setColor(c); } public void lauchFrame() { this.setLocation(400, 300); this.setSize(800, 600); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); this.setBackground(Color.WHITE); setVisible(true); } public static void main(String[] args) { DrawPerson person = new DrawPerson(); person.lauchFrame(); } }
“写的很快嘛,那么我现在想要你再画一个身体比较胖的小人呢。”
“那不难啊,我马上就做好。”
Color c = g.getColor(); g.setColor(Color.BLACK); g.fillOval(50, 50, 30, 30);//头 g.fillOval(45, 80, 40, 50);//身体 g.drawLine(60, 80, 40, 130);//左手 g.drawLine(70, 80, 90, 130);//左手 g.drawLine(60, 130, 45, 180);//左腿 g.setColor(c);
“啊,等等,我画了一条腿。”
g.drawLine(70, 130, 85, 180);//右腿
“哈,这就和我们刚才去吃炒面一样,老板忘记了放盐,让本是非常美味的夜宵变得无趣。如果是让你开发一个游戏程序,里面的健全人物却少了一条腿,那怎么能行?”
“是啊,画人的时候,头身手脚是必不可少的,不管什么人物,开发时是不能少的。”
“你现在的代码全写在窗体里面,我要是需要在别的地方用这些画小人的程序怎么办?”
“嘿,你的意思是分离,这不难办,我建两个类,一个瘦人的类,一个是胖人的类,不管是谁都可以调用它了。”
//画瘦人的类 public class PersonThinBuilder { private Graphics g; public PersonThinBuilder(Graphics g) { this.g = g; } public void build() { Color c = g.getColor(); g.setColor(Color.BLACK); g.fillOval(50, 50, 30, 30);// 头 g.fillRect(60, 80, 10, 50);// 身体 g.drawLine(60, 80, 40, 130);// 左手 g.drawLine(70, 80, 90, 130);// 左手 g.drawLine(60, 130, 45, 180);// 左腿 g.drawLine(70, 130, 85, 180);// 右腿 g.setColor(c); } } //画胖人的类 public class PersonFatBuilder { private Graphics g; public PersonFatBuilder(Graphics g) { this.g = g; } public void build() { Color c = g.getColor(); g.setColor(Color.BLACK); g.fillOval(50, 50, 30, 30);// 头 g.fillRect(60, 80, 10, 50);// 身体 g.drawLine(60, 80, 40, 130);// 左手 g.drawLine(70, 80, 90, 130);// 左手 g.drawLine(60, 130, 45, 180);// 左腿 g.drawLine(70, 130, 85, 180);// 右腿 g.setColor(c); } } //客户端代码 public class Main extends Frame { public void paint(Graphics g) { PersonThinBuilder p = new PersonThinBuilder(g); p.build(); } public void lauchFrame() { this.setLocation(400, 300); this.setSize(800, 600); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); this.setBackground(Color.WHITE); setVisible(true); } public static void main(String[] args) { Main drawPerson = new Main(); drawPerson.lauchFrame(); } }
“你这样写确实达到了可以复用这两个画小人程序的目的。但炒面忘记放盐的问题依然没有解决啊。比如我现在需要你加一个高个小人,你会不会因为编程不注意,又让他缺胳膊少腿呢?”
“啊啊,最好的办法是规定,凡是建造小人,都必须要有头和身体,以及两手两脚。”
“你仔细分析就会发现,这里建造小人的过程是稳定的,都需要头身手脚,而具体建造的细节是不同的,有胖有瘦有高有低,但对于用户而言,我才不需要管这些,我只想告诉你,我需要一个胖小人来游戏,于是你就建造了一个给我就行了。如果你需要将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图时,我们需要应用一个设计模式,建造者模式(Builder),又叫生成器模式。建造者模式可以将一个产品的内部表象与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同内部表象的产品对象。如果我们用了建造者模式,那么用户就只需要指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。”
建造者模式(Builder),将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
“那怎样用建造者模式呢?”
“一步一步来,首先我们要画一个人,都要画什么?”
“头、身体、左手、右手、左脚、右脚。”
“对的,所以我们先定义一个抽象的建造人的类,把这个过程给稳定住,不让任何人遗忘当中的任何一步。”
//画人的抽象类 public 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(); } //瘦子类继承人的抽象类 public class PersonThinBuilder extends PersonBuilder { public PersonThinBuilder(Graphics g) { super(g); } public void buildArmLeft() { g.drawLine(60, 80, 40, 130);// 左手 } public void buildArmRight() { g.drawLine(70, 80, 90, 130);// 右手 } public void buildBody() { g.fillRect(60, 80, 10, 50);// 身体 } public void buildHead() { g.fillOval(50, 50, 30, 30);// 头 } public void buildLegLeft() { g.drawLine(60, 130, 45, 180);// 左腿 } public void buildLegRight() { g.drawLine(70, 130, 85, 180);// 右腿 } } //胖子类继承人的抽象类 public class PersonFatBuilder extends PersonBuilder { public PersonFatBuilder(Graphics g) { super(g); } public void buildArmLeft() { g.drawLine(60, 80, 40, 130);// 左手 } public void buildArmRight() { g.drawLine(70, 80, 90, 130);// 右手 } public void buildBody() { g.fillRect(60, 80, 10, 50);// 身体 } public void buildHead() { g.fillOval(50, 50, 30, 30);// 头 } public void buildLegLeft() { g.drawLine(60, 130, 45, 180);// 左腿 } public void buildLegRight() { g.drawLine(70, 130, 85, 180);// 右腿 } } //胖人或高个子其实都是用类似的代码去实现这个类就可以了,在客户端调用时,还是需要知道头身手脚这些方法,所以还缺少建造者模式中一个很重要的类,指挥者(Director),用它来控制建造过程,也用它来隔离用户与建造过程的关联。 public 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(); } } //客户端代码 public class Main extends Frame { public void paint(Graphics g) { Color c = g.getColor(); g.setColor(Color.BLACK); PersonThinBuilder ptb = new PersonThinBuilder(g); PersonDirector pdThin = new PersonDirector(ptb); pdThin.createPerson(); g.setColor(c); } public void lauchFrame() { this.setLocation(400, 300); this.setSize(800, 600); this.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); this.setBackground(Color.WHITE); setVisible(true); } public static void main(String[] args) { Main drawPerson = new Main(); drawPerson.lauchFrame(); } }
“这样,通过调用PersonDirector类就可以根据用户的选择一步一步地建造小人了,而建造的过程是在指挥者里已经提前制定好了的,用户就不需要知道了,而且,由于这个过程每一步都是一定要做的,那就不会让少画了一只手,少画了一条腿的问题出现了。”
代码结构图如下
“试想一下,我如果需要增加一个高个子和矮个子的小人,我们应该怎样做呢?”
“加两个类,一个高个子类和一个矮个子类,让它们都去继承PersonBuilder,然后客户端调用就可以了。但我有个问题,如果我需要细化一些,比如人的五官,手的上臂、前臂和手掌,大腿小腿这些的,如何办呢?”
“问的8错8错啊,这就需要权衡了,如果这些细节是每个具体的小人都需要建造的,那就应该加进去的说,反之,就没有必要的。其实建造者模式是逐步建造产品的,所以建造者的Builder类里的那些建造方法必须要足够普遍,以便为各种类型的具体建造者构造。”
“来,我们看看建造者模式的结构。”
建造者模式(Builder)结构图
“现在你看这张图就不会感觉陌生了。来总结一下,Builder是什么?”
“是一个建造小人各个部分的抽象类。”
“概括的说,是为创建一个Product对象的各个部件指定的抽象接口。ConcreteBuilder是什么呢?”
“具体的小人,具体实现如何画出小人的头身手脚各个部分。”
“对的,它是具体建造者,实现Builder接口,构造和装配各个部件。Product当然就是那些具体的小人,产品角色了,Director是什么?”
“指挥者,用来根据用户的需求构建小人对象。”
“嗯,它是构建一个使用Builder接口的对象。”
“那都是什么时候需要使用建造者模式呢?”
“主要用于创建一些复杂对象,这些对象内部构建间的建造顺序非常稳定的情况下,但对象内部的构建通常面临着复杂变化。”
“哦,是不是建造者模式的好处就是使得建造代码与表示代码分离,由于建造者隐藏了该产品是如何组装的,所以若需要改变一个产品的内部表示,只需要再定义一个具体的建造者就可以了。”
“来来来,我们试着把建造者模式的基本代码推演一下,以便于有一个更宏观的认识。”
//Product类—产品类,由多个部件组成 public class Product { List<String> parts = new ArrayList<String>(); public void add(String part) { parts.add(part); } public void show() { System.out.println("产品 创建----"); for (String part : parts) { System.out.println(part); } } } //Builder类—抽象建造者类,确定产品由两个部件PartA和PartB组成,并声明一个得到产品建造后结果的方法getResult public abstract class Builder { public abstract void buildPartA(); public abstract void buildPartB(); public abstract Product getResult(); } //ConcreteBuilder1类—具体建造者类 public 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; } } // ConcreteBuilder2类—具体建造者类 public 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; } } //Director类—指挥类 public class Director { public void construct(Builder builder) { builder.buildPartA(); builder.buildPartB(); } } //客户端代码 public class Main { public static void main(String[] args) { Builder builder1 = new ConcreteBuilder1(); Builder builder2 = new ConcreteBuilder2(); Director director = new Director(); director.construct(builder1); director.construct(builder2); Product product1 = builder1.getResult(); Product product2 = builder2.getResult(); product1.show(); product2.show(); } }
“所以说,建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。”
“如果今天的大排档做炒面的老板知道建造者模式,他就明白,盐是一定要放的,不然,编译就通不过。”
“什么啊,不然,钱就赚不到了,而且还大大丧失我们对他厨艺的信任。看来,各行各业都应该要懂模式啊。”