这里简单总结一下三种组合方式:
interface
中嵌套interface
struct
中嵌套struct
struct
中嵌套interface
interface
中嵌套 interface
这种组合方式体现了接口隔离原则(ISP)和接口组合原则, 通过接口嵌套,我们可以构建更大的接口
// 基础读接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// 基础写接口
type Writer interface {
Write(p []byte) (n int, err error)
}
// 组合接口
type ReadWriter interface {
Reader // 嵌套 Reader 接口
Writer // 嵌套 Writer 接口
}
struct
中嵌套 struct
这种组合方式体现了组合优于继承12的原则,是 Go 中实现代码复用的重要方式
// 地址信息
type Address struct {
Street string
City string
Country string
}
// 用户信息
type User struct {
Name string
Age int
Address // 嵌套 Address struct
}
// 使用示例
func main() {
user := User{
Name: "张三",
Age: 25,
Address: Address{
Street: "中关村大街",
City: "北京",
Country: "中国",
},
}
// 可以直接访问嵌套字段
fmt.Println(user.Street) // 输出: 中关村大街
}
struct
中嵌套 interface
这种组合方式体现了依赖倒置原则(DIP),常用于策略模式的实现
// 支付接口
type PaymentMethod interface {
Pay(amount float64) error
}
// 支付订单
type Order struct {
ID string
Amount float64
PaymentMethod // 嵌套支付接口
}
// 支付宝实现
type Alipay struct{}
func (a Alipay) Pay(amount float64) error {
fmt.Printf("使用支付宝支付 %.2f 元\n", amount)
return nil
}
// 微信支付实现
type WechatPay struct{}
func (w WechatPay) Pay(amount float64) error {
fmt.Printf("使用微信支付 %.2f 元\n", amount)
return nil
}
// 使用示例
func main() {
order := Order{
ID: "ORDER001",
Amount: 100.00,
PaymentMethod: new(Alipay),
}
order.Pay(order.Amount)
}
组合优于继承
初步问题
比如说我们要设计一个关于车的类。按照面向对象编程的思想,我们将“车类”这样一个事务抽象成一个BaseCar类,默认有run的行为。那么所有车类都可以继承这个抽象类。比如,汽车,卡车等。
public class BaseCar {
//... 省略其他属性和方法...
public void run() { //... }
}
// 汽车
public class Car extends AbstractCar {
}
但是,基于对车这个对象的理解和需求,车出了跑,还可以修轮胎,可以修引擎等。那么 AbstractCar
就变成如下的类,那么这个时候有个自行车的类需要实现,自行车不能修引擎,要怎么写呢
public class BaseCar {
//... 省略其他属性和方法...
public void run() { //跑... }
public void repairTire() { //修轮胎... }
public void repairEngine() { //修引擎... }
}
// 自行车
public class Bicycle extends BaseCar {
//... 省略其他属性和方法...
public void repairEngine() {
throw new UnSupportedMethodException("我没有引擎!");
}
}
上面的设计有三个点有很大隐患:
- 果我们把基类的行为实现都放到基类里面,比如说,如果后面增加了自动驾驶功能,全景功能,带天窗功能,那是不是都要堆到基类里面了,虽然能提高复用性,但是也会改变所有子类的功能,这也会导致代码的复杂性提升,这点是我们并不想看到的。
- 对于没有没有那些功能的对象,比如自行车,就不应该把修引擎的功能暴露到自行车类里面
- 如果扩展到其他对象怎么办,比如说人也会跑,飞机也会跑。那么这个设计后面就不好扩展了,也不够灵活。那么对于上面的问题我们要怎么解决呢。
你是不是想到了接口,对,接口更多的是行为的定义,抽象类更多的是定义的某一类类型的基础通用行为的实现。其实抽象类的加入也提高了代码的复杂度。
接口优化
对于以上问题,我们无视具体对象,只看行为,比如,跑,修引擎,修轮胎,这些功能行为,我们可以定义成接口:IRun,IEngine,ITire,这三个接口
public interface IRun {
void run();
}
public interface IEngine {
void repairEngine();
}
public interface ITire {
void repairTire();
}
public class Car implements IRun, IEngine, ITire {
@Override
public void run() { /*跑的具体实现*/ }
@Override
public void repairEngine() { /*修引擎的具体实现*/ }
@Override
public void repairTire() { /*修轮胎的具体实现*/ }
}
问题在于:如果有多个类需要相同的"跑"的功能实现,每个类都要重复写一遍相同的代码。
比如汽车和自行车的"跑"可能实现逻辑是一样的,但在这种方式下需要在两个类中都写一遍。
组合优化
对于上面的问题,我们可以通过先实现接口,然后通过组合、委托的方式来解决
// 把"跑"的实现抽取成单独的类
public class CarRunEnable implements IRun {
@Override
public void run() { /*车跑的具体实现*/ }
}
public class Car implements IRun {
private CarRunEnable runEnable = new CarRunEnable(); // 组合
@Override
public void run() {
runEnable.run(); // 委托给 CarRunEnable 来实现
}
}
这种方式的优点是:
- CarRunEnable 中的"跑"的实现可以被多个类复用,不用每个类都写一遍
- 如果要修改"跑"的实现,只需要改 CarRunEnable 一个地方
- 更灵活,可以在运行时切换不同的实现类
本质上是把接口的具体实现抽取成独立的类,需要该功能的类通过组合的方式来复用这个实现。