NOTE

Builder Pattern

The builder pattern constructs a complex object from many smaller parts; the builder creates the parts and the director assembles them.

Software Architecture & EngineeringCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

Used to construct an object that consists of many smaller objects and has a complex construction process, such as various Builders.

The builder is responsible for construction, and the director is responsible for assembly.

1. UML

PlantUML 图表

2. Java

2.1. Client

public class Client
{
    public static void main(String[] args)
    {
        AirShip ship = new MyAirshipDirector().direct(new MyAirshipBuilder());
        System.out.println(ship);
    }
}

2.2. Product

public class AirShip
{
    private Engine engine;
    private EscapeTower tower;

    public Engine getEngine()
    {
        return engine;
    }

    public void setEngine(Engine engine)
    {
        this.engine = engine;
    }

    public EscapeTower getTower()
    {
        return tower;
    }

    public void setTower(EscapeTower tower)
    {
        this.tower = tower;
    }

    @Override
    public String toString()
    {
        return "AirShip{" + "engine=" + engine + ", tower=" + tower + '}';
    }
}
public class Engine
{
    private String name;

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    @Override
    public String toString()
    {
        return "Engine{" + "name='" + name + '\'' + '}';
    }
}
public class EscapeTower
{
    private String name;

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    @Override
    public String toString()
    {
        return "EscapeTower{" + "name='" + name + '\'' + '}';
    }
}

2.3. Builder

public interface AirshipBuilder
{
    Engine buildEngine();
    EscapeTower buildTower();
}
public class MyAirshipBuilder implements AirshipBuilder
{
    @Override
    public Engine buildEngine()
    {
        Engine engine = new Engine();
        engine.setName("my engine");
        return engine;
    }

    @Override
    public EscapeTower buildTower()
    {
        EscapeTower escapeTower = new EscapeTower();
        escapeTower.setName("my escape tower");
        return escapeTower;
    }
}

2.4. Director

public interface AirshipDirector
{
    AirShip direct(AirshipBuilder airshipBuilder);
}
public class MyAirshipDirector implements AirshipDirector
{
    @Override
    public AirShip direct(AirshipBuilder airshipBuilder)
    {
        // Build components through the builder
        Engine engine = airshipBuilder.buildEngine();
        EscapeTower escapeTower = airshipBuilder.buildTower();

        // This can be combined with the Factory pattern
        AirShip airShip = new AirShip();

        // Assemble
        airShip.setEngine(engine);
        airShip.setTower(escapeTower);

        return airShip;
    }
}

3. Golang

3.1. Client

func main() {
	fmt.Println(构建者.NewAirShipDirectorImpl().DirectAirShip(构建者.NewStrongAirShipPartsBuilder()).ToString())
	fmt.Println(构建者.NewAirShipDirectorImpl().DirectAirShip(构建者.NewWeakAirShipPartsBuilder()).ToString())
}

3.2. Product

/* Spacecraft part 1: engine */
type Engine struct {
	Name string
}

func NewEngine(name string) *Engine {
	return &Engine{Name: name}
}

/* Spacecraft part 2: escape tower */
type EscapeTower struct {
	Name string
}

func NewEscapeTower(name string) *EscapeTower {
	return &EscapeTower{Name: name}
}

/* Spacecraft */
type AirShip struct {
	Engine      *Engine
	EscapeTower *EscapeTower
}

func NewAirShip(engine *Engine, escapeTower *EscapeTower) *AirShip {
	return &AirShip{Engine: engine, EscapeTower: escapeTower}
}

func (a AirShip) ToString() string {
	return "Engine: " + a.Engine.Name + " Escape tower: " +  a.EscapeTower.Name
}

3.3. Builder

/* Builder for spacecraft parts */
type AirShipPartsBuilder interface {
	BuildEngine() *Engine
	BuildEscapeTower() *EscapeTower
}

type StrongAirShipPartsBuilder struct {
}

func NewStrongAirShipPartsBuilder() *StrongAirShipPartsBuilder {
	return &StrongAirShipPartsBuilder{}
}

func (s StrongAirShipPartsBuilder) BuildEngine() *Engine {
	return NewEngine("powerful engine")
}

func (s StrongAirShipPartsBuilder) BuildEscapeTower() *EscapeTower {
	return NewEscapeTower("powerful escape tower")
}

type WeakAirShipPartsBuilder struct {
}

func NewWeakAirShipPartsBuilder() *WeakAirShipPartsBuilder {
	return &WeakAirShipPartsBuilder{}
}

