Golang - Template Method Pattern

Golang - Template Method Pattern

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass but lets subclasses override specific steps of the algorithm without changing its structure. In other words, the Template Method Pattern provides a template for performing an operation but allows the subclasses to modify certain steps of the operation without changing its overall structure.

The Template Method Pattern is a part of the "Gang of Four" design patterns, which were first introduced in their book "Design Patterns: Elements of Reusable Object-Oriented Software". The "Gang of Four" includes Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. These patterns are widely used in software development to solve commonly occurring problems in object-oriented programming.

Example

Let's consider a real-world example of how the Template Method Pattern can be used in Golang programming. Suppose we want to create a program that reads data from different sources and then processes it. The sources can be files, databases, or even remote APIs. We can create an abstract class that defines the template for reading and processing data.

type DataReaderProcessor interface {
    ReadData() ([]byte, error)
    ProcessData(data []byte) error
    GetDataSourceName() string
}

type DataReaderProcessorTemplate struct {
    dataSource DataReaderProcessor
}

func (p *DataReaderProcessorTemplate) ReadAndProcessData() error {
    data, err := p.dataSource.ReadData()
    if err != nil {
        return err
    }
    err = p.dataSource.ProcessData(data)
    if err != nil {
        return err
    }
    return nil
}

func (p *DataReaderProcessorTemplate) GetDataSourceName() string {
    return p.dataSource.GetDataSourceName()
}

In the above code, we have defined an interface DataReaderProcessor that specifies the methods required for reading and processing data. We then define a DataReaderProcessorTemplate struct that implements the DataReaderProcessor interface. The DataReaderProcessorTemplate struct provides a template for reading and processing data.

The ReadAndProcessData method reads data from the data source and processes it by calling the ProcessData method. The GetDataSourceName method returns the name of the data source.

Now we can create structs that implement the DataReaderProcessor interface and implement the ReadData and ProcessData methods according to their specific data sources. For example, we can define a FileReaderProcessor that reads data from a file and a DatabaseReaderProcessor that reads data from a database.

type FileReaderProcessor struct {
    DataReaderProcessorTemplate
    fileName string
}

func (p *FileReaderProcessor) ReadData() ([]byte, error) {
    data, err := ioutil.ReadFile(p.fileName)
    if err != nil {
        return nil, err
    }
    return data, nil
}

func (p *FileReaderProcessor) ProcessData(data []byte) error {
    // process data
    return nil
}

func (p *FileReaderProcessor) GetDataSourceName() string {
    return p.fileName
}

type DatabaseReaderProcessor struct {
    DataReaderProcessorTemplate
    db *sql.DB
}

func (p *DatabaseReaderProcessor) ReadData() ([]byte, error) {
    // read data from database
    return nil, nil
}

func (p *DatabaseReaderProcessor) ProcessData(data []byte) error {
    // process data
    return nil
}

func (p *DatabaseReaderProcessor) GetDataSourceName() string {
    return "Database"
}

In the above code, we have defined FileReaderProcessor and DatabaseReaderProcessor structs that implement the DataReaderProcessor interface. These classes implement the ReadData and ProcessData methods according to their data sources.

Now we can use these structs to read and process data from different sources. For example, we can create a FileReaderProcessor object and call its ReadAndProcessData method to read and process data from a file.

func main() {
    processor := &FileReaderProcessor{
        DataReaderProcessorTemplate: DataReaderProcessorTemplate{
            dataSource: &FileReaderProcessor{
                fileName: "/path/to/file",
            },
        },
    }
    err := processor.ReadAndProcessData()
    if err != nil {
        log.Fatalf("Error: %v", err)
    }
}

In the above code, we have created a FileReaderProcessor object and passed it to the DataReaderProcessorTemplate struct. We then call the ReadAndProcessData method to read and process data from the file.

Conclusion

The Template Method Pattern provides a way to define the skeleton of an algorithm in a superclass but allows subclasses to modify specific steps of the algorithm without changing its overall structure. This pattern is useful in situations where we want to define a common algorithm but allow subclasses to customize certain steps of the algorithm. In Golang, we can use the Template Method Pattern to create a template for reading and processing data from different sources. The pattern allows us to define a common algorithm for reading and processing data but allows subclasses to customize the specific data source.


References

Did you find this article valuable?

Support Matthias Bruns by becoming a sponsor. Any amount is appreciated!