好菜每回味道不同--建造者模式

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和产品B
        Product p1 = b1.getResult();
        p1.show();
        
        //指挥者用ConcreteBuilder2的方法来建造产品
        director.construct(b2); //创建的是产品X和产品Y
        Product 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();      //建造部件A
    public abstract void buildPartB();      //建造部件B
    public 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();
    }
}

"所以说,建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式。"

相关推荐
Coder码匠12 小时前
Dockerfile 优化实践:从 400MB 到 80MB
java·spring boot
reddingtons19 小时前
【游戏宣发】PS “生成式扩展”流,30秒无损适配全渠道KV
游戏·设计模式·新媒体运营·prompt·aigc·教育电商·游戏美术
李慕婉学姐19 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆21 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin21 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200521 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉1 天前
JavaBean相关补充
java·开发语言
提笔忘字的帝国1 天前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882481 天前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈1 天前
两天开发完成智能体平台
java·spring·go