Go语言学习笔记09--面向对象的接口与多态

1.接口interface   

(1)接口定义

        在go语言中接口interface的概念类似于Objective-C中的协议protocol,
        也就是说实现接口的类必须实现接口中规定的所有方法。
        (即遵守协议的类必须实现协议中的方法。)
        接口的出现,实际上是为了面向对象语言编程中模块化思想更好的体现。
        在go语言中接口通常采用interface关键字定义

type 接口名称 interface{
    接口方法1();
    接口方法2();
    ...
}

    (2)接口实现

        与协议相同的地方是,接口只是规定了【实现接口的类】必须实现的方法有什么,而不必具体去实现他们。
        但是实现了这个接口的类就必须实现接口中的每一个方法。
        换句话说,接口只是一个规定,一个对类中应该实现什么方法的规定而已。
        eg:
            var 接口对象 接口名称
            接口对象 = &实现接口的类的对象

type Operate interface{
    operate() int;
}
type Add struct{ 
    num1 int
    num2 int
}
func (add *Add) operate()int{return add.num1+add.num2}
func main(){
    var op Operate
    op = &Add{num1:100, num2:20}
    fmt.Println(op.operate());//120
}

    (3)接口意义

        在上面的例子中看来接口的存在毫无意义,毕竟我们可以直接通过创建结构体对象来调用结构体自己的方法

var add Add
fmt.Println(add.operate(100,20));//100

        这和上面的写法的结果如出一辙,那这样说来,接口的存在还有什么必要性么?
        如果你存在这样的问题,其实是由于没有考虑到当程序结构变得庞杂的时候,对程序的维护量级导致的。
        我们可以尝试来看一下下面的例子:

type Add struct{
    num1 int
    num2 int
}
type Sub struct{
    num1 int
    num2 int
}
func (add *Add) operate()int{return add.num1+add.num2}
func main(){
    var add Add = &Add{num1:100, num2:20}
    fmt.Println(add.operate());//120
    var sub Sub = &Sub{num1:100, num2:20}
    fmt.Println(sub.operate());//error
}

         似乎看出来点什么了?显然,上面的这段代码其实从模块化的角度来看简直就是灾难。
        因为每当我们需要添加一种算法类的时候,在main函数中都必须与之对应的去操作对应类的代码
        而这还意味着【将类和main函数拆分不同包,然后分工合作】的期望完全打了水漂
        因为我们必须将类结构和main函数整体交给一个人,才能让这个人真正去添加一个算法,
        才不会对原有的结构造成影响
        否则如果我们只将类结构交给一个人,将main函数交给另一个人。
        那么编写类结构的人不知道main函数中要如何调用,而编写main函数的人又不知道类中添加了什么方法
        就像上面案例中那样,类Sub没能添加operate运算方法而导致的报错。
        只有做出一个统一的规定,大家都按照这个规定来编辑:
        例如,添加的算法类中参与运算的方法就必须名叫operate,这样就可以分工合作了
        而这个规定,就是接口。
        而这,就是接口存在的意义。
    

(4)接口继承

        在go语言中,接口和类一样都允许通过匿名字段来实现继承关系。并且当子类接口继承了父类接口的时候,
        实现了子类接口方法的类,也必须同时实现父类接口中的方法。

//父类接口
type FatherJieKou interface {
    father();
}
//子类接口
type SonJieKou interface {
    FatherJieKou//接口继承
    son();
}
//类
type Son struct {
    sName string
}
//类方法--子类接口规定的方法
func (son *Son) son(){
    fmt.Println(son.sName);
}
func main(){
    var sonjiekou SonJieKou
    sonjiekou = &Son{"frank"};
    /*
        最后一句产生一个类型错误,提示说该类型的对象&取地址后不能作为SonJieKou类型的接口。
        这就是因为子类Son尽管实现了子类接口规定的方法son,
        但是由于子类接口继承了父类接口,
        因此子类Son只有同时实现子类接口规定的方法son,和父类接口规定的方法father
        才能作为SonJieKou类型的接口存在。
    */    
}

    ps:下面列举了一个详细的接口继承案例,分别列举了接口继承后,实现接口类对象与接口的调用关系

