1
头图

This article will describe the preemptive interface pattern that is often used in code and why I think it is usually incorrect to follow this pattern in Go.

What is a preemptible interface

Interfaces are a way of describing behavior and exist in most types of languages. A preemptive interface means that the developer encodes the interface before the actual need arises. An example might look like the following.

type Auth interface {
  GetUser() (User, error)
}

type authImpl struct {
  // ...
}
func NewAuth() Auth {
  return &authImpl
}

When is a preemptive interface useful

The preemption interface is usually used in Java and has been a great success. This is the idea of most programmers. I believe that many Go developers think so too. The main difference in this usage is that Java has an explicit interface, while Go has an implicit interface. Let's look at some sample Java code, which shows the difficulties that may arise if you do not use the preemptive interface in Java.

// auth.java
public class Auth {
  public boolean canAction() {
    // ...
  }
}
// logic.java
public class Logic {
  public void takeAction(Auth a) {
    // ...
  }
}

Now suppose you want to change the parameter Auth type object in Logic's takeAction method, as long as it has a canAction() method. Unfortunately, you can't. Auth does not implement an interface with canAction() in it. You must now modify Auth to provide an interface for it, and then you can accept the interface in takeAction, or wrap Auth in a class that does nothing except the implemented method. Even if logic.java defines an Auth interface to accept in takeAction(), it may be difficult for Auth to implement this interface. You may not have the right to modify Auth, or Auth may be in a third-party library. Maybe the author of Auth disagrees with your modification. Maybe share Auth with colleagues in the code base, and now we need to reach a consensus before making changes. This is the desired Java code.

// auth.java
public interface Auth {
  public boolean canAction()
}
// authimpl.java
class AuthImpl implements Auth {
}
// logic.java
public class Logic {
  public void takeAction(Auth a) {
    // ...
  }
}

If the author of Auth originally coded and returned an interface, then you will never encounter problems when trying to extend takeAction. It is naturally applicable to any Auth interface. In languages with explicit interfaces, you will thank your past self for using preemptive interfaces in the future.

Why is this not a problem in Go

Let's set the same situation in Go.

// auth.go 
type Auth struct { 
// ... 
}
// logic.go 
func TakeAction(a *Auth) { 
  // ... 
}

If logic wants to make TakeAction universal, the logic owner can perform this operation unilaterally without disturbing others.

// logic.go 
type LogicAuth interface { 
  CanAction() bool 
}
func TakeAction(a LogicAuth) { 
  // ... 
}

Please note that auth.go does not need to be changed. This is the key to making preemptible interfaces no longer needed.

Unexpected side effects of preemptive interfaces in Go

Go's interface definitions are small, but very powerful. In the standard library, most interface definitions are single methods. This allows maximum reuse because it is easy to implement the interface. When a programmer encodes a preemptive interface like Auth above, the number of methods of the interface tends to increase sharply, which makes the full meaning of the interface (interchangeable implementation) more difficult to achieve.

The best use of interfaces in Go

A good rule of thumb for Go is-accept the interface and return the structure. The accept interface provides the maximum flexibility for your API, and the return structure allows the caller to quickly navigate to the correct function.

Even if your Go code accepts the structure and returns the structure to start, the implicit interface allows you to extend your API later without breaking backward compatibility. An interface is an abstraction, and abstraction is sometimes useful. However, unnecessary abstraction will cause unnecessary complications. Don't make the code too complicated until you need it.


面向加薪学习
18 声望1k 粉丝