Golang模板中的装饰器模式实现

Golang模板中的装饰器模式实现 我正在寻找一个库或示例,展示如何使用HTML模板实现装饰器模式。

以下是一个HTML表单的示例。

假设你有这样的结构:

[
  {
    "name": "firstname",
    "description": "First name",
    "type": "input",
    "req": true,
    "placeholder": "Enter your first name",
    "value": "John"
  },
  {
    "name": "lastname",
    "description": "Last name",
    "type": "input",
    "req": true,
    "placeholder": "Enter your last name",
    "value": "Doe"
  },
  {
    "name": "age",
    "description": "Age",
    "type": "input",
    "req": true,
    "placeholder": "Enter your age",
    "value": "27"
  },
  {
    "name": "descr",
    "description": "Description",
    "type": "textarea",
    "req": false,
    "placeholder": "Enter description"
  }
]

基础/默认装饰器针对不同类型:

输入框:

<label for="{{ .Name }}">{{if .Required}}*{{end}}{{ .Description }}</label>
<input type="text" name="{{ .Name }}" id="{{ .Name }}" value="{{ .Value }}" placeholder="{{ .Placeholder }}" />

文本区域:

<label for="{{ .Name }}">{{if .Required}}*{{end}}{{ .Description }}</label>
<textarea name="{{ .Name }}" id="{{ .Name }}" placeholder="{{ .Placeholder }}">{{ .Value }}</textarea>

然后是实际的装饰器:

div示例:

<div class="decorator{{if .Required}} required{{end}}">{{.}}</div>

最后是包裹整个表单的最高级装饰器:

<form action="{{.Action}}" method="{{.Method}}">
{{.}}
</form>

最终结果示例如下:

<form action="foo" method="post">
  <div class="decorator required">
    <label for="firstname">*First name</label>
    <input type="text" name="firstname" id="firstname" value="John" placeholder="Enter your first name" />
  </div>
   ....
  <div class="decorator">
    <textarea name="descr" id="descr" placeholder="Enter description"></textarea>
  </div>
</form>

装饰器链可以被更改、添加或删除。因此输出也可以像开头的JSON那样。

以下是我目前的进展:

package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"os"
)

type FormFieldDescription struct {
	Name        string `json:"name,"`
	Description string `json:"description,"`
	Type        string `json:"type,"`
	Required    bool   `json:"req"`
	Placeholder string `json:"placeholder,omitempty"`
	Value       string `json:"value,omitempty"`
}

const (
	dec_input_text = `{{ define "default.field.input" }}
	<label for="{{ .Name }}">{{if .Required}}*{{end}}{{ .Description }}</label>
	<input type="text" name="{{ .Name }}" id="{{ .Name }}" value="{{ .Value }}" placeholder="{{ .Placeholder }}" />
	{{ end }}`
	
	dec_textarea = `{{ define "default.field.input" }}
	<label for="{{ .Name }}">{{if .Required}}*{{end}}{{ .Description }}</label>
	<textarea name="{{ .Name }}" id="{{ .Name }}" placeholder="{{ .Placeholder }}">{{ .Value }}</textarea>
	{{ end }}`

	dec_field_div = `{{define "default.field.decorator.div"}}<div class="decorator{{if .Required}} required{{end}}">
	{{.}}
	</div>
	{{end}}`

)

