bingoohuang/blog

把大象塞进冰箱,共分几步?

Opened this issue · 3 comments

把大象塞进冰箱的OO实现

  • 问:把大象塞进冰箱,共分几步?
  • 答:分三步,第一步,打开冰箱门,第二步,把大象塞进去,第三步,关上冰箱门。对于经常忘记关门的人来说,在关门环节,还需要检查一下,是否真的关好了。

JAVA实现

  1. 定义冰箱接口
  2. 定义普通冰箱类,实现接口
  3. 定义密码冰箱类,继承普通冰箱类,重载PutInside方法和Check方法,因为这两个方法,需要验证密码
  4. 创建密码冰箱类,开门、塞大象、关门,一气呵成,输出与预期相符
public class Elephant {
    public static void main(String[] args) {
        PinRefrigerator r = new PinRefrigerator();
        r.Open();      //  Normal Open
        r.PutInside(); //  Pin PutInside,
        r.Close();     // Normal Close, Pin Check
    }

    public interface Refrigerator {
        void Open();
        void PutInside();
        void Close();
        void Check();
    }
    public static class NormalRefrigerator implements Refrigerator {
        public void Open() { System.out.println("Normal Open"); }
        public void PutInside() { System.out.println("Normal PutInside"); }
        public void Close() { System.out.println("Normal Close");this.Check(); }
        public void Check() { System.out.println("Normal Check"); }
    }
    public static class PinRefrigerator extends NormalRefrigerator {
        public void PutInside() { System.out.println("Pin PutInside"); }
        public void Check() { System.out.println("Pin Check"); }
    }
}

GO实现

刘金良同学,按照Java的OO思路,迅速上手了一般GO实现,可是实际输出与预期不相符了,主要是密码冰箱重载的Check方法,竟然没有被调用到。

package main

import "fmt"

func main() {
	r := PinRefrigerator{}
	r.Open()      // Normal Open
	r.PutInside() // Pin PutInside
	r.Close()     // Normal Close, Normal Check
}

type Refrigerator interface {
	Open()
	PutInside()
	Close()
	Check()
}

type NormalRefrigerator struct{}

func (NormalRefrigerator) Open()      { fmt.Println("Normal Open") }
func (NormalRefrigerator) PutInside() { fmt.Println("Normal PutInside") }
func (n NormalRefrigerator) Close()   { fmt.Println("Normal Close"); n.Check() }
func (NormalRefrigerator) Check()     { fmt.Println("Normal Check") }

type PinRefrigerator struct {
	NormalRefrigerator
}

func (PinRefrigerator) PutInside() { fmt.Println("Pin PutInside") }
func (PinRefrigerator) Check()     { fmt.Println("Pin Check") }

改一版,依然带着JAVA的印迹,区别如下:

  1. 大接口拆分成小接口 (践行一下 SRP )
  2. 组合,组合,组合 (践行一下 组合优于继承)
  3. 接收器改成指针类型

这下可以正常输出了

package main

import "fmt"

type Opener interface{ Open() }
type Closer interface{ Close() }
type Checker interface{ Check() }

type NormalRefrigerator struct {
	Checker
}

func (*NormalRefrigerator) Open()      { fmt.Println("Normal Open") }
func (*NormalRefrigerator) PutInside() { fmt.Println("Normal PutInside") }
func (n *NormalRefrigerator) Close()   { fmt.Println("Normal Close"); n.Check() }

type PinRefrigerator struct {
	Opener
	Closer
}

func (*PinRefrigerator) PutInside() { fmt.Println("Pin PutInside") }
func (*PinRefrigerator) Check()     { fmt.Println("Pin Check") }

func main() {
	n := &NormalRefrigerator{}
	r := &PinRefrigerator{Opener: n, Closer: n}
	n.Checker = r
	r.Open()      // Normal Open
	r.PutInside() // Pin PutInside
	r.Close()     // Normal Close, Pin Check
}

再改第3版,更加gopher style了,对比JAVA,是不是感觉:

  1. "碎"了一地
  2. 但每一个都好简单啊
  3. 组合,就是搭积木,比继承好玩多了不是,各种小部件更加灵活了
package main

import "fmt"

type Opener interface{ Open() }
type Closer interface{ Close() }
type Checker interface{ Check() }

type NormalOpener struct{}
type NormalCloser struct{}
type NormalPutInsider struct{}

func (*NormalOpener) Open()                   { fmt.Println("Normal Open") }
func (*NormalPutInsider) PutInside()          { fmt.Println("Normal PutInside") }
func (n *NormalCloser) Close(checker Checker) { fmt.Println("Normal Close"); checker.Check() }

type PinPutInsider struct{}
type PinChecker struct{}

func (*PinPutInsider) PutInside() { fmt.Println("Pin PutInside") }
func (*PinChecker) Check()        { fmt.Println("Pin Check") }

func main() {
	opener := NormalOpener{}
	PutInsider := PinPutInsider{}
	closer := NormalCloser{}
	checker := PinChecker{}

	opener.Open()          // Normal Open
	PutInsider.PutInside() // Pin PutInside
	closer.Close(&checker) // Normal Close, Pin Check
}

是不是可以不用写那么多struct, 直接把"behave"作为func来写呢?

package main

import (
    "fmt"
)

type Open func()
type PutInside func()
type Close func(check Check)
type Check func()

var NormalOpen Open = func() { fmt.Println("Normal Open") }
var NormalPutInside PutInside = func() { fmt.Println("Normal PutInside") }
var NormalClose Close = func(check Check) { fmt.Println("Normal Close"); if nil != check { check() } }
var NormalCheck Check = func() { fmt.Println("Normal Check") }

var PinPutInside PutInside = func() { fmt.Println("Pin PutInside") }
var PinCheck Check = func() { fmt.Println("Pin Check") }

func main() {
    var open = NormalOpen
    var putInside = PinPutInside
    var close = NormalClose
    var check = PinCheck

    open()
    putInside()
    close(check)
}

是不是可以不用写那么多struct, 直接把"behave"作为func来写呢?

理论上完全可以的啦。所以经常有这种 func 与 interface 互换的模式:

// Open 定义了一个打开的接口.
type Opener interface{ Open() }
// OpenHandler 定义了一个打开的行为.
type OpenHandler func()
// Open 让 OpenHandler 隐式实现了 Opener 接口.
func (o OpenHandler) Open() {o()}

// 1. 当你需要 Opener 接口,但是只有 OpenHandler 的时候,你可以直接用,因为 OpenHandler 已经实现了 Opener 接口,最多套一层 OpenHandler(yourFn)
// 2. 当你需要 OpenHandler 函数,但是只有 Opener 对象的时候,你可以直接使用 opener.Open 

但是使用 interface 的方式,有个好处,那就是 IDE (如 goland ),可以 Go to Implementations, 或者 Go to Method Specifications,阅读代码,非常方便

image