func (s WeakAirShipPartsBuilder) BuildEngine() *Engine {
	return NewEngine("weak engine")
}

func (s WeakAirShipPartsBuilder) BuildEscapeTower() *EscapeTower {
	return NewEscapeTower("weak escape tower")
}

3.4. Director

/* Assembler for spacecraft parts */
type AirShipDirector interface {
	DirectAirShip(AirShipPartsBuilder) AirShip
}

type AirShipDirectorImpl struct {
}

func NewAirShipDirectorImpl() *AirShipDirectorImpl {
	return &AirShipDirectorImpl{}
}

func (a AirShipDirectorImpl) DirectAirShip(builder AirShipPartsBuilder) *AirShip {
	return NewAirShip(builder.BuildEngine(), builder.BuildEscapeTower())
}

4. Examples

4.1. HttpBuilder

package builder

import (
	"bytes"
	"context"
	"net/http"
	"time"
)

// DefaultTimeout ...
const DefaultTimeout = 1

// HttpBuilder ...
type HttpBuilder struct {
	method      string
	url         string
	headers     map[string]string
	queryParams map[string]string
	body        string
	timeout     int
}

// NewHttpBuilder ...
func NewHttpBuilder() *HttpBuilder {
	return &HttpBuilder{
		headers:     make(map[string]string),
		queryParams: make(map[string]string),
	}
}

// Method ...
func (h *HttpBuilder) Method(method string) *HttpBuilder {
	h.method = method
	return h
}

// Url ...
func (h *HttpBuilder) Url(url string) *HttpBuilder {
	h.url = url
	return h
}

// Header ...
func (h *HttpBuilder) Header(key string, value string) *HttpBuilder {
	h.headers[key] = value
	return h
}

// QueryParam ...
func (h *HttpBuilder) QueryParam(key string, value string) *HttpBuilder {
	h.queryParams[key] = value
	return h
}

// Body ...
func (h *HttpBuilder) Body(body string) *HttpBuilder {
	h.body = body
	return h
}

// Timeout ...
func (h *HttpBuilder) Timeout(timeout int) *HttpBuilder {
	h.timeout = timeout
	return h
}

// Construct the HTTP client and request the target address
// When error is non-nil, remember to defer cleanup of the returned response and CancelFunc after use
func (h *HttpBuilder) DoRequest() (*http.Response, context.CancelFunc, error) {
	req, err := http.NewRequest(h.method, h.url,
		bytes.NewBuffer([]byte(h.body)))
	if err != nil {
		return nil, nil, err
	}

	// Assemble headers
	for k, v := range h.headers {
		req.Header.Set(k, v)
	}

	// Assemble query params
	query := req.URL.Query()
	for k, v := range h.queryParams {
		query.Add(k, v)
	}
	req.URL.RawQuery = query.Encode()

	// Set the timeout
	timeout := DefaultTimeout
	if h.timeout > 0 {
		timeout = h.timeout
	}
	ctx, cancel := context.WithTimeout(context.Background(),
		time.Duration(timeout)*time.Second)
	req = req.WithContext(ctx)

	// Construct the client and send the request
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		defer cancel()
		return nil, nil, err
	}

	return resp, cancel, nil
}
  • Usage
response, cancelFunc, err := builder.NewHttpBuilder().
	Method(http.MethodGet).
	Url(config.GlobalTencentCloudConfig.TencentCloud.AccessToken.Url).
	QueryParam(constant.ParamAppId, appId).
	QueryParam(constant.ParamSecret, secret).
	QueryParam(constant.ParamGrantType,
		config.GlobalTencentCloudConfig.TencentCloud.AccessToken.GrantType).
	QueryParam(constant.ParamVersion,
		config.GlobalTencentCloudConfig.TencentCloud.Version).
	DoRequest()

if err != nil {
	metrics.Counter(constant.MonitorGetAccessTokenNetworkFail).Incr()

	log.ErrorContextf(ctx,
		"getAccessToken: call tencent cloud get token network err. err=%v",
		err.Error())
	return "", errs.New(trpc_utils.TransferRpcErr,
		fmt.Sprintf(constant.FuncErrorPrefix, trpc_utils.RunFuncName(),
			err))
}
defer cancelFunc()
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)

4.2. UrlBuilder

func BuildUrl(ctx context.Context, path string,
	params map[string]string) string {
	urlParams := url.Values{}
	for k, v := range params {
		urlParams.Set(k, v)
	}
	return fmt.Sprintf("%s?%s", path, urlParams.Encode())
}

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub