????建造者模式是一种创建型设计模式,它可以将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。
在建造者模式中,有以下几个重要角色:
产品(Product):表示正在构建的复杂对象。它由一系列部件组成,这些部件可能是具体类或接口的实现。
抽象建造者(Abstract Builder):定义了构建产品的接口,声明了创建各个部件的抽象方法。
具体建造者(ConcreteBuilder):实现了抽象建造者接口,负责具体的产品构建,并实现各个部件的具体创建方法。
指挥者(Director):负责安排已有的部件的建造过程,和具体建造者进行交互,以便构建最终的产品。
客户端(Client):通过指挥者创建产品对象的客户端代码。
????建造者模式的核心思想是将一个复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示。通过定义不同的具体建造者,可以构建出不同的产品对象,而客户端只需关心所需的产品类型和指挥者的调用即可。这样可以增加灵活性,降低了客户端与产品对象的耦合度。
建造者模式适用于以下情况:
????理解一个设计模式,可以先从概念入手,在建造者模式概念当中,有几个说的较为模糊的词语(其实是专业词汇,只是我们不太懂罢了),再来回顾下概念“将复杂对象的构建过程与其表示分离,使得同样的构建过程可以创建不同的表示”,这里的复杂对象在前面的角色当中有说过,就是product,那什么叫构建和表示分离呢,构建是什么,表示是什么。接下来结合代码和业务说下我的理解。
PersonBuilder 抽象建造者类:定义了构建产品各个部件的抽象方法,以及保存产品对象的引用。在例子中,它定义了画人物各个部分的抽象方法,并保存了 Graphics 类型的引用。
PersonThinBuilder 具体建造者类:实现了抽象建造者类中定义的抽象方法,构建小瘦人的各个部分。在例子中,它实现了画小瘦人各个部分的方法。
PersonFatBuilder 具体建造者类:实现了抽象建造者类中定义的抽象方法,构建小胖人的各个部分。在例子中,它实现了画小胖人各个部分的方法。
PersonDirector 指挥者类:负责组织建造过程,控制具体建造者的调用顺序,最终构建出完整的产品。在例子中,它将小瘦人和小胖人的构建过程组织起来,并调用具体建造者的方法构建出完整的人物。
Client 客户端类:通过实例化具体建造者和指挥者对象,构建出不同类型的产品。在这里,创建了一个窗口,并在窗口中画出小瘦人和小胖人。
//抽象建造者
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);
}
@Override
public void buildHead() {
g.drawOval(150, 120, 30, 30);
}
@Override
public void buildBody() {
g.drawRect(160, 150, 10, 50);
}
@Override
public void buildArmLeft() {
g.drawLine(160, 150, 140, 200);
}
@Override
public void buildArmRight() {
g.drawLine(170, 150, 190, 200);
}
@Override
public void buildLegLeft() {
g.drawLine(160, 200, 145, 250);
}
@Override
public void buildLegRight() {
g.drawLine(170, 200, 185, 250);
}
}
//具体建造者2--画小胖人
public class PersonFatBuilder extends PersonBuilder{
public PersonFatBuilder(Graphics g) {
super(g);
}
@Override
public void buildHead() {
g.drawOval(250, 120, 30, 30);
}
@Override
public void buildBody() {
g.drawOval(245, 150, 40, 50);
}
@Override
public void buildArmLeft() {
g.drawLine(250, 150, 230, 200);
}
@Override
public void buildArmRight() {
g.drawLine(280, 150, 300, 200);
}
@Override
public void buildLegLeft() {
g.drawLine(260, 200, 245, 250);
}
@Override
public void buildLegRight() {
g.drawLine(280, 200, 185, 250);
}
}
//指挥者
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 Client extends JFrame {
public Client() {
setSize(400, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
}
public void paint(Graphics g) {
PersonThinBuilder gThin = new PersonThinBuilder(g);
PersonDirector pdThin = new PersonDirector(gThin);
pdThin.createPerson();
PersonFatBuilder gFat = new PersonFatBuilder(g);
PersonDirector pdFat = new PersonDirector(gFat);
pdFat.createPerson();
}
public static void main(String[] args) {
new Client().setVisible(true);//显示窗口
}
}
????在这个例子中,可以清晰地看到建造者模式的结构和流程:抽象建造者定义产品的各个部分,具体建造者实现这些部分;指挥者组织建造过程,调用具体建造者构建出完整的产品。这种方式可以使得系统更加灵活、可扩展,并且降低了组件之间的耦合度。
????本例中通过定义不同的具体建造者,可以创建出具有不同特征的产品。在示例中,通过 PersonThinBuilder 和 PersonFatBuilder 可以构建出小瘦人和小胖人两种不同类型的产品。
添加新的具体建造者并不影响已有的建造者类和指挥者类。如果需要增加一种新类型的产品,只需要创建一个新的具体建造者来实现该产品的构建过程,并在指挥者类中调用新的具体建造者即可。这样,系统的扩展性得到了保证,而无需修改已有的代码。
????指挥者类负责组织建造过程,但它不知道具体建造者的细节。具体建造者类负责构建产品的各个部分,但它不知道最终产品的组装方式。这样,指挥者和具体建造者之间的关系是松耦合的,它们之间仅通过抽象建造者进行交互。这种解耦使得指挥者类和具体建造者类可以独立变化,互不影响。
????总之,这里通过将复杂对象的构建过程分解为多个简单的部分,使得系统更加灵活、可扩展,并且减少了组件之间的依赖关系,提高了系统的可维护性和可测试性。
????根据业务了解完建造者模式之后,来看下建造者的基本代码,同时也来对比一下基本代码和上面的画小人的业务有什么不一样的地方。
????建造者模式的基本代码包含了四个角色:产品类、抽象建造者、具体建造者和指挥者。Product 类表示产品,Builder 类是抽象建造者,ConcreteBuilder1 和 ConcreteBuilder2 是具体建造者,而 Director 是指挥者。这些角色各自承担不同的责任,协作完成产品的构建过程。而在上面的画小人的代码中,并没有product,那这两段代码有什么本质上的不同吗,先看完下面的代码。
//产品
public 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();
public abstract void buildPartB();
public abstract Product getResult();
}
//具体建造者1
public class ConcreteBuilder1 extends Builder {
private Product product = new Product();
@Override
public void buildPartA() {
product.add("部件A");
}
@Override
public void buildPartB() {
product.add("部件B");
}
@Override
public Product getResult() {
return product;
}
}
//具体建造者2
public class ConcreteBuilder2 extends Builder {
private Product product = new Product();
@Override
public void buildPartA() {
product.add("部件X" );
}
@Override
public void buildPartB() {
product.add("部件Y");
}
@Override
public Product getResult() {
return product;
}
}
//指挥者
public class Director {
public void construct(Builder builder){
builder.buildPartA();
builder.buildPartB();
}
}
//客户端
public class Client {
public static void main(String[] args) {
Director director = new Director();
Builder b1 = new ConcreteBuilder1();
Builder b2 = new ConcreteBuilder2();
//指挥者用concreteBuilder1的方法来建造产品
director.construct(b1);
Product p1=b1.getResult();
p1.show();
//指挥者用concreteBuilder2的方法来建造产品
director.construct(b2);
Product p2=b2.getResult();
p2.show();
}
}
两段代码的不同:
????1、在上面画小人的例子中, PersonFatBuilder和PersonThintBuilder使用了具体的数值来定义绘图的位置和大小,这在一些情况下可能不够灵活和可维护。通常,建议使用变量或常量来代替这些硬编码的数值,以增加代码的可读性和可扩展性。
????2、画小人的例子中没有明确定义一个产品类(Product)。建造者模式通常包含一个产品类,建造者负责创建并组装产品,而指挥者负责调用具体的建造者来创建产品。但是在画小人的例子并没有一个显式的产品类。在这里可以认为PersonBuilder和其子类(如PersonFatBuilder)兼具了建造者和产品的角色。也就是说,建造者本身也承担了产品的属性和方法,用于表示要构建的对象,(但是要注意这里的产品有具体的属性值了,这与product就有了很大的区别,后面细说),但是即便没有显式的产品类,但仍然可以理解为遵循了建造者模式的核心思想:将构建逻辑与表现逻辑分离,通过不同的建造者来创建不同的对象。建造者模式不一定非要有一个显式的产品类,这取决于具体的设计需求。在一些简单的场景中,可以将产品的属性和方法直接定义在建造者中,以简化代码结构。然而,在更复杂的场景中,使用一个独立的产品类可以更好地组织和管理产品的属性和行为。
????3、在看两段代码中建造者里的接口(看指挥者里调用的builder接口也一样,这样可能更直观),看代码:
//画小人业务中builder里的接口
public void createPerson(){
pb.buildHead();
pb.buildBody();
pb.buildArmLeft();
pb.buildArmRight();
pb.buildLegLeft();
pb.buildLegRight();
}
//建造者基本代码中builder里的接口
public class Director {
public void construct(Builder builder){
builder.buildPartA();
builder.buildPartB();
}
}
????这两个看起来好像只是名字不同,其实完全不一样,画小人的业务画出来的只能是小人,因为有头有胳膊有腿,但是建造者基本代码中起名是buildPartA(),再去看两个具体的builder里添加的都是什么内容(一个是“部件A/B”,另一个是“部件X/Y”), 这里buildpartA和buildpartB并不是具体的内容,只是两个规范或者说两个标准,这里不光是标准,也定义了执行的顺序,具体创建什么东西是由product来决定的, Product当中添加的参数类型,也是可以更换的,也就是用户指定的需要创建的类型。 也就是符合Builder当中定义的规范数量,并且符合product当中 add方法接收的参数的类型的产品就可以,无论是部件a,b还是部件xy ,再举一个例子,可以创建有头有身子的小人也可以创建放盐又放醋的菜。在逻辑上就好像改变父类 Builder的类型的效果。
????4、现在就来说说要引入一个独立的产品类product有什么好处:
????前面说过概念中的复杂对象指的是product。这里product也是概念中提到的“表示”,在这里是个虚的概念,所谓虚的就是谁都可以,只要符合规范,规范指的的只要数据类型是String就可以,当然这个类型也是你自己定义的。例子中指定的是String。
????ConcreteBuilder做“构建”的,构建的是product的组件(builderPartA/B),这部分的组件是由部件(部件X/Y等)构成的,规定的是每个组件里部件(最小的颗粒)的数量和顺序。在这里也可以把builderPartA/B看成是部件的“表示”。而director相对于builder来说是构建的过程,构建的是各个组件(builderPartA/B)的顺序,以及有哪些组件(不是ConcreteBuilder定义的组件都必须调用,需要谁就调用谁)。
在建造者模式中,涉及到以下几个设计原则:
单一职责原则(Single Responsibility Principle):每个类应该只有一个引起它变化的原因。在建造者模式中,具体建造者负责构建产品的各个部件,指挥者负责组织建造过程,产品负责表示正在构建的复杂对象。通过将不同的责任分配给不同的类,保持了类的单一职责。
开闭原则(Open-Closed Principle):软件实体应对扩展开放,对修改关闭。在建造者模式中,可以通过新增具体建造者来扩展不同类型的产品,而无需修改已有的代码。这样在增加新的产品类型时,不会对现有的客户端代码产生影响。
依赖倒置原则(Dependency Inversion Principle):高层模块不应依赖于低层模块,二者都应依赖于抽象。在建造者模式中,指挥者与具体建造者之间的交互是通过抽象建造者接口进行的,而不是直接依赖于具体建造者。这样可以降低耦合性,并且使得具体建造者可以灵活替换。
迪米特法则(Law of Demeter):一个对象应该对其他对象保持最小的了解。在建造者模式中,客户端只需要与指挥者进行交互,而不直接与具体建造者交互。这样客户端只需关注所需产品和指挥者的调用,不需要了解具体建造者的细节。
????通过遵循以上设计原则,建造者模式可以使系统更加灵活、可扩展,并且降低了组件之间的耦合度。
????建造者模式适用于创建复杂对象,但如果对象结构相对简单,可以考虑使用其他创建型模式,如工厂方法模式。
????当构建过程固定且简单时,可以考虑省略抽象建造者和指挥者的角色,直接在具体建造者中进行构建。