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.
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
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
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub