普里姆演算法 PHP寫法
介面
在講解具體的介面之前,先看如下問題。
使用面向物件的方式,設計一個加減的計算器
程式碼如下:
package main import "fmt" //父類 type Operate struct { num1 int num2 int } //加法子類 type Add struct { Operate } //減法子類 type Sub struct { Operate } //加法子類的方法 func (a *Add) Result() int { return a.num1 + a.num2 } //減法子類的方法 func (s *Sub) Result() int { return s.num1 - s.num2 } //方法呼叫 func main0201() { //建立加法物件 //var a Add //a.num1 = 10 //a.num2 = 20 //v := a.Result() //fmt.Println(v) //建立減法物件 var s Sub s.num1 = 10 s.num2 = 20 v := s.Result() fmt.Println(v) }
以上實現非常簡單,但是有個問題,在main( )函式中,當我們想使用減法操作時,建立減法類的物件,呼叫其對應的減法的方法。但是,有一天,系統需求發生了變化,要求使用加法,不再使用減法,那麼需要對main( )函式中的程式碼,做大量的修改。將原有的程式碼註釋掉,建立加法的類物件,呼叫其對應的加法的方法。有沒有一種方法,讓main( )函式,只修改很少的程式碼就可以解決該問題呢?有,要用到接下來給大家講解的介面的知識點。
1.什麼是介面
介面就是一種規範與標準,在生活中經常見介面,例如:膝上型電腦的USB介面,可以將任何廠商生產的滑鼠與鍵盤,與電腦進行連結。為什麼呢?原因就是,USB介面將規範和標準制定好後,各個生產廠商可以按照該標準生產滑鼠和鍵盤就可以了。
在程式開發中,介面只是規定了要做哪些事情,幹什麼。具體怎麼做,介面是不管的。這和生活中介面的案例也很相似,例如:USB介面,只是規定了標準,但是不關心具體滑鼠與鍵盤是怎樣按照標準生產的.
在企業開發中,如果一個專案比較龐大,那麼就需要一個能理清所有業務的架構師來定義一些主要的介面,這些介面告訴開發人員你需要實現那些功能。
2.介面定義
介面定義的語法如下:
//先定義介面 一般以er結尾 根據介面實現功能
type Humaner interface {
//方法 方法的宣告
sayhi()
}
怎樣具體實現介面中定義的方法呢?
type student11 struct { name string age int score int } func (s *student11)sayhi() { fmt.Printf("大家好,我是%s,今年%d歲,我的成績%d分\n",s.name,s.age,s.score) } type teacher11 struct { name string age int subject string } func (t *teacher11)sayhi() { fmt.Printf("大家好,我是%s,今年%d歲,我的學科是%s\n",t.name,t.age,t.subject) }
具體的呼叫如下:
func main() {
//介面是一種資料型別 可以接收滿足物件的資訊
//介面是虛的 方法是實的
//介面定義規則 方法實現規則
//介面定義的規則 在方法中必須有定義的實現
var h Humaner
stu := student11{"小明",18,98}
//stu.sayhi()
//將物件資訊賦值給介面型別變數
h = &stu
h.sayhi()
tea := teacher11{"老王",28,"物理"}
//tea.sayhi()
//將物件賦值給介面 必須滿足介面中的方法的宣告格式
h = &tea
h.sayhi()
}
只要類(結構體)實現對應的介面,那麼根據該類建立的物件,可以賦值給對應的介面型別。
介面的命名習慣以er結尾。
3.多型
介面有什麼好處呢?實現多型。
多型就是同一個介面,使用不同的例項而執行不同操作
所謂多型指的是多種表現形式,如下圖所示:
使用介面實現多型的方式如下:
package main
import "fmt"
//先定義介面 一般以er結尾 根據介面實現功能
type Humaner1 interface {
//方法 方法的宣告
sayhi()
}
type student12 struct {
name string
age int
score int
}
func (s *student12)sayhi() {
fmt.Printf("大家好,我是%s,今年%d歲,我的成績%d分\n",s.name,s.age,s.score)
}
type teacher12 struct {
name string
age int
subject string
}
func (t *teacher12)sayhi() {
fmt.Printf("大家好,我是%s,今年%d歲,我的學科是%s\n",t.name,t.age,t.subject)
}
//多型的實現
//將介面作為函式引數 實現多型
func sayhello(h Humaner1) {
h.sayhi()
}
func main() {
stu := student12{"小明",18,98}
//呼叫多型函式
sayhello(&stu)
tea := teacher12{"老王",28,"Go"}
sayhello(&tea)
}
關於介面的定義,以及使用介面實現多型,大家都比較熟悉了,但是多型有什麼好處呢?現在還是以開始提出的計算器案例給大家講解一下,在開始我們已經實現了一個加減功能的計算器,但是有同學感覺太麻煩了,因為實現加法,就要定義加法操作的類(結構體),實現減法就要定義減法的類(結構體),所以該同學實現了一個比較簡單的加減法的計算器,如下所示:
1.使用面向物件的思想實現一個加減功能的計算器,可能有同學感覺非常簡單,程式碼如下:
我們定義了一個類(結構體),然後為該類建立了一個方法,封裝了整個計算器功能,以後要使用直接使用該類(結構體)建立物件就可以了。這就是面向物件總的封裝性。
也就是說,當你寫完這個計算器後,交給你的同事,你的同事要用,直接建立物件,然後呼叫GetResult()方法就可以,根本不需要關心該方法是怎樣實現的.這不是我們前面在講解面向物件概念時說到的,找個物件來幹活嗎?不需要自己去實現該功能。
2.大家仔細觀察上面的程式碼,有什麼問題嗎?
現在讓你在改計算器中,再增加一個功能,例如乘法,應該怎麼辦呢?你可能會說很簡單啊,直接在GetResult( )方法的switch中新增一個case分支就可以了。
問題是:在這個過程中,如果你不小心將加法修改成了減法怎麼辦?或者說,對加法運算的規則做了修改怎麼辦?舉例子說明:
你可以把該程式方法想象成公司中的薪資管理系統。如果公司決定對薪資的運算規則做修改,由於所有的運算規則都在Operation類中的GetResult()方法中,所以公司只能將該類的程式碼全部給你,你才能進行修改。這時,你一看自己作為開發人員工資這麼低,心想“TMD,老子累死累活才給這麼點工資,這下有機會了”。直接在自己工資後面加了3000
numA+numB+3000
所以說,我們應該將加減等運算分開,不應該全部糅合在一起,這樣你修改加的時候,不會影響其它的運算規則:
具體實現如下:
現在已經將各個操作分開了,並且這裡我們還定義了一個父類(結構體),將公共的成員放在該父類中。如果現在要修改某項運算規則,只需將對應的類和方法發給你,進行修改就可以了。
這裡的實現雖然將各個運算分開了,但是與我們第一次實現的還是有點區別。我們第一次實現的加減計算器也是將各個運算分開了,但是沒有定義介面。那麼該介面的意義是什麼呢?繼續看下面的問題。
3:現在怎樣呼叫呢?
這就是我們一開始給大家提出的問題,如果呼叫的時候,直接建立加法操作的物件,呼叫對應的方法,那麼後期要改成減法呢?需要做大量的修改,所以問題解決的方法如下:
建立了一個類OperationFactory,在改類中添加了一個方法CreateOption( )負責建立物件,如果輸入的是“+”,建立
OperationAdd的物件,然後呼叫OperationWho( )方法,將物件的地址傳遞到該方法中,所以變數i指的就是OperationAdd,接下來在呼叫GetResult( )方法,實際上呼叫的是OperationAdd類實現的GetResult( )方法。
同理如果傳遞過來的是“-”,流程也是一樣的。
所以,通過該程式,大家能夠體會出多型帶來的好處。
4:最後呼叫
這時會發現呼叫,非常簡單,如果現在想計算加法,只要將”-”,修改成”+”就可以。也就是說,除去了main( )函式與具體運算類的依賴。
當然程式經過這樣設計以後:如果現在修改加法的運算規則,只需要修改OperationAdd類中對應的方法,
不需要關心其它的類,如果現在要增加“乘法” 功能,應該怎樣進行修改呢?第一:定義乘法的類,完成乘法運算。
第二:在OperationFactory類中CrateOption( )方法中新增相應的分支。但是這樣做並不會影響到其它的任何運算。
大家可以自己嘗試實現“乘法”與“除法”的運算。
在使用面向物件思想解決問題時,一定要先分析,定義哪些類,哪些介面,哪些方法。把這些分析定義出來,然後在考慮具體實現。
最後完整程式碼如下:
package main
import "fmt"
//定義介面
type Opter interface {
//方法宣告
Result() int
}
//父類
type Operate struct {
num1 int
num2 int
}
//加法子類
type Add struct {
Operate
}
//加法子類的方法
func (a *Add) Result() int {
return a.num1 + a.num2
}
//減法子類
type Sub struct {
Operate
}
//減法子類的方法
func (s *Sub) Result() int {
return s.num1 - s.num2
}
//建立一個類負責物件建立
//工廠類
type Factory struct {
}
func (f *Factory) Result(num1 int, num2 int, ch string) {
switch ch {
case "+":
var a Add
a.num1 = num1
a.num2 = num2
Result(&a)
case "-":
var s Sub
s.num1 = num1
s.num2 = num2
Result(&s)
}
}
//通過設計模式呼叫
func main() {
//建立工廠物件
var f Factory
f.Result(10, 20, "+")
}
下面我們將介面其它的知識點再給大家說一下:
4.介面繼承與轉換(瞭解)
介面也可以實現繼承:
package main
import "fmt"
//先定義介面 一般以er結尾 根據介面實現功能
type Humaner2 interface { //子集
//方法 方法的宣告
sayhi()
}
type Personer interface { //超集
Humaner2 //繼承sayhi()
sing(string)
}
type student13 struct {
name string
age int
score int
}
func (s *student13)sayhi() {
fmt.Printf("大家好,我是%s,今年%d歲,我的成績%d分\n",s.name,s.age,s.score)
}
func (s *student13)sing(name string) {
fmt.Println("我為大家唱首歌",name)
}
func main() {
//介面型別變數定義
var h Humaner2
var stu student13 = student13{"小吳",18,59}
h = &stu
h.sayhi()
//介面型別變數定義
var p Personer
p = &stu
p.sayhi()
p.sing("大碗麵")
}
介面繼承後,可以實現“超集”介面轉換“子集”介面,程式碼如下:
package main
import "fmt"
//先定義介面 一般以er結尾 根據介面實現功能
type Humaner2 interface { //子集
//方法 方法的宣告
sayhi()
}
type Personer interface { //超集
Humaner2 //繼承sayhi()
sing(string)
}
type student13 struct {
name string
age int
score int
}
func (s *student13)sayhi() {
fmt.Printf("大家好,我是%s,今年%d歲,我的成績%d分\n",s.name,s.age,s.score)
}
func (s *student13)sing(name string) {
fmt.Println("我為大家唱首歌",name)
}
func main() {
//介面型別變數定義
var h Humaner2 //子集
var p Personer //超集
var stu student13 = student13{"小吳",18,59}
p = &stu
//將一個介面賦值給另一個介面
//超集中包含所有子集的方法
h = p //ok
h.sayhi()
//子集不包含超集
//不能將子集賦值給超集
//p = h //err
//p.sayhi()
//p.sing("大碗麵")
}
5.空介面
空介面(interface{})不包含任何的方法,正因為如此,所有的型別都實現了空介面,因此空介面可以儲存任意型別的數值。
例如:
var i interface{}
//介面型別可以接收任意型別的資料
//fmt.Println(i)
fmt.Printf("%T\n",i)
i = 10
fmt.Println(i)
fmt.Printf("%T\n",i)
當函式可以接受任意的物件例項時,我們會將其宣告為interface{},最典型的例子是標準庫fmt中PrintXXX系列的函式,例如:
func Printf(fmt string, args ...interface{})
func Println(args ...interface{})
如果自己定義函式,可以如下:
func Test(arg ...interface{}) {
}
Test( )函式可以接收任意個數,任意型別的引數。