头图

The general principle of "accepting the interface and returning the structure", I wrote in the previous article, and introduced it to my colleagues many times during code reviews, but often encountered "why" questions. Especially because this is not a hard and fast rule. The key to this idea is to keep flexibility while avoiding pre-abstraction and understanding when to change it.

gopher.png

Pre-abstraction complicates the system

Any problem in the field of computer science can be solved by adding an indirect middle layer, of course, except for problems with too much indirection-David J. Wheeler

Software engineers like abstraction. Personally, I have never seen a colleague who is more involved in writing code than creating abstractions. In Go, the interface abstraction is separated from the structure, and this indirect layer does not even have the lowest level of embedding complexity. Following the philosophy of software design you will not use it , there is no point in creating this complexity before you need it. A common reason for the function call return interface is to allow users to focus on the API that the function opens. Because of the implicit interface, Go doesn't need to do this. The public function iu of the structure is its API.

Always when really [abstract], not when foresees needs [abstract].

Some languages require you to foresee every interface you will need in the future. One of the great advantages of implicit interfaces is that they allow for elegant abstraction after the fact, without the need to abstract beforehand.

Individual "needs"

When really needed

How to define when abstraction is needed? For return types, this is easy. You are the person who wrote the function, so you know exactly when you need to abstract the return value.

For function input, the demand is not within your control. You may think that the database struct is sufficient, but the user may need to decorate it with other things. Even if it is not impossible, it is difficult to predict the state of everyone using your function. Can accurately control output, but cannot predict user input. Compared with the abstraction of the output, this imbalance causes a stronger emphasis on the abstraction of the input.

Remove unnecessary code details

recipes.png

Another aspect of simplification is to remove unnecessary details. Functions are like cooking recipes: given input, you get a cake! No recipe will list unwanted ingredients. Similarly, functions should not list unwanted inputs. How do you look at the following function?

func addNumbers(a int, b int, s string) int { 
    return a + b 
}

For most programmers, it is obvious that the parameter s is inappropriate. When the parameter is a structure, it is less obvious.

type Database struct{ } 
func (d *Database) AddUser(s string) {...} 
func (d *Database) RemoveUser(s string) {...}
func NewUser(d *Database, firstName string, lastName string) { 
    d.AddUser(firstName + lastName) 
}

Just like a recipe with too many ingredients, NewUser receives a Database object that can do too many things. It only needs AddUser, but it also receives RemoveUser as the parameter. Functions created using the interface can only depend on what is necessary.

type DatabaseWriter interface { 
    AddUser(string) 
} 
func NewUser(d DatabaseWriter, firstName string, lastName string) { 
    d.AddUser(firstName + lastName) 
}

Dave Cheney describing Interface Segregation Principle when wrote this . He also described other advantages of restricting input, which are worth reading. The overall goal of making this idea understandable is:

The result is a function at the same time, its requirements are the most specific-it only needs a writable thing-and its function is the most general

I just want to add that the above function addNumber obviously should not have the parameter string s, and the function NewUser ideally does not need to delete the user's database.

Summarize the reason and review the exception

The main reasons are as follows:

  • Remove unnecessary abstractions
  • The user's need for function input is ambiguous
  • Simplify function input

The above reasons also allow us to define exceptions to the rule. For example, if a function needs to return multiple types, then obviously the return needs to be defined as an interface. Similarly, if the function is private, then the function input is not ambiguous, because you can control it, so it tends to be non-pre-abstraction. For the third rule, go has no way to abstract the values of struct members. Therefore, if your function needs to access structure members (not just functions on the structure), then you will be forced to accept the structure directly.

Original: What “accept interfaces, return structs” means in Go

The author of this article : cyningsun
This text address : https://www.cyningsun.com/08-08-2021/go-accept-interfaces-return-structs.html
copyright statement : All articles in this blog, except for special statements, use the CC BY-NC-ND 3.0 CN license agreement. Please indicate the source!


有疑说
12 声望5 粉丝