//1.定义接口
//超类接口
type USBer interface {
    usbRead()
    usbWrite()
    usbDoInterface()
}
//子类接口
type Mobiler interface {
    USBer
    mobileCall()
}
//2.定义类
//定义超类
type USBDev struct {
    name       string
    readSpeed  int
    writeSpeed int
}
func (usb *USBDev) usbRead() {
    fmt.Printf("【%s】的读取速度是:%d\n", usb.name, usb.readSpeed)
}
func (usb *USBDev) usbWrite() {
    fmt.Printf("【%s】的写入速度是:%d\n", usb.name, usb.writeSpeed)
}
func (usb *USBDev) usbDoInterface() {
    usb.usbRead()
    usb.usbWrite()
}
//定义子类
type Mobile struct {
    USBDev
    callInfo string
}
func (mobile *Mobile) mobileCall() {
    fmt.Printf("%s的功能是%s\n", mobile.name, mobile.callInfo)
}
//3.调用
func main() {
    //(1)子类接口,子类对象实现
        var dev1 Mobiler
        //实现对应子类接口的子类
        dev1 = &Mobile{USBDev{"苹果电话", 100, 10}, "打电话"}
        dev1.mobileCall()
        dev1.usbDoInterface()
        //实现对应子类接口的子类
        dev1 = &Mobile{USBDev{"安卓电话", 50, 5}, "玩游戏"}
        dev1.mobileCall()
        dev1.usbDoInterface()
        //这种情况正常输出没问题
    //(2)子类接口,父类对象实现
        //var dev2 Mobiler;
        //实现对应子类接口的父类
        //dev2 = &USBDev{"常规设备",10000,10000};
        /*
            到这位置已经开始报错,因为子类接口继承了父类接口
            因此要求实现子类接口的对象必须不但实现父类接口的方法,还必须实现子类接口自己的方法
            而父类对象尽管实现了父类接口中的方法,
            但是父类对象是没有办法实现子类接口中的方法的(即父类没法继承子类,只能子类继承父类)
            因此,出现了【实现对象没能实现接口规定的所有方法】。
            因此报错
        */
    //(3)父类接口,子类对象实现
        //创建父类接口
        var dev3 USBer
        //实现对应父类接口的子类
        dev3 = &Mobile{USBDev{"(实现父类接口的子类对象)苹果手机", 1000, 100}, "(实现父类接口的子类对象)打电话"}
        //dev3.mobileCall();
        /*
            本句话发生错误
            原因是子类对象尽管不但实现了父类接口的所有方法,还实现了子类接口自己的所有方法
            但是接口是父类接口,因此接口能够调用的方法只能是父类接口中规定的方法
            所以,子类对象实现的子类接口方法在此是不能被看到的。
            因此报错。
        */
        dev3.usbDoInterface()//如果上一句注释,这句能够正常输出
    //(4)父类接口,父类对象实现
        var dev4 USBer
        dev4 = &USBDev{"常规设备", 10000, 10000}
        dev4.usbDoInterface()
        //这种情况正常输出没问题
}

    (5)接口转换

        可以将【子类接口实现对象】赋值给【父类接口实现对象】
        相当于对父类接口实现对象的一个内容扩充。
        eg:
            fatherJieKou = sonJieKou;
        但是不能将【父类接口实现对象】赋值给【子类接口实现对象】
        这是因为子类接口实现对象实现的方法不但拥有父类接口的方法,还有子类接口独有的方法
        如果赋值,相当于砍掉了一部分子类接口实现对象的方法,而这是不允许的。

