面向对象编程有四大特性

面向对象编程有四大特性

四大特性

  1. 封装特性

    封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式来访问内部信息或者数据。它需要编程语言提供权限访问控制语法来支持,例如Java中的private、protected、public关键字。封装特性存在的意义,一方面是保护数据不被随意修改,提高代码的可维护性;另一方面是仅暴露有限的必要接口,提高类的易用性。

  2. 抽象特性

    封装主要讲如何隐藏信息、保护数据,那抽象就是讲如何隐藏方法的具体实现,让使用者只需要关心方法提供了哪些功能,不需要知道这些功能是如何实现的。抽象可以通过接口类或者抽象类来实现,但也并不需要特殊的语法机制来支持。抽象存在的意义,一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息。

  3. 继承特性

    继承是用来表示类之间的is-a关系,分为两种模式:单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类。为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持。继承主要是用来解决代码复用的问题。

  4. 多态特性

    多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。多态这种特性也需要编程语言提供特殊的语法机制来实现,比如继承、接口类、duck-typing。多态可以提高代码的扩展性和复用性,是很多设计模式、设计原则、编程技巧的代码实现基础。

实现

想要用面向对象编程来实现业务逻辑,必然需要掌握的是如何使用这门语言实现四大特性。在掌握了这份知识的基础上,才有可能灵活的使用设计模式,设计出好的架构。今后学习新的语言的时候,对于面向对象的实现,需要成为关注点之一。看下面的内容需要掌握基本的Go语言知识。

封装

Go中封装的实现方法为使用结构体,并给结构体添加相应的方法。

一般的实现步骤如下:

  1. 将结构体、字段的首字母小写;
  2. 给结构体所在的包提供一个工厂模式的函数,首字母大写,类似一个构造函数;
  3. 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值;
  4. 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值。

举例:对于员工,不能随便查看年龄,工资等隐私,并对输入的年龄进行合理的验证。代码结构如下

package model

import "fmt"

type person struct {
  Name string
  age  int //其它包不能直接访问..
  sal  float64
}

//写一个工厂模式的函数,相当于构造函数
func NewPerson(name string) *person {
  return &person{
     Name: name,
  }
}

//为了访问age 和 sal 我们编写一对SetXxx的方法和GetXxx的方法
func (p *person) SetAge(age int) {
  if age > 0 && age < 150 {
     p.age = age
  } else {
     fmt.Println("年龄范围不正确..")
     //给程序员给一个默认值
  }
}
func (p *person) GetAge() int {
  return p.age
}

func (p *person) SetSal(sal float64) {
  if sal >= 3000 && sal <= 30000 {
     p.sal = sal
  } else {
     fmt.Println("薪水范围不正确..")
  }
}

func (p *person) GetSal() float64 {
  return p.sal
}

关于这个代码,有两点需要说明

  1. 关于大小写

    • 小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。这个规则也适用于类型和变量的可见性。
    • 大小写影响了可见性,大写字母开头等同于public,小写字母开头等同于private,这种做法不仅免除了public、private关键字,更重要的是统一了命名风格。
  2. 结构体方法参数

    • 如果要求对象必须以指针传递,这有时会是个额外成本,因为对象有时很小(比如4字节),用指针传递并不划算。
    • 只有在你需要修改对象的时候,才必须用指针。

继承

Go语言根本就不支持面向对象思想中的继承语法。

从另一个维度而言,Go语言也提供了继承,但是采用了组合的文法,所以我们将其称为匿名组合。

举例:

package main

import "fmt"

type Base struct {
  Name string
}

func (b *Base) SetName(name string) {
  b.Name = name
}

func (b *Base) GetName() string {
  return b.Name
}

// 组合,实现继承
type Child struct {
  base Base // 这里保存的是Base类型
}

// 重写GetName方法
func (c *Child) GetName() string {
  c.base.SetName("modify...")
  return c.base.GetName()
}

// 实现继承,但需要外部提供一个Base的实例
type Child2 struct {
  base *Base // 这里是指针
}

//
type Child3 struct {
  Base
}

type Child4 struct {
  *Base
}

func main() {
  c := new(Child)
  c.base.SetName("world")
  fmt.Println(c.GetName())

  c2 := new(Child2)
  c2.base = new(Base) // 因为Child2里面的Base是指针类型,所以必须提供一个Base的实例
  c2.base.SetName("ccc")
  fmt.Println(c2.base.GetName())

  c3 := new(Child3)
  c3.SetName("1111")
  fmt.Println(c3.GetName())

  c4 := new(Child4)
  c4.Base = new(Base)
  c4.SetName("2222")
  fmt.Println(c4.GetName())
}

关于这段代码,有几点需要说明

  1. 可以使用匿名组合和非匿名组合,如果使用非匿名组合,则调用的时候需要显示指定变量名称。如果使用匿名组合,则无需显示指定变量名称,当然也可以显示调用,如c3.Base.Name。
  2. 在Go语言中,还可以以指针方式从一个类型“派生”:如Child4,这段Go代码仍然有“派生”的效果,只是Child4创建实例的时候,需要外部提供一个Base类实例的指针。
  3. 在“派生类”Child3没有改写“基类”Base的成员方法时,相应的方法就被“继承”,例如在上面的例子中,调用c3.GetName()和调用c3.Base.GetName()效果一致。在“派生类”Child改写“基类”Base的成员方法,c.GetName()会调用派生类的方法,如果想调用基类的方法,可以显示调用。
  4. 如果“派生类”和“基类”有相同的变量名Name,所有的派生类的Name成员的访问都只会访问到最外层的那个Name变量,基类的Name变量相当于被覆盖了,可以用显示引用。

多态

在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。如果类实现了接口,便可将对象实例赋值给接口。

举例:

package main

import "fmt"

type Money interface {
  show() string
}

type OldMoney struct {
}

func (oldMoney *OldMoney) show() string {
  return "I am old money"
}

type NewMoney struct {
}

func (newMoney *NewMoney) show() string {
  return "I am new money"
}

func PrintMoney(l []Money) {
  for _, item := range l {
     fmt.Println(item.show())
  }
}

func main() {
  moneyList := []Money{new(OldMoney), new(NewMoney), new(OldMoney)}
  PrintMoney(moneyList)
}

关于这段代码,有几点需要说明:

  1. 接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集,那么接口B可以赋值给接口A。

  2. Go语言可以根据下面的函数:func (oldMoney OldMoney) show() string自动生成一个新的show()方法:func (oldMoney *OldMoney) show() string,所以赋值时使用引用还是对象,需要考虑函数参数的类型。

结论

简单梳理了一下Go语言的特性,使用Go做面向对象编程及其简单。后面会用Go实现23种设计模式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值