func main() {

	// Generated dynamically from struct
	var fields []FormFieldDescription
	fields = append(fields, FormFieldDescription{
		Name:        "firstname",
		Description: "First name",
		Placeholder: "Enter your first name",
		Type:        "input",
		Required:    true,
		Value:       "John",
	})
	fields = append(fields, FormFieldDescription{
		Name:        "lastname",
		Description: "Last name",
		Placeholder: "Enter your last name",
		Type:        "input",
		Required:    true,
		Value:       "Doe",
	})
	fields = append(fields, FormFieldDescription{
		Name:        "age",
		Description: "Age",
		Placeholder: "Enter your age",
		Type:        "input",
		Required:    true,
		Value:       "27",
	})
	fields = append(fields, FormFieldDescription{
		Name:        "descr",
		Description: "Description",
		Placeholder: "Enter description",
		Type:        "textarea",
		Required:    false,
		Value:       "",
	})

	var err error

	jsonb, err := json.MarshalIndent(&fields, "", "  ")

	fmt.Printf("Generating from:\n%v\n\n", string(jsonb))

	tpl := template.New("html")
	if err != nil {
		panic(err)
	}

	// Load decorators	
	tpl.Parse(dec_input_text)
	tpl.Parse(dec_textarea)
	tpl.Parse(dec_field_div)


	decorators := []string{"default", "default.field.decorator.div" /* N decorators ... */}

	fmt.Println("Into HTML:")

	// Apply decorators
	for _, item := range fields {
		tmpWriter := os.Stdout
		for _, decorator := range decorators {
			if decorator == "default" {
				decorator = fmt.Sprintf("default.field.%v", item.Type)
			}
			
			tpl.ExecuteTemplate(tmpWriter, decorator, item)
		}
	}

	// Then apply most highest decorators, ie. <form>

}

我也不确定装饰器管道链的流向应该是像现在这样从基础到最高级装饰器,还是反过来。

我认为链也应该是模板函数的数组,例如:

func input() Template {}
func div() Template {}

decoratorChain := [input, div]

欢迎提供任何关于如何实现这一点的指导和帮助。


更多关于Golang模板中的装饰器模式实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

好的。现在得到了正确的结果:

https://play.golang.org/p/0WPwGWEL-bI

package main

import (
	"encoding/json"
	"fmt"
	"html/template"
	"bytes"
)

type FormFieldDescription struct {
	Name        string `json:"name,"`
	Description string `json:"description,"`
	Type        string `json:"type,"`
	Required    bool   `json:"req"`
	Placeholder string `json:"placeholder,omitempty"`
	Value       string `json:"value,omitempty"`
}

const (
	dec_input_text = `{{ define "default.field.input" }}
	&lt;label for="{{ .Item.Name }}"&gt;{{if .Item.Required}}*{{end}}{{ .Item.Description }}&lt;/label&gt;
	&lt;input type="text" name="{{ .Item.Name }}" id="{{ .Item.Name }}" value="{{ .Item.Value }}" placeholder="{{ .Item.Placeholder }}" /&gt;
	{{- end -}}`
	
	dec_textarea = `{{ define "default.field.textarea" }}
	&lt;label for="{{ .Item.Name }}"&gt;{{if .Item.Required}}*{{end}}{{ .Item.Description }}&lt;/label&gt;
	&lt;textarea name="{{ .Item.Name }}" id="{{ .Item.Name }}" placeholder="{{ .Item.Placeholder }}"&gt;{{ .Item.Value }}&lt;/textarea&gt;
	{{- end -}}`

	dec_field_div = `{{define "default.field.decorator.div"}}&lt;div class="decorator{{if .Item.Required}} required{{end}}"&gt;
	{{.Parent}}
	&lt;/div&gt;
	{{- end -}}`

)

type Decorator struct {
	Item FormFieldDescription
	Parent template.HTML
}

