NOTE
Builder Pattern
The builder pattern constructs a complex object from many smaller parts; the builder creates the parts and the director assembles them.
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
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