设计模式之建造者模式

建造者模式

情景模式:

需要写一个游戏程序,程序包含几个不同的角色,比如一个胖子角色,一个瘦子角色和一个比较帅气的角色。

大概代码如下:

  • 比较瘦的人
public class ThinPeople {

    private String leg;

    private String head;

    private String hand;

    private String body;


    private   ThinPeople(){

        leg="很瘦的腿";

        head="很小的头";

        hand = "很小的手";

        body ="很小的身体";
    }


    public void show(){

        System.out.println(String.format("我有%s,%s,%s,%s",leg,head,hand,body));
    }

}
  • 比较胖的人
public class FatPeople {


        private String leg;

        private String head;

        private String hand;

        private String body;


        private   FatPeople(){

            leg="很粗的腿";

            head="很胖的头";

            hand = "很胖的手";

            body ="很圆的身体";
        }

        public void show(){

            System.out.println(String.format("我有%s,%s,%s,%s",leg,head,hand,body));
        }
}

  • 比较帅气的人
public class HandsomePeople {

    private String leg;

    private String head;

    private String hand;

    private String body;


    private  HandsomePeople(){

        leg="很长的腿";

        head="很帅气的头";

        hand = "很好看的手";

        body ="很强壮的身体";
    }

    public void show(){
        System.out.println(String.format("我有%s,%s,%s,%s",leg,head,hand,body));
    }
}

从第一版代码我们可以看出,代码的重复率很高,很多代码都可以抽象出来。于是稍微修改一下

  • 新建一个抽象的People
public abstract class People {

        protected String leg;

        protected String head;

        protected String hand;

        protected String body;


        public void show(){
            System.out.println(String.format("我有%s,%s,%s,%s",leg,head,hand,body));
        }

}

  • 修改其他的类
public class ThinPeople extends People {

    public  ThinPeople(){

        leg="很瘦的腿";

        head="很小的头";

        hand = "很小的手";

        body ="很小的身体";
    }
}

public class FatPeople  extends  People{

    public FatPeople(){

        leg="很粗的腿";

        head="很胖的头";

        hand = "很胖的手";

        body ="很圆的身体";
    }
}

public class HandsomePeople extends  People {

    public HandsomePeople(){
        leg="很长的腿";

        head="很帅气的头";

        hand = "很好看的手";

        body ="很强壮的身体";
    }
}

可以看见,经过抽象以后,每个类都只有关注初始化自己的属性即可。新建一个类也不违背任何设计原则,但是这样有个问题,如果新建一个角色的时候,由于粗心忘了初始化某个属性,这样的错误只会在运行的时候才会显现出来。

因此我们可以将每个属性的设置都作为抽象方法,让每个子类都强制实现:

public abstract class People {

    protected String leg;

    protected String head;

    protected String hand;

    protected String body;


    public abstract void initLeg();

    public abstract void initHead();

    public abstract void initHand();

    public abstract void initBody();

    protected People(){

    }

    public final void show() {
        initLeg();
        initHead();
        initHand();
        initBody();
        System.out.println(String.format("我有%s,%s,%s,%s", leg, head, hand, body));
    }

}
  • 其他子类
public class HandsomePeople extends  People {

    @Override
    public void initLeg() {
        leg="很长的腿";
    }

    @Override
    public void initHead() {
        head="很帅气的头";
    }

    @Override
    public void initHand() {
        hand = "很好看的手";
    }

    @Override
    public void initBody() {
        body ="很强壮的身体";
    }
}

public class ThinPeople extends People {

    @Override
    public void initLeg() {
        leg="很瘦的腿";
    }

    @Override
    public void initHead() {
        head="很小的头";
    }

    @Override
    public void initHand() {
        hand = "很小的手";
    }

    @Override
    public void initBody() {
        body ="很小的身体";
    }

}
public class FatPeople  extends  People{

    @Override
    public void initLeg() {
        leg="很粗的腿";
    }

    @Override
    public void initHead() {
        head="很胖的头";
    }

    @Override
    public void initHand() {
        hand = "很胖的手";
    }

    @Override
    public void initBody() {
        body ="很圆的身体";
    }
}

到这里,我们的类已经完全抽象出来,不同的角色只用覆盖属性方法即可。

使用如下:

public class Main {

    public static void main(String[] args) {
        People people=new FatPeople();
        people.show();
    }
}

不过在一般大型系统中,如果People的功能比较复杂,而初始化People的过程也比较复杂的话,People会看起来非常臃肿,比如上面的People代码,我们真正使用People的地方只有show()一个方法而已,如果方法再多,People这个类就会很大,更加不好维护,这个时候,我们可以考虑将People的初始化抽离出来,单独为People创建一个专门的初始化类:

整理代码如下:

  • 创建一个People
@Getter
@Setter
public class People {

    private String leg;

    private String head;

    private String hand;

    private String body;


    public  void show() {
        System.out.println(String.format("我有%s,%s,%s,%s", leg, head, hand, body));
    }

}
  • 创建一个抽象的Builder
public abstract class PeopleBuilder {

    protected   People people=new People();

    abstract void builderHead();

    abstract void builderHand();

    abstract void builderBody();

    abstract void builderLeg();

    People getPeople(){
        return  people;
    }
}
  • 根据各个属性,创建对应的具体Builder
public class FatPeopleBuilder extends PeopleBuilder {

    @Override
    public void builderHead() {
        people.setHead("很胖的头");
    }

    @Override
    public void builderHand() {
      people.setHand("很胖的手");
    }

    @Override
    public void builderBody() {
        people.setBody("很圆的身体");
    }

    @Override
    public void builderLeg() {
        people.setLeg("很粗的腿");
    }

}
//其他的省略
  • 创建一个指导类,用于指导合成People的类
public class PeopleController {
    public People create(PeopleBuilder builder) {
        builder.builderBody();
        builder.builderHand();
        builder.builderHead();
        builder.builderLeg();
        return builder.getPeople();
    }
}
  • 使用
public class Main {

    public static void main(String[] args) {
        PeopleBuilder peopleBuilder=new FatPeopleBuilder();
        PeopleController peopleController=new PeopleController();
        People people= peopleController.create(peopleBuilder);
        people.show();
    }
}

经过整理后,我们可以发现,People除了必要的属性和Getter&Setter外,没有多余的初始化代码,而复杂的初始化过程被分离开来,这样People可以专注功能方面的代码,而Builder可以专注初始化功能。

建造者模式(Builder Pattern)

定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示

本质:将对象的复杂创建过程抽象到另外一个类中,让其他类来负责它的初始化

UML

image

可以看出来,UML图比较简单,其中Builder作为抽象的接口,不同的实现产生不同属性的产品。

优点

  • 将对象的创建与使用分离开来,使对象耦合度更低,更加易于创建相同创建过程的不同产品
  • 可以精确控制对象的创建,对象创建的过程被Director控制,不会出现缺少的初始化信息
  • 易于拓展,增加新的产品不用修改原本的代码,符合开闭原则

缺点

  • 产生了更多的小类
  • 只适用于仅仅是产品创建过程相同,但是产品的属性不同的创建,如果产品创建过程差异过大,则不适合使用该模式

应用场景

  • 某个系统中,需要产生一系列生产过程相同,但是属性不同的类
  • 某个复杂的类创建和使用过程比较复杂,需要将类的使用和创建分离开来
  • 创建某个类的过程比较复杂,需要统一将类的创建统一整理

其他

有些时候,可省略Director类,如果不需要将类的创建和使用分离开,可以简单使用上面例子的第二次优化结果,这样可以简化用户使用这个类的复杂程度。