func main() {

	// Generated dynamically from struct
	var fields []FormFieldDescription
	fields = append(fields, FormFieldDescription{
		Name:        "firstname",
		Description: "First name",
		Placeholder: "Enter your first name",
		Type:        "input",
		Required:    true,
		Value:       "John",
	})
	fields = append(fields, FormFieldDescription{
		Name:        "lastname",
		Description: "Last name",
		Placeholder: "Enter your last name",
		Type:        "input",
		Required:    true,
		Value:       "Doe",
	})
	fields = append(fields, FormFieldDescription{
		Name:        "age",
		Description: "Age",
		Placeholder: "Enter your age",
		Type:        "input",
		Required:    true,
		Value:       "27",
	})
	fields = append(fields, FormFieldDescription{
		Name:        "descr",
		Description: "Description",
		Placeholder: "Enter description",
		Type:        "textarea",
		Required:    false,
		Value:       "",
	})

	var err error

	jsonb, err := json.MarshalIndent(&fields, "", "  ")

	fmt.Printf("Generating from:\n%v\n\n", string(jsonb))

	tpl := template.New("html")
	if err != nil {
		panic(err)
	}

	// Load decorators	
	tpl.Parse(dec_input_text)
	tpl.Parse(dec_textarea)
	tpl.Parse(dec_field_div)

	decorators := []string{"default", "default.field.decorator.div" /* N decorators ... */}

	fmt.Println("Into HTML:")

	// Apply decorators
	for _, item := range fields {
		var dec Decorator
		dec.Parent = ""
		var tmpWriter bytes.Buffer

		for _, decorator := range decorators {
			if decorator == "default" {
				decorator = fmt.Sprintf("default.field.%v", item.Type)
			}
			tmpWriter.Reset()
			dec.Item = item
			tpl.ExecuteTemplate(&tmpWriter, decorator, dec)
			dec.Parent = template.HTML(tmpWriter.String())
		}
		
		fmt.Println(tmpWriter.String())
	}

}

这种方法看起来正确吗?

更多关于Golang模板中的装饰器模式实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go语言中,使用HTML模板实现装饰器模式可以通过模板组合和嵌套执行来实现。以下是基于你的代码示例的改进实现:

package main

import (
    "encoding/json"
    "html/template"
    "io"
    "os"
    "strings"
)

type FormFieldDescription struct {
    Name        string `json:"name"`
    Description string `json:"description"`
    Type        string `json:"type"`
    Required    bool   `json:"req"`
    Placeholder string `json:"placeholder,omitempty"`
    Value       string `json:"value,omitempty"`
}

type FormData struct {
    Action string
    Method string
    Fields []FormFieldDescription
}

const (
    inputTemplate = `{{define "field.input"}}
<label for="{{.Name}}">{{if .Required}}*{{end}}{{.Description}}</label>
<input type="text" name="{{.Name}}" id="{{.Name}}" value="{{.Value}}" placeholder="{{.Placeholder}}" />
{{end}}`

    textareaTemplate = `{{define "field.textarea"}}
<label for="{{.Name}}">{{if .Required}}*{{end}}{{.Description}}</label>
<textarea name="{{.Name}}" id="{{.Name}}" placeholder="{{.Placeholder}}">{{.Value}}</textarea>
{{end}}`

    divDecorator = `{{define "decorator.div"}}
<div class="decorator{{if .Required}} required{{end}}">
{{.Content}}
</div>
{{end}}`

    formTemplate = `{{define "form"}}
<form action="{{.Action}}" method="{{.Method}}">
{{range .Fields}}{{.}}{{end}}
</form>
{{end}}`
)

// 装饰器执行函数
type DecoratorFunc func(io.Writer, interface{}) error

