闽公网安备 35020302035485号
里氏替换原则:如果你的代码依赖于一个抽象,一个实现可以被另一个实现替换,而不需要改变你的代码。
type ReadWriteCloser interface {
io.Reader
io.Writer
io.Closer
}
3. 你没有编写行为驱动的接口
package main
// ====== producer side
// This interface is not needed
type UsersRepository interface {
GetAllUsers()
GetUser(id string)
}
type UserRepository struct {
}
func (UserRepository) GetAllUsers() {}
func (UserRepository) GetUser(id string) {}
// ====== client side
// 堆代码 duidaima.com
// Client only needs GetUser and
// can create this interface implicitly implemented
// by concrete UserRepository on his side
type UserGetter interface {
GetUser(id string)
}
如果客户端想使用生产者的所有方法,可以使用具体的结构体。因为结构体方法已经提供了这些行为。即使客户端想要解耦代码并使用多种实现,他也可以在自己这边创建一个包含所有方法的接口:
type Shape interface {
Area() float64
Perimeter() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
// NewCircle returns an interface instead of struct
func NewCircle(radius float64) Shape {
return Circle{Radius: radius}
}
func main() {
circle := NewCircle(5)
// we lose access to circle.Radius
}
在上面的示例中,我们不仅无法访问 circle.Radius,而且每次要访问它时都需要在代码中添加类型断言:shape := NewCircle(5)
if circle, ok := shape.(Circle); ok {
fmt.Println(circle.Radius)
}
因此在设计上,请遵循 Postel 定律:“发送时要保守,接受时要宽松”,从方法中返回具体的结构,并选择接受接口。// Save writes the contents of doc to the file f. func Save(f *os.File, doc *Document) error但入参是 f *os.File,这并不灵活,也不便于测试。我们可以通过上面提到的接口方式,改造为如下代码:
// Save writes the contents of doc to the supplied // Writer. func Save(w io.Writer, doc *Document) error6. 你没有验证接口合规性
package users
type User struct {
Name string
Email string
}
func (u User) String() string {
return u.Name
}
客户端的代码如下:package main
import (
"fmt"
"pkg/users"
)
func main() {
u := users.User{
Name: "脑子进煎鱼了",
Email: "xxx@gmail.com",
}
fmt.Printf("%s", u)
}
这将正确输出:脑子进煎鱼了。现在,假设你进行了重构,不小心删除或注释了 String() 的实现,你的代码看起来就像这样:package users
type User struct {
Name string
Email string
}
在这种情况下,您的代码仍然可以编译和运行,但输出结果将是 {脑子进煎鱼了 xxx@gmail.com}。没有任何程序反馈来提示你出现了问题。编译器也不会报错。这种场景下,为了强制校验某个类型是否实现了某个接口,我们可以这样做:package users
import "fmt"
type User struct {
Name string
Email string
}
var _ fmt.Stringer = User{} // User implements the fmt.Stringer
func (u User) String() string {
return u.Name
}
我们再尝试一次。现在如果我们删除 String() 方法,就会在构建时得到如下结果:cannot use User{} (value of type User) as fmt.Stringer value in variable declaration: User does not implement fmt.Stringer (missing method String)
在该行中,我们试图将一个空的 User{} 赋值给一个 fmt.Stringer类型的变量。由于 User{} 不再实现 fmt.Stringer,我们得到了程序的报错反馈。我们在变量名中使用了 _,因为我们并没有真正使用它,所以不会执行真正的分配。上面我们看到 User 实现了接口。User 和 *User 是不同的类型。因此,如果你想让 *User 实现它,你可以这样实现:var _ fmt.Stringer = (*User)(nil) // *User implements the fmt.Stringer并且通过这种实现,IDE 会显式提示我们的方法是否有缺失。少了的话会有报错提示:
