🧠 Go for OOP Developers

A quick guide for developers transitioning from Object-Oriented languages (like Java, C#, or C++) to Go.

If you come from an Object-Oriented Programming (OOP) background, Go might initially feel minimalistic or even “too simple”. You may look for familiar constructs like classes, inheritance, annotations, generics, or frameworks, and not find them the same way.

But Go is designed with clarity, simplicity, and explicitness in mind — trading some abstraction for ease of reasoning, concurrency, and performance.

This guide highlights the main paradigm shifts you’ll encounter and provides pro-tips for writing idiomatic Go code.

🔄 OOP vs Go: Conceptual Shifts

Classes

Java (OOP)

  • Blueprint that encapsulates data + behavior
  • Example:
  class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
  }

Go (Golang)

  • No classes. Use struct to model data and functions with receivers to attach behavior
  • Example:
  type Person struct {
    Name string
  }

  func (p *Person) GetName() string {
    return p.Name
  } 

Inheritance

Java (OOP)

  • Core mechanism for reuse and polymorphism
  • Example:
  class Employee extends Person {
    private String role;
  }

Go (Golang)

  • No inheritance. Use composition and interfaces instead
  • Example:
type Employee struct {
  Person
  Role string
}

Interfaces

Java (OOP)

  • Declared explicitly with implements
  • Example:
  interface Speaker {
    void speak();
  }

  class Person implements Speaker {
    public void speak() { System.out.println("Hello!"); }
  }

Go (Golang)

  • Implicit implementation: any type that has the required methods satisfies the interface
  • Example:
  type Speaker interface {
    Speak()
  }

  type Person struct {}

  func (p Person) Speak() {
    fmt.Println("Hello!")
  }

Access Modifiers

Java (OOP)

  • public, private, protected keywords

Go (Golang)

  • Simplicity: Capitalized = exported (public), lowercase = unexported (private)
  • Example:
  type Person struct {
    Name string  // exported
    age  int     // unexported
  }

Constructors

Java (OOP)

  • Defined by the name of the class
  • Example:
  class Person {
    private String name;

    public Person(String name) {
      this.name = name;
    }
  }

Go (Golang)

  • Use factory functions to initialize structs
  • Example:
  type Person struct {
    Name string
  }

  func NewPerson(name string) *Person {
    return &Person{Name: name}
  }

Annotations / Reflection

Java (OOP)

  • Commonly used for frameworks (e.g., @Autowired, @Entity)
  • Example:
  @Entity
  class Person {
    @Id
    private int id;
  }

Go (Golang)

  • Rarely used; prefer explicit configuration
  • Example:
  type Person struct {
    ID int `json:"id"` // struct tags for JSON or DB mapping
  }

Exceptions

Java (OOP)

  • Error handling via try/catch
  • Example:
  try {
    int result = divide(10, 0);
  } catch (ArithmeticException e) {
    System.out.println("Cannot divide by zero");
  }

Go (Golang)

  • No exceptions; handle errors via explicit return values
  • Example:
  func divide(a, b int) (int, error) {
    if b == 0 {
      return 0, fmt.Errorf("cannot divide by zero")
    }
    return a / b, nil
  }

  result, err := divide(10, 0)
  if err != nil {
    fmt.Println(err)
  }

Framework-driven

Java (OOP)

  • Heavy reliance on frameworks (Spring, Hibernate, etc.)

Go (Golang)

  • Prefers libraries + composition, not frameworks
  • Example:
  // No heavy frameworks, just libraries for routing and DB
  r := gin.Default()
  r.GET("/users", getUsersHandler)

Generics

Java (OOP)

  • Widely used with collections (List<T>, Map<K,V>)
  • Example:
  List<String> names = new ArrayList<>();

Go (Golang)

  • Supported since 1.18; encourages simple concrete types when possible
  • Example:
  func MapSlice[T any](items []T, fn func(T) T) []T {
    var result []T
    for _, item := range items {
      result = append(result, fn(item))
    }
    return result
  }

Dependency Injection

Java (OOP)

  • Automated by frameworks (Spring DI)

Go (Golang)

  • Manual DI via constructors or function parameters
  • Example:
  type Service struct {
    Repo Repository
  }

  func NewService(repo Repository) *Service {
    return &Service{Repo: repo}
  }

Multithreading / Concurrency

Java (OOP)

  • Threads, executors, futures, Virtual Threads
  • Example:
  ExecutorService executor = Executors.newFixedThreadPool(2);
  executor.submit(() -> System.out.println("Running in parallel"));

Go (Golang)

  • Lightweight goroutines and channels for concurrency
  • Example:
  go func() {
    fmt.Println("Running in parallel")
  }()