func main() {
    fields := []FormFieldDescription{
        {
            Name:        "firstname",
            Description: "First name",
            Placeholder: "Enter your first name",
            Type:        "input",
            Required:    true,
            Value:       "John",
        },
        {
            Name:        "lastname",
            Description: "Last name",
            Placeholder: "Enter your last name",
            Type:        "input",
            Required:    true,
            Value:       "Doe",
        },
        {
            Name:        "age",
            Description: "Age",
            Placeholder: "Enter your age",
            Type:        "input",
            Required:    true,
            Value:       "27",
        },
        {
            Name:        "descr",
            Description: "Description",
            Placeholder: "Enter description",
            Type:        "textarea",
            Required:    false,
            Value:       "",
        },
    }

    jsonb, _ := json.MarshalIndent(&fields, "", "  ")
    fmt.Printf("Generating from:\n%v\n\n", string(jsonb))

    // 创建模板并解析所有定义
    tpl := template.New("html")
    tpl.Parse(inputTemplate)
    tpl.Parse(textareaTemplate)
    tpl.Parse(divDecorator)
    tpl.Parse(formTemplate)

    // 定义装饰器链
    decoratorChain := []string{"field", "decorator.div"}

    fmt.Println("Into HTML:")

    // 处理每个字段
    var processedFields []string
    for _, field := range fields {
        var output strings.Builder
        
        // 执行基础字段模板
        fieldType := field.Type
        if fieldType == "input" {
            fieldType = "input"
        }
        tpl.ExecuteTemplate(&output, "field."+fieldType, field)
        
        // 应用装饰器链
        fieldContent := output.String()
        for _, decorator := range decoratorChain {
            if decorator == "field" {
                continue // 已经执行过基础字段模板
            }
            
            var decoratedOutput strings.Builder
            data := struct {
                Content  string
                Required bool
            }{
                Content:  fieldContent,
                Required: field.Required,
            }
            tpl.ExecuteTemplate(&decoratedOutput, decorator, data)
            fieldContent = decoratedOutput.String()
        }
        
        processedFields = append(processedFields, fieldContent)
    }

    // 应用表单装饰器
    formData := FormData{
        Action: "foo",
        Method: "post",
        Fields: processedFields,
    }
    
    tpl.ExecuteTemplate(os.Stdout, "form", formData)
}

更灵活的装饰器管道实现:

package main

import (
    "bytes"
    "html/template"
    "io"
)

// 装饰器类型
type Decorator struct {
    Name string
    Template *template.Template
}

// 执行装饰器链
func applyDecorators(w io.Writer, data interface{}, decorators []Decorator) error {
    var content bytes.Buffer
    
    // 执行基础模板
    if err := decorators[0].Template.Execute(&content, data); err != nil {
        return err
    }
    
    // 应用装饰器链
    currentContent := content.String()
    for i := 1; i < len(decorators); i++ {
        var decoratedContent bytes.Buffer
        decoratorData := struct {
            Content interface{}
        }{
            Content: template.HTML(currentContent),
        }
        
        if err := decorators[i].Template.Execute(&decoratedContent, decoratorData); err != nil {
            return err
        }
        currentContent = decoratedContent.String()
    }
    
    _, err := w.Write([]byte(currentContent))
    return err
}

// 使用示例
func main() {
    // 创建装饰器模板
    fieldTemplates := `
{{define "input"}}
<label for="{{.Name}}">{{if .Required}}*{{end}}{{.Description}}</label>
<input type="text" name="{{.Name}}" id="{{.Name}}" value="{{.Value}}" placeholder="{{.Placeholder}}" />
{{end}}

{{define "textarea"}}
<label for="{{.Name}}">{{if .Required}}*{{end}}{{.Description}}</label>
<textarea name="{{.Name}}" id="{{.Name}}" placeholder="{{.Placeholder}}">{{.Value}}</textarea>
{{end}}

{{define "div"}}
<div class="decorator{{if .Content.Required}} required{{end}}">
{{.Content}}
</div>
{{end}}

{{define "form"}}
<form action="{{.Action}}" method="{{.Method}}">
{{range .Fields}}{{.}}{{end}}
</form>
{{end}}`

    tpl := template.Must(template.New("decorators").Parse(fieldTemplates))
    
    // 定义装饰器链
    inputDecorators := []Decorator{
        {Name: "input", Template: tpl},
        {Name: "div", Template: tpl},
    }
    
    textareaDecorators := []Decorator{
        {Name: "textarea", Template: tpl},
        {Name: "div", Template: tpl},
    }
    
    // 应用装饰器
    field := FormFieldDescription{
        Name:        "firstname",
        Description: "First name",
        Type:        "input",
        Required:    true,
        Value:       "John",
    }
    
    var output bytes.Buffer
    applyDecorators(&output, field, inputDecorators)
    
    // 输出结果
    fmt.Println(output.String())
}

这种实现方式允许你灵活地组合不同的装饰器,并且可以轻松地添加、删除或重新排列装饰器链。装饰器的执行顺序是从基础模板开始,然后依次应用每个装饰器。

回到顶部