回顧
先回顧下面向對象的三個基本特性:
- 封裝
- 繼承
- 多態
Java作爲面向對象的王者,以下示例完美的展現了面向對象的三個基本特徵。
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)叫做方法的接收者,有點怪異,我們可以這樣理解:
- Go裏沒有this,要自己加個類似this的東西,用於指代方法對應的實例,括號裏前面的e相當於this,當然名字可以隨便取。
- 方法定義和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())
}