• 面向对象编程有四大特性


    面向对象编程有四大特性

    四大特性

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

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

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

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

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

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

    结论

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

  • 相关阅读:
    《持续交付:发布可靠软件的系统方法》- 读书笔记(十二)
    重新认识交叉编译
    Hadoop3教程(二十一):MapReduce中的压缩
    高级数据结构与算法 | 布谷鸟过滤器(Cuckoo Filter):原理、实现、LSM Tree 优化
    力扣刷题-哈希表-哈希表理论基础
    酪氨酸激酶、自噬等抗肿瘤抑制剂
    数学术语之源——“齐次(homogeneity)”的含义
    【JavaScript】完善注册表单校验&案例2:表格隔行换色
    SpringMVC的拦截器和JSR303的使用
    GitKraken Pro安装
  • 原文地址:https://blog.csdn.net/qq_53267860/article/details/126282545