Go語言沒有類怎麼面向對象

回顧

先回顧下面向對象的三個基本特性:

  • 封裝
  • 繼承
  • 多態

Java作爲面向對象的王者,以下示例完美的展現了面向對象的三個基本特徵。

uml.png

public class Main {
    public static void main(String[] args) {
        List<Payroll> all = new ArrayList<>();

        //員工類封裝了數據(姓名、年齡、性別)和行爲(計算薪酬)
        //銷售員工和技術員工繼承自員工類,都實現了計算薪酬的接口
        //機器不是員工,但實現了計算薪酬的接口,也可以計算薪酬
        all.add(new SaleEmployee());
        all.add(new TechEmployee());
        all.add(new Machine());

        //多態:不管是人還是機器,只要實現了Payroll接口,就都可以正確的計算出各自的薪酬
        for (Payroll payroll : all) {
            System.out.println(payroll.calcSalary());
        }
    }
}

那麼Go語言呢?我們一起來探索下Go語言的面向對象,先從類的封裝開始。

封裝

員工薪酬示例中,Java裏的Employee類是這樣的:

public abstract class Employee implements Payroll {
    private String name;
    private String sex;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

這裏暫時先關注員工類的三個屬性,Go裏面它確實沒有類,要表示上面的員工對象,它是用struct的(和C很像):

type Employee struct {
	Name string
	Sex string
	Age int
}

看起來簡潔多了,注意到Go裏面沒有private public的修飾符,它是用屬性的首個字母大小寫來區分private public,小寫字母開頭的是私有屬性,大寫字母開頭表示公開屬性。對象的實例化和使用對比:

//由於Employee是抽象類,不能實例化,直接用子類TechEmployee實例化
Employee tech = new TechEmployee();
tech.setName("lee");
System.out.println(tech.getName());
//這裏的object.是包名,Employee放在object目錄下
tech := object.Employee{
    Name: "lee",
    Sex:  "male",
    Age:  20,
}
tech.Name = "bruce lee"
fmt.Println(tech.Name)

另外,struct中是沒有方法(行爲)的,少了這個不是等於封裝特徵缺了只腳嗎?當然不是了,Go語言裏對應面向對象裏的成員方法不是定義在struct裏面,而是直接定義在struct外面,和struct平級,這裏定義一個類似Java中的toString方法:

type Employee struct {
	Name string
	Sex  string
	Age  int
}

func (e *Employee) ToString() string {
	return "name=" + e.Name + ";sex=" + e.Sex + ";age=" + strconv.Itoa(e.Age)
}

這裏(e *Employee)叫做方法的接收者,有點怪異,我們可以這樣理解:

  1. Go裏沒有this,要自己加個類似this的東西,用於指代方法對應的實例,括號裏前面的e相當於this,當然名字可以隨便取。
  2. 方法定義和struct平級,如果不加個接收者定義,哪裏知道這個方法屬於誰的呢,括號裏後面的類型表示這個方法屬性於誰的,這裏可以用(e Employee)或(e *Employee),區別是傳值還是傳指針,一般統一用後者。

繼承

接下來繼承,先看看Java的:

public class TechEmployee extends Employee {
    private double salaryPerMonth;

    public double getSalaryPerMonth() {
        return salaryPerMonth;
    }

    public void setSalaryPerMonth(double salaryPerMonth) {
        this.salaryPerMonth = salaryPerMonth;
    }

    @Override
    public double calcSalary() {
        return salaryPerMonth;
    }
}

public class SaleEmployee extends Employee {
    private Double baseSalary;
    private Double extraRate;

    public Double getBaseSalary() {
        return baseSalary;
    }

    public void setBaseSalary(Double baseSalary) {
        this.baseSalary = baseSalary;
    }

    public Double getExtraRate() {
        return extraRate;
    }

    public void setExtraRate(Double extraRate) {
        this.extraRate = extraRate;
    }

    @Override
    public double calcSalary() {
        return baseSalary * (1 + extraRate);
    }
}

同樣,Go裏面也沒有像Java中類似extend繼承的語法,Go是用了類似Java裏組合的東西來讓語法看起來像繼承:

type TechEmployee struct {
	Employee
	SalaryPerMonth float32
}

type SaleEmployee struct {
	Employee
	BaseSalary float32
	ExtraRate  float32
}

對應的實例化和使用:

//實例化時,是傳了個employee
tech := object.TechEmployee{
    Employee:       object.Employee{Name: "lee"},
    SalaryPerMonth: 10000,
}
//這裏看起來像擁有了Employee的name屬性,可以設置和訪問
tech.Name = "bruce lee"
fmt.Println(tech.Name)

多態

關於多態,必須要提接口,終於Go裏也是有接口的了:

public interface Payroll {
    double calcSalary();
}
type Payroll interface {
	CalcSalary() float32
}

接口的實現:

public class Machine implements Payroll {
    @Override
    public double calcSalary() {
        return 0;
    }
}
type TechEmployee struct {
	Employee
	SalaryPerMonth float32
}

func (e *TechEmployee) CalcSalary() float32 {
	return e.SalaryPerMonth
}

type Machine struct {

}

func (e *Machine) CalcSalary() float32 {
	return 0
}

可以看出,Java裏比較直觀,語法裏直接寫着實現xxx接口,Go相比的話,沒那麼直觀,但更靈活,它沒有指定實現哪個接口,而是如果定義了一個相同名字和返回值的方法,就認爲是實現了對應擁有這個方法的接口,這裏假如接口有兩個方法,對應也必須要兩個方法都有定義了,才認爲是實現了接口。

最後,看一下集成使用的對比:

List<Payroll> all = new ArrayList<>();

//員工類封裝了數據(姓名、年齡、性別)和行爲(計算薪酬)
//銷售員工和技術員工繼承自員工類,都實現了計算薪酬的接口
//機器不是員工,但實現了計算薪酬的接口,也可以計算薪酬
all.add(new SaleEmployee());
all.add(new TechEmployee());
all.add(new Machine());

//多態:不管是人還是機器,只要實現了Payroll接口,就都可以正確的計算出各自的薪酬
for (Payroll payroll : all) {
    System.out.println(payroll.calcSalary());
}
var all [3] object.Payroll

all[0] = &object.TechEmployee{
    Employee:       object.Employee{Name: "lee"},
    SalaryPerMonth: 10000,
}
all[1] = &object.SaleEmployee{
    Employee:   object.Employee{Name: "lee"},
    BaseSalary: 10000,
    ExtraRate:  0.1,
}
all[2] = &object.Machine{}

for _, item := range all {
    fmt.Println(item.CalcSalary())
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章