//父接口
type USBer interface {
    doInterface();
}
//子接口
type Phoner interface {
    USBer
    phoneCall();
}
//父类
type USBDev struct {
    name string
}
func (usbDev *USBDev)doInterface(){
    fmt.Printf("name:%s\n",usbDev.name);
    fmt.Println("这是父接口规定的方法,在父类实现方法中作出的输出");
}
//子类
type PhoneDev struct {
    USBDev
    phoneCallInfo string
}
func (phone PhoneDev)phoneCall(){
    fmt.Println("这是子接口规定的方法,在子类实现方法中作出的输出");
    fmt.Printf("打电话的内容是:%s\n",phone.phoneCallInfo);
}
func main() {
    //父接口,父对象实现
    var usber USBer
    usber = &USBDev{"父类设备"};
    usber.doInterface();//正常输出
    //子接口,子对象实现
    var phoner Phoner
    phoner = &PhoneDev{USBDev{"子类继承的父类设备"},"这是子类打电话的内容"};
    phoner.doInterface();
    phoner.phoneCall();//正常输出

    //(1)接口转换,将子接口赋值给父接口
    //这个操作"相当于"对父接口进行扩充,不是真的扩充了
    //父接口还是只能调用父接口的方法
    //但实际内容已经发生了变更
    usber = phoner;
    usber.doInterface();//内容变更
    //usber.phoneCall();//不是真正的扩充,因此这个子类接口的方法还是不能调用

    //(2)接口转换,将父接口赋值给子接口
    //这个操作"相当于"将内容较多的子接口塞进了内容较少的父接口
    //这会导致子接口中未知某部分内容丢失
    //这不符合计算机的执行标准,因此违法error
    //phoner = usber;
}

    (6)空接口

        在go语言中interface除了定义接口,用作类的“标准”之外,还存在一个“万能指针”的用法
        就是空接口
        eg:
            var 空接口名 interface{}
        空接口实际上是一个可以接受任意数据类型的内存指针变量,
        它十分类似于弱类型语言(例如JavaScript)中的动态变量,即变量被声明时可以不指定类型
        变量的类型是根据其被赋予的值来动态发生变化的。

            var temp interface{}
            temp = 10;
            fmt.Println("1.",temp);//10
            temp = "123";
            fmt.Println("2.",temp);//"123"
            temp = []int{1,2,3}
            fmt.Println("3.",temp);//[1,2,3]
            temp = map[string]int{"id":135,"nameID":11331133};
            fmt.Println("4.",temp);//map{id:135,nameId:11331133}

            空接口在go语言中有非常多的表现形式,例如空切片接口

    var tempSlice []interface{}
    tempSlice = append(tempSlice, 1,"2",[2]int{100,200});
    fmt.Println(tempSlice);//[1,2,[100,200]]

            这种写法比较类似于弱类型语言(例如JavaScript)中的数组结构

    (7)类型断言

        go语言中类型断言是经常会和空接口配合使用的一种判别变量类型的手段
        其作用是在程序执行的时候判断出变量的类型。
        由于go语言在变量声明的时候就已经对变量的类型做出了规定,
        只有空接口才会出现不确定变量的类型情况。
        eg:
            存储结果,判别标志 = 数据本身.(要判定的数据类型);

            tempSlice := make([]interface{},2);
            tempSlice[0] = 11;
            tempSlice[1] = "11";
            fmt.Println(tempSlice);//[11,"11"]
            for _,val := range tempSlice{
                if data,flag := val.(int);flag{
                    fmt.Println("int整数:",data);
                }else if data,flag := val.(string);flag{
                    fmt.Println("string字符串:",data);
                }
            }

        ps:类型断言执行完毕后会将存储结果返回,并对判别标志赋值。
            存储结果会被赋值为数据本身
            而判别标志是一个布尔值,根据数据是否为要判定的数据类型被赋值为true或false


2.多态polymorphism

    【封装Encapsulation】、【继承Enheritance】和【多态Polymorphism】是面向对象思想语言的三大特征。
    而go语言是一门典型的面向过程思想语言,只不过为了解决面向对象的问题,提出了很多模拟的手段。
    例如,使用【结构体struc+方法扩展】来模拟【封装】的特性,使用【匿名字段】来模拟【继承】的特性。
    而go语言中的多态其实就利用到了接口interface的相关内容。
    多态的官方解释为:
        同名方法在不同的实例调用时有不同的表现形式与结果。
    其实翻译成大白话可以这么理解:
        不同类的实例拥有同名方法,当不同实例分别调用这个方法的时候,有不同的输出结果。

        type Operate interface{
            operate() int;
        }    
        type Add struct{
            num1 int
            num2 int
        }
        type Sub struct{
            num1 int
            num2 int
        }
        func (add *Add) operate()int{return add.num1+add.num2}
        func (sub *Sub) operate()int{return sub.num1-sub.num2}

        //提供接口方法operate的【多态实现】
        func OperateMethod(op Operate) int{
            return op.operate();
        }
        func main(){
            var op Operate
            op = &Add{num1:100, num2:20};
            fmt.Println(op.operate());        //operate方法第一次调用,结果为120
            op = &Sub{num1:100, num2:20};
            fmt.Println(op.operate());        //operate方法第二次调用,结果为80
        }

       
