How to Simulate Inheritance in Go with Generics, Composition, and Interfaces

This article demonstrates how to achieve class‑inheritance‑like behavior in Go by combining generics, struct embedding, and interfaces, walking through a BaseJob generic base class, a ProductJob implementation, runtime polymorphism, and a unified task manager for scalable scheduling.

Code Wrench
Code Wrench
Code Wrench
How to Simulate Inheritance in Go with Generics, Composition, and Interfaces

1. Generic BaseJob Implementation

In lib/basejob.go a generic BaseJob[T model.JobConfig] struct is defined, providing common fields such as JobConfig, JobName, Description, RetryCount and an EasyFunc placeholder.

type BaseJob[T model.JobConfig] struct {
    JobConfig   T
    JobName    string
    Description string
    RetryCount int
    EasyFunc   interface{}
}

The type parameter T is constrained to model.JobConfig, ensuring every task configuration implements a specific interface. Common fields enable reuse across tasks, and several generic methods (error handling, config retrieval, state saving) are provided:

func (job *BaseJob[T]) DoError(err error, description string) {
    job.RetryCount++
    log.Println(description, err)
    // update task status
}

func (job *BaseJob[T]) GetSyncConfig() error {
    // generic implementation to fetch task configuration
}

2. "Inheritance" via ProductJob

The file jobs/productjob/easyjob.go shows how composition is used to mimic inheritance. An EasyJob struct embeds the generic BaseJob:

type EasyJob[T model.ByIdConfig] struct {
    easylib.BaseJob[model.ByIdConfig]
}

Key design points:

Embedding BaseJob : EasyJob inherits all base functionality.

Method overriding : Specific business logic is supplied by redefining methods.

Method‑overriding example – data retrieval

func (job *EasyJob[T]) GetDataPageList() ([]interface{}, []interface{}, interface{}, error) {
    jobConfig := job.JobConfig
    maxPid := easylib.GetMaxPid()
    if jobConfig.Lastid >= maxPid {
        return nil, nil, maxPid, nil
    }
    // call business query
    products, delProducts, lastId, err := QueryProduct(jobConfig.Lastid, jobConfig.Limit)
    data := easylib.MapperFromProducts(products)
    deleteData := easylib.MapperFromProducts(delProducts)
    return data, deleteData, lastId, nil
}

Method‑overriding example – ES update

func (job *EasyJob[T]) UpdateEs(data []interface{}) error {
    ps := easylib.MapperToProducts(data)
    err := products.BulkInsert(ps)
    return err
}

3. Polymorphism with Interfaces and Runtime Binding

The Run method of BaseJob demonstrates runtime polymorphism using a type assertion on the EasyFunc interface:

func (job *BaseJob[T]) Run() {
    // ...
    data, delData, last, err := job.EasyFunc.(EasyFunc).GetDataPageList()
    // ...
    err = job.EasyFunc.(EasyFunc).UpdateEs(data)
    // ...
}

Key points:

EasyFunc interface : defines the methods a task must implement.

Runtime binding : the concrete implementation is invoked via job.EasyFunc.(EasyFunc).

4. Task Manager Design

A centralized manager in lib/jobmanager.go tracks all tasks:

type EasyJobParam struct {
    JobName     string
    Status      int
    Description string
    RetryCount  int
    Interval    time.Duration
}

var EasyJobManager = struct {
    Jobs map[string]*EasyJobParam
    Lock sync.RWMutex
}{
    Jobs: make(map[string]*EasyJobParam),
}

Conclusion

The design achieves:

Generic configuration : type parameters allow different task configs.

Code reuse : BaseJob supplies common logic, reducing duplication.

Flexible extension : new tasks only need to embed BaseJob and override key methods.

Unified management : the task manager centrally controls task states.

Although Go lacks traditional class inheritance, combining composition, interfaces, and method overriding provides powerful abstraction suitable for highly extensible systems such as a task scheduling platform.

The full source code will be published on GitHub soon, with further performance‑tuning examples to follow.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

task schedulingGenericscomposition
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.