NOTE

Template Method

The template-method pattern applies when the overall algorithm steps are fixed while some parts vary, with Java and Go implementations and a combined template/factory/strategy example.

Software Architecture & EngineeringCreated Updated 1 min readhistorical

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

1. Definition

Suitable when the overall steps of an algorithm are fixed but some parts change frequently. The changing parts can be abstracted, such as the doGet() and doPost() methods of a servlet.

2. UML

PlantUML 图表

3. Java

3.1. Client

public class Client
{
    public static void main(String[] args)
    {
        BankTemplateMethod method = new BankTemplateMethod()
        {
            @Override
            public void transact()
            {
                System.out.println("I want to make a deposit!");
            }
        };

        method.process();

        BankTemplateMethod method2 = new BankTemplateMethod()
        {
            @Override
            public void transact()
            {
                System.out.println("I want to manage my finances!");
            }
        };

        method2.process();
    }
}

3.2. Fixed Steps

public abstract class BankTemplateMethod
{
    private void takeNumber()
    {
        System.out.println("Take a number and wait in line");
    }

    public abstract void transact();// Hook method, implemented in a concrete subclass

    private void evaluate()
    {
        System.out.println("Provide feedback and a rating");
    }

    // Fixed steps
    public final void process()
    {
        this.takeNumber();
        this.transact();
        this.evaluate();
    }
}

4. Golang

4.1. First Approach

4.1.1. Client

func main() {
	var downloader 模板方法.IDownloader
	downloader = 模板方法.NewFTPDownloader()
	downloader.Download("https://www.baidu.com/")

	downloader = 模板方法.NewHTTPDownloader()
	downloader.Download("https://www.baidu.com/")
}

4.1.2. Template Method

/* Downloader */
type IDownloader interface {
	Download(url string)
}

/* Steps inside the template method */
type ISteps interface {
	getContentFromUrl()
	saveContentToDisk()
}

type template struct {
	ISteps
	url string
}

func newTemplate(iSteps ISteps) *template {
	return &template{ISteps: iSteps}
}

