看完 Go 的 Interface
後,整體感覺這不是我認識的 Interface
,有很多新東西跟黑科技。下面通過對比 PHP以及 demo 來進行理解。
PHP 有建構函式、很多魔術方法,為什麼 Golang 沒有?或者說不需要呢
PHP 對物件初始化賦值 $a = new Human("Jerr", 23);
需要通過建構函式實現
Go jerr := Human{"Jerr", 23}
,這裡是很優秀。
但是 PHP 的建構函式還可以做一些前置處理,目前還不知道 Go 在這一塊怎麼解決,可能通過包的 init()
,且學且看
最後發現 Golang 官方的關於反射講解的,需要翻牆同時是外文,準備明天「週六」給翻譯過來。
Interface 是什麼
PHP中:
可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容
Interface 定義一個介面類「只有定義,沒有實現」,通過 implements 來實現一個或多個介面類中定義的方法「必須實現介面類中的所有方法」
// 定義 Human 介面有 run 跟 say 兩個方法
interface HumanInterface {
public function run();
public function say();
}
// 實現 Human 介面 必須實現所有方法 run 與 say
class Human implements HumanInterface {
public $age;
public $name;
public function run() {
echo 'I can run';
}
public function say() {
echo 'I can say';
}
}
// 歌手
class Singer extends Human {
public $collection; // 專輯
public function sing() {
echo 'I can sing'
}
}
// 學生
class Student extends Human {
public $lesson;
public function learn() {
echo "I need learn {$lesson}";
}
}
Golang 中:
interface 是一組 method 簽名的組合,我們通過 interface 來定義物件的一組行為。
簡單說 interface 型別定義了一組方法,如果某個物件實現了某個介面的所有方法,則此物件就實現了此介面
// 這裡我們用 go 來做上面 php 類似的事情
// 定義 interface
type HumanInterface interface {
Say()
Run()
}
// 定義 Human 類「結構體」
type Human struct {
name string
age int
}
// 實現 Human 的 Say() 方法
func (h Human) Say() {
fmt.Printf("I can say, I am %s, %d years old\n", h.name, h.age)
}
// 實現 Human 的 Run() 方法
func (h Human) Run() {
fmt.Printf("%s is running\n", h.name)
}
// 歌手
type Singer struct {
Human
collecton string
}
// 歌手的 Sing 方法
func (s Singer) Sing() {
fmt.Printf("%s can sing %s\n", s.name, s.collecton)
}
func main() {
tom := Singer{Human{"Tom", 22}, "《Tom專輯歌曲》"}
tom.Run()
tom.Say()
tom.Sing()
}
你有沒有發現 PHP 的 class 通過 implement 來實現 interface 的所有方法。
而這裡 Golang 的 interface 似乎沒啥用,甚至一點都沒用上,而且你在 Human 裡面只實現 run 或者 say 程式都正常執行。如果你對 golang 的 interface 有疑惑,且繼續往下看
要揭開 Golang interface 神祕的面紗,先來了解下 interface 型別跟 interface 值
interface 的 型別 與 值
interface 型別定義了一組方法,如果某個物件實現了某個介面的所有方法,則此物件就實現了此介面。
換句話說:如果某個物件實現了某個介面的所有方法,則這些物件屬於這個介面「你可理解為對類做了一個分類」
- PHP:介面定義了一系列方法,類顯式的去 implement 這個介面並必須實現介面的所有方法
- Golang:介面定義了一組方法,類定義了一組方法,如果類裡有介面的所有方法「即類實現了介面」
更直觀的理解:控制器在類手裡,我類實現了介面所有的方法我屬於你,我也可以不實現所有方法,我就不屬於你
我們再來看 interface 的值,這個更好玩
如果我們定義了一個 interface 的變數,那麼這個變數裡面可以存實現這個 interface 的任意型別的物件
直接看程式碼
// 控制權在類手裡,所以我們先定義類跟類的方法吧 下面定義的程式碼基本跟上面的一樣 你可以直接看 main 裡面的程式碼
type Human struct {
name string
age int
}
func (h Human) Say() {
fmt.Printf("I can say, I am %s, %d years old\n", h.name, h.age)
}
func (h Human) Run() {
fmt.Printf("%s is running\n", h.name)
}
type Singer struct {
Human
collecton string
}
func (s Singer) Sing() {
fmt.Printf("%s can sing %s\n", s.name, s.collecton)
}
type Student struct{
Human
lesson string
}
func (s Student) Learn() {
fmt.Printf("%s nee learn %s\n", s.name, s.lesson)
}
// interface
type Men interface {
Say()
Run()
}
func main() {
tom := Singer{Human{"Tom", 22}, "《Tom專輯歌曲》"}
peter := Student{Human{"Perter", 18}, "《演算法與資料結構 C 語言描述》"}
jerrk := Human{"jerrk", 26}
var men Men
// men 存 tom
men = tom
men.Say()
men.Run()
// men.Sing() 不能呼叫 Sing() Men 沒有 Sing()
// men 存 peter
men = peter
men.Say()
men.Run()
// men.Learn() 同理
// men 存 jerrk
men = jerrk
men.Say()
men.Run()
x := make([]Men, 3)
// 這三個都是不同型別的元素,但是他們實現了 interface 同一個介面
x[0], x[1], x[2] = tom, peter, jerrk
for _, value := range x{
value.Say()
value.Run()
}
}
Go 通過 interface 實現了 duck-typing: 即 "當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子"
空 interface
空 interface 就是沒有任何 method,順理成章的所有的型別都實現了空 interface,所以空 interface 可以儲存任意型別的值類似於 C 的 void *
PHP 的 $a
作為函式引數
interface 變數持有任意實現該 interface 型別的物件,所以我們可以通過它對我們的函式引數型別做任何我們想要的擴充套件
比如 fmt.Println
可以接收任意型別的資料,
// 原始碼中 interface 引數的定義
type Stringer interface {
String() string
}
預設有個 String 方法的實現,fmt.Println(a) 會呼叫 a.String()
輸入 a, 你可以自己重寫 String 來改變預設輸出
變數儲存的型別
interface 的變數刻意儲存任意實現了該介面的類的例項,我們通過 Comma-ok 斷言
switch 測試
反過來知道這個變數裡面實際儲存了的是哪個型別的物件
- Comman-ok
- switch 測試
直接看程式碼
// value, ok = element.(T)
// value 就是變數的值,ok 是一個 bool 型別,element 是 interface 變數,T 是斷言的型別
// 空 interface
type Element interface {}
type List []Element
type Person struct {
name string
age int
}
func main() {
list := make(List, 3)
list[0] = 1 // int
list[1] = "hello" // string
list[2] = Person{"Jack", 29}
fmt.Println(list)
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is an string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is an Person and its value is %s\n", index, value)
} else {
fmt.Println("Unknow type")
}
}
// switch
for index, element := range list{
switch value := element.(type) { // element.(type) 只能在 switch 內部使用 外部使用 comma-ok
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
element.(type) 只能在 switch 內部使用 外部使用 comma-ok
嵌入 interface
Golang 的 interface 跟 struct 的匿名欄位一樣,可以嵌入到新的 interface 裡面「隱式的包含了 被嵌者 裡面的 method」
io 包下面的 io.ReadWriter ,它包含了 io 包下面的 Reader 和 Writer 兩個 interface:
// io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
反射
PHP 中也實現了反射,我們先來看一下 PHP 中的反射
PHP 提供了一套強大的反射API,允許你在PHP執行環境中,訪問和使用類、方法、屬性、引數和註釋等,其功能十分強大,經常用於高擴充套件的PHP框架,自動載入外掛,自動生成文件,甚至可以用來擴充套件PHP語言
反射能做什麼?換句話說一個類的反射類能做什麼?
- 獲取類的所有屬性
- 獲取類的所有方法
- 獲取類的所有常量
- 獲取類、方法、變數的文件註釋
- 建立類的示例
- 呼叫指定例項的方法、設定變數
- 修改物件的可訪問性
詳細可以去看官方文件 PHP 反射
步驟大概可以為
- 構建類的反射類
- 獲取反射類能反射的資料
- 通過反射 api 操作「建立例項、呼叫方法、修改變數...」
看如下程式碼
use ReflectionClass;
/**
* @author Jerrk
*/
class Human {
/*
* @var string $name
*/
public $name;
public $age;
public __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
public function Say() {
echo "{$this->name} can say";
}
}
// 構建反射類
$class = new ReflectionClass('\Human')
$properties = $class->getProperties(); //獲取所有屬性
$property = $class->getProperty('name'); // 獲取單一屬性
$instance = $class->newInstance('Jerrk', 22);
$class->getProperty('name')->setValue($instance, 'Jerr');
Golang
這裡需要注意的是 反射只能訪問 public 變數不能訪問 private,所以要想讓反射刻意訪問必須大寫結構體的變數名
// 反射
i := Human{"Jerrk", 22}
//t := reflect.TypeOf(&i) // 得到型別的後設資料,通過t我們能獲取型別定義裡面的所有元素
s := reflect.ValueOf(&i).Elem() // 得到實際的值,還可以去改變值
typeOfi := s.Type(); // = reflect.TypeOf(&i) 獲取型別
f2Name := typeOfi.Field(1).Name // 獲取 第二個欄位名 Age
f2Type := s.Field(1).Type() // 獲取第二給欄位值的型別 int
f2Value := s.Field(1).Interface() // 獲取第二給欄位的值 22
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i, typeOfi.Field(i).Name, f.Type(), f.Interface())
}
// 改變值
s.Field(0).SetString("Peter") // 現在 Name 變成了 Peter
s.Field(1).SetInt(44) // 現在 Age 變成了 44
對比下來跟 PHP 還是有一些不一樣的地方,但是符合反射的定義
reflection is just a mechanism to examine the type and value pair stored inside an interface variable
反射是一種檢查程式執行時介面變數中的型別和值的機制
更多的反射相關的知識可以去看官方的 laws of reflection
同時也可以稍微等一天,我明天把 laws of reflection 翻譯過來,深入理解 Golang 的 interface 跟 反射