3.工厂模式

    所谓的设计模式,其实就是代码编辑的一种规律性写法。
    而工厂模式就是设计模式中非常经典的一种,其特征是能够推迟对象实例化的时机。
    但是go语言是强格式类型语言,即对象赋值的类型必须与对象声明时的类型一致
    因此go语言中的工厂模式被用作【推迟对象实例化,并执行某些操作的时机】
    而在实际开发中工厂模式有简单工厂模式、抽象工厂模式、超级工厂模式...很多种
    今天在这里只讨论最基础的工厂模式。
    eg:
        type 工厂类名 struct{}
        func (工厂形参 工厂类名)工厂方法(实例类型){
            switch 实例类型{
                case 类型1:
                    对象1 = 类型1实例化
                    对象1.某方法()...
                case 类型2:
                    对象2 = 类型2实例化
                    对象2.某方法()...
            }
         }

        type PhoneFactory struct{}
        func (phFactory PhoneFactory)createPhone(phoneType string)(re []interface{}){
            switch phoneType{
            case "苹果手机":
                iphone := IPhone{Phone{"苹果手机"}}
                re = append(re, iphone);
            case "安卓手机":
                aphone := APhone{Phone{"安卓手机"}}
                re = append(re, aphone);
            }
            return;
        }
        type Phone struct {
            pName string
        }
        type IPhone struct {Phone}
        type APhone struct {Phone}
        func main(){
            var factory PhoneFactory
            /*
                并不是直接实例化安卓手机类
                而是将实例化的操作推迟到了方法内部,由APhone自己去实现。
            */
            result := factory.createPhone("安卓手机");
            /*
                这里之所以使用类型断言来输出
                是因为工厂返回的是一个空切片接口,内部的数据类型并不能确定究竟是何种类型
                这种情况下就必须使用类型断言来处理输出。
            */
            for _,v := range result{
                if data,flag := v.(IPhone);flag{
                    fmt.Println(data.pName);
                }else if data,flag := v.(APhone);flag{
                    fmt.Println(data.pName);
                }
            }
        }

4.综合实例

        /*采用面向对象思想实现计算器逻辑*/
        //(1)创建运算接口,并提供接口的多态实现函数
        type OperateInterface interface {
            operate() int;
        }
        func OperatePolyMethod(tempOperateObj OperateInterface) int{
            return tempOperateObj.operate();
        }
        //(2)创建运算类,并按照运算接口实现运算方法
        type NUM struct {
            num1 int
            num2 int
        }
        type Add struct{NUM}
        type Sub struct{NUM}
        type And struct{NUM}
        type Dvd struct{NUM}
        func (add *Add) operate()int{ return add.num1+add.num2; }
        func (sub *Sub) operate()int{ return sub.num1-sub.num2; }
        func (and *And) operate()int{ return and.num1*and.num2; }
        func (dvd *Dvd) operate()int{ return dvd.num1/dvd.num2; }
        //(3)创建运算工厂,暴露接口
        type OperaterFactory struct{}
        func (tempFactory OperaterFactory)DoOperate(num1,num2 int,typ string)int{
            var operaterInterfaceObj OperateInterface;
            switch typ {
            case "+":
                operaterInterfaceObj = &Add{NUM{num1,num2}};
            case "-":
                operaterInterfaceObj = &Sub{NUM{num1,num2}};
            case "*":
                operaterInterfaceObj = &And{NUM{num1,num2}};
            case "/":
                operaterInterfaceObj = &Dvd{NUM{num1,num2}};
            }
            return OperatePolyMethod(operaterInterfaceObj);
        }
        //(4)调用暴露接口,实现计算
        func main06(){
            var opFactory OperaterFactory
            result1:=opFactory.DoOperate(100,80,"+");
            result2:=opFactory.DoOperate(100,80,"-");
            result3:=opFactory.DoOperate(100,80,"*");
            result4:=opFactory.DoOperate(100,80,"/");
            fmt.Println(result1,result2,result3,result4);//180 20 8000 1
        }

你可能感兴趣的:(go语言基础)