/* Fixed steps of the template method */
func (t *template) Download(url string) {
	t.url = url
	fmt.Print("Preparing download
")
	t.ISteps.getContentFromUrl()
	t.ISteps.saveContentToDisk()
	fmt.Print("Download complete
")
	fmt.Println()
}

4.1.3. Concrete Template-Method Subclasses

/* FTP downloader implements the template interface */
type FTPDownloader struct {
	*template
}

func NewFTPDownloader() *FTPDownloader {
	downloader := &FTPDownloader{}
	downloader.template = newTemplate(downloader)
	return downloader
}

func (F *FTPDownloader) getContentFromUrl() {
	fmt.Printf("FTP gets data from URL [%s]
", F.url)
}

func (F *FTPDownloader) saveContentToDisk() {
	fmt.Printf("FTP stores the data on disk
")
}

/* HTTP downloader implements the template interface */
type HTTPDownloader struct {
	*template
}

func NewHTTPDownloader() *HTTPDownloader {
	downloader := &HTTPDownloader{}
	downloader.template = newTemplate(downloader)
	return downloader
}

func (H *HTTPDownloader) getContentFromUrl() {
	fmt.Printf("HTTP gets data from URL [%s]
", H.url)
}

func (H *HTTPDownloader) saveContentToDisk() {
	fmt.Printf("HTTP stores the data on disk
")
}

4.2. Second Approach

package 模板方法

import (
	"fmt"
	"testing"
)

// Interface
type IGetInfo interface {
	GetName() string
	GetAge() int
}

// Abstract implementation class
type BaseGetInfo struct {
}

func (b *BaseGetInfo) GetName() string {
	return "BaseGetInfo-GetName"
}

func (b *BaseGetInfo) GetAge() int {
	return 0
}

// Concrete class 1
type GetInfo1 struct {
	*BaseGetInfo
}

func (a *GetInfo1) GetAge() int {
	return 1
}

// Concrete class 2
type GetInfo2 struct {
	*BaseGetInfo
}

func (a *GetInfo2) GetName() string {
	return "GetInfo2-GetName"
}

// Template method
func TemplateMethod(handler IGetInfo) {
	fmt.Println("1111")
	fmt.Println(handler.GetName(), handler.GetAge())
	fmt.Println("2222")
}

func TestHandle(t *testing.T) {
	TemplateMethod(&GetInfo1{})
	TemplateMethod(&GetInfo2{})
}

5. Example

Template method + simple factory + strategy pattern.

  • pb.go
package pb

type SwitchRoomReq struct {
}

type SwitchRoomRsp struct {
}
  • abstract_template.go
package template

import (
	"context"
	"fmt"
	"test/template/pb"
)

type abstractFetchDataService struct {
	IFetchDataSteps
	req       *pb.SwitchRoomReq
	rsp       *pb.SwitchRoomRsp
	basicInfo interface{}
	extraInfo interface{}
}

func (t *abstractFetchDataService) checkParam(ctx context.Context) error {
	fmt.Println("default checkParam")
	return nil
}

func (t *abstractFetchDataService) getBasicInfo(ctx context.Context) error {
	fmt.Println("default getBasicInfo")
	return nil
}

func (t *abstractFetchDataService) getExtraInfo(ctx context.Context) error {
	fmt.Println("default getExtraInfo")
	return nil
}

func newTemplate(iSteps IFetchDataSteps) *abstractFetchDataService {
	return &abstractFetchDataService{IFetchDataSteps: iSteps}
}

func (t *abstractFetchDataService) HandleRequest(ctx context.Context, req *pb.SwitchRoomReq, rsp *pb.SwitchRoomRsp) error {
	t.req = req
	t.rsp = rsp
	if err := t.IFetchDataSteps.checkParam(ctx); err != nil {
		return err
	}
	if err := t.IFetchDataSteps.getBasicInfo(ctx); err != nil {
		return err
	}
	if err := t.IFetchDataSteps.getExtraInfo(ctx); err != nil {
		return err
	}
	return nil
}
  • content_read_template_impl.go
package template

import (
	"context"
	"fmt"
)

const TypeContentReadService = 2

var contentReadServiceConstructorFunc fetchDataServiceConstructor = func() IFetchDataService {
	return NewFetchDataFromContentReadService()
}

func init() {
	GlobalServiceFactory.RegisterService(TypeContentReadService, contentReadServiceConstructorFunc)
}

type FetchDataFromContentReadService struct {
	*abstractFetchDataService
}

func (f *FetchDataFromContentReadService) checkParam(ctx context.Context) error {
	fmt.Println("checkParam")
	return f.abstractFetchDataService.checkParam(ctx)
}

func (f *FetchDataFromContentReadService) getBasicInfo(ctx context.Context) error {
	fmt.Println("getBasicInfo")
	return f.abstractFetchDataService.getBasicInfo(ctx)
}

func NewFetchDataFromContentReadService() *FetchDataFromContentReadService {
	service := &FetchDataFromContentReadService{}
	service.abstractFetchDataService = newTemplate(service)
	return service
}
  • factory.go
package template

import "fmt"

var GlobalServiceFactory = newServiceFactory()

type fetchDataServiceConstructor = func() IFetchDataService

type serviceFactory struct {
	serviceMap map[int]fetchDataServiceConstructor
}

func newServiceFactory() *serviceFactory {
	return &serviceFactory{serviceMap: make(map[int]fetchDataServiceConstructor)}
}

func (s *serviceFactory) GetServiceByType(serviceType int) (IFetchDataService, error) {
	serviceConstructor, ok := s.serviceMap[serviceType]
	if ok {
		return serviceConstructor(), nil
	}
	err := fmt.Errorf("GetServiceByType: serviceType %v invalid", serviceType)
	return nil, err
}

func (s *serviceFactory) RegisterService(serviceType int, serviceConstructor fetchDataServiceConstructor) {
	s.serviceMap[serviceType] = serviceConstructor
}
  • itemplate.go
package template

import (
	"context"
	"test/template/pb"
)

type IFetchDataService interface {
	HandleRequest(ctx context.Context, req *pb.SwitchRoomReq, rsp *pb.SwitchRoomRsp) error
}

type IFetchDataSteps interface {
	checkParam(ctx context.Context) error
	getBasicInfo(ctx context.Context) error
	getExtraInfo(ctx context.Context) error
}
  • template_test.go
package template

import (
	"context"
	"testing"
)

func TestTemplate(t *testing.T) {
	service, _ := GlobalServiceFactory.GetServiceByType(TypeContentReadService)
	service.HandleRequest(context.Background(), nil, nil)
}
// Output
checkParam
default checkParam
getBasicInfo
default getBasicInfo
default getExtraInfo

6. References

Discussion

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