深度思考:到底什么是抽象?

简介: 深度思考:到底什么是抽象?

到底什么是抽象?

前言:

不要为了抽象而抽象。 ——《Tony Bai · Go 语言第一课》

1 回顾软件设计的六大原则

一、单一职责原则(SRP: Single responsibility principle)

二、开放封闭原则(OCP: Open Closed Principle)

三、里氏替换原则 ( LSP: Liskov Substitution Principle)

四、接口隔离原则( ISP: Interface Segregation Principle)

五、依赖倒置原则( DIP: Dependence Inversion Principle)

六、迪米特原则(Law of Demeter)

解释:

  • 单一职责原则:
    一个类/接口/方法只负责一项职责,并且有且只有一个需要被改变的理由。
  • 开放封闭原则:
    一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
  • 里式替换原则:
    子类可以替换父类,即子类可以扩展父类的功能,但是不能改变父类原有的功能。
  • 接口隔离原则:
    用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口,一个类对一个类的依赖应该建立在最小的接口上。
  • 依赖倒置原则:
    高层模块不应该依赖底层模块,二者都应该依赖其抽象。
  • 迪米特原则:
    一个对象应该对其他对象保持最少的了解,又叫最少知道原则。

2 抽象引入

在依赖倒置原则中有一个非常常见但又让你理解起来很模糊的名词——抽象

我们看下维基百科对抽象的解释:

抽象就是把一个问题或模型,以不同规则或方法所得出的不同的解(求解方法和解本身即抽象层),这些不同的解可以组合并还原成问题或模型的本身。

抽象的意义是可以忽略不是求解过程中必需的。例如要用计算机程序)去模拟“人”,在描述了人的动作(饮食、思考、移动等)符合设计要求后(如可完整表达“人”在坐下时候的动作),其他“人”的细节都可以忽略,以集中设计需要的功能,并减低程序的复杂度。

在程序设计中,高级语言是对机器指令序列的抽象。高级语言的出现,使得程序的编写得以简化,极大提高了程序的编写效率。随着软件技术的发展,组件技术进一步提升了程序抽象的级别。

另一种可取的替代方法是设计一种语言机制,允许程序师在需要的时候构建自己的抽象方法。一个通用的机制是使用过程(procedure)。通过分离过程的定义和规则,编程语言包含了两种重要的抽象方法:参数化抽象(abstraction by parameterization)和规范化抽象(abstraction by specification)。

程序设计中,抽象类别包括下列4类:

1.过程抽象:能够引入一些新的操作;

2.资料抽象:能够引入新的资料对象类型;

3.反复运算抽象:能够反复运算遍历在集合中的元素,而不必显示如何获得元素的细节;

4.类型层次:能够从多个单独的资料类型中抽象成几组相关的类型。

3 软件设计中的抽象举例

3.1 业务场景

在平时生活中我们每个人在加入一个新的群体前都会进行自我介绍,以此来让人们认识自己,比如一名老师会这样说:“大家好,我是一名老师,我叫XXX”,一名运动员会说:“大家好,我叫XXX,我的职业是一名运动员”等等,但是生活中总是不乏有有趣的人出现,比如突然来了一位程序员自我介绍:“Hello World,我叫XXX,来自中国,是一名Programmer”。

由上述我们可以得知,每个人的自我介绍一般都会先问好,然后介绍自己的职业,自己的姓名等等,于是我们可以将每个人的自我介绍进行提取和抽象,在大多数情况下每个人都可以依赖这个抽象,并且在自己有别出心裁的自我介绍时还可以将抽象具体化成自己的自我介绍。

因此,到这里我们就可以总结出可以抽象的部分问好、职业,(因为每个人都会先问好,并且很多人也都可能从事同一个职业,但是名字的抽象度并不高):

  • 问好
  • 职业

3.2 代码实现

提供抽象:

// Say 抽象接口
type Say interface {
  // SayHello 抽象方法
  SayHello() string
}
// PeopleSayHello 利用反射调用接口
func PeopleSayHello(dest interface{}) (string, error) {
  var sayHello string
  if dest == nil {
    return "dest is nil", nil
  }
  value := reflect.ValueOf(dest)
  if value.Kind() == reflect.Ptr && value.IsNil() {
    value = reflect.New(value.Type().Elem())
  }
  modelType := reflect.Indirect(value).Type()
  if modelType.Kind() == reflect.Interface {
    modelType = reflect.Indirect(reflect.ValueOf(dest)).Elem().Type()
  }
  modelValue := reflect.New(modelType)
  sayHello = DefaultSayHello(modelType.String())
  if say, ok := modelValue.Interface().(Say); ok {
    sayHello = say.SayHello()
  }
  return sayHello, nil
}
// DefaultSayHello 抽象的默认实现
func DefaultSayHello(str string) string {
  split := strings.Split(str, ".")
  return "Hello,I am a " + split[len(split)-1]
}
复制代码

使用抽象:

// Teacher 教师
type Teacher struct {
  Name string
  Age  int
}
// Student 学生
type Student struct {
  Name string
  No   string
  Age  int
}
// Sportsman 运动员
type Sportsman struct {
  Name string
  Age  int
  Like string
}
func (stu Student) SayHello() string {
  return "Hello,I am a Student and my name is Zs."
}
func (tea Teacher) SayHello() string {
  return "Hello,I am a Teacher and my name is Ls."
}
复制代码

运行:

func main() {
  teacherHello, err := PeopleSayHello(Teacher{})
  studentHello, err := PeopleSayHello(Student{})
  sportsmanHello, err := PeopleSayHello(Sportsman{})
  if err != nil {
    fmt.Println(err)
  }
  fmt.Println(teacherHello)
  fmt.Println(studentHello)
  fmt.Println(sportsmanHello)
}
复制代码

网络异常,图片无法展示
|

解释:

在整个代码中,我们使用接口定义了抽象,使用接口的方法定义了抽象方法,并且通过反射能够让抽象更加的通用,进而形成抽象层,将人们习惯的方式进行统一,但是通过提供抽象的具体化让每个角色能使用自己的具体方法。

4 探究反射和抽象

"反射是框架设计的灵魂"

我们都知道架构的设计高度依赖于反射这个特性,可以说反射是框架的灵魂,究其原因,反射是因为有以下这个概念:

反射用于观察并修改程序在运行时的行为。一个反射导向的程序组件可以监测一个范围内的代码执行情况,可以根据获取的目标对象信息及与此相关的范围修改自身。这可通过在运行时动态分配程序代码实现。

反射主要用途就是使给定的程序,动态地适应不同的运行情况。利用面向对象建模中的多态(多态性)也可以简化编写分别适用于多种不同情形的功能代码,但是反射可以解决多态(多态性)并不适用的更普遍情形,从而更大程度地避免硬编码(即把代码的细节“写死”,缺乏灵活性)的代码风格。

反射也是元编程的一个关键策略。

反射本身很抽象,反射让架构更加抽象。

反射本身是抽象的,我认为反射的抽象在于他能在有条件的情况下取出运行时的对象状态,进而作出判定或修改。

反射的最大优势在于动态地适应不同的运行情况,进而解决多态,比如在上面的代码中,无论实例化的结构体类型是Teacher、Student还是Sportsman,反射构成的方法都可以当做interface{}类型进行处理,进而能让方法更加的抽象化,这种方式在架构设计和编码中也非常的常见,如Java的JDBC抽象、Spring IOC抽象等等。

5 更高级的抽象

对于计算机领域中,我认为更高级的抽象是数学

德国数学家Hermann Weyl曾说:“数学抽象中最关键的一步让我们忘记这些符号所表示的对象,有许多操作可以应用于这些符号,而根本不必考虑他们到底代表着什么。”数学离不开抽象,架构的建模过程同样离不开抽象。

对于数学和软件架构的关系,因为本人目前的阅历也是较为浅薄,所以在此先一笔带过,待日后经验更加丰富,思想更加有深度时再回来补充。

6 总结

文章的开头前言就写出了一句话:“不要为了抽象而抽象”

确实是这样的,盲目的抽象只会让系统过度设计,而我们要做的就是懂得适度权衡。

为什么要抽象?这是一个值得思考的问题,而我认为抽象最大的好处就是:

  • 能够最大限度的减少重复
  • 基于抽象可以灵活的扩展

所以只有当我们的设计能够达到可抽象的要求是才适合使用抽象,而本来的简单和精巧的设计不一定非要进行抽象。

以上就是个人对抽象的全部理解,欢迎读者留言~

参考文章:

zh.wikipedia.org/wiki/%E6%8A…

blog.csdn.net/qq_26900081…

blog.csdn.net/u014681799/…

zh.wikipedia.org/wiki/%E5%8F…


相关文章
|
Dubbo Java 应用服务中间件
由浅入深RPC通信原理实战1
由浅入深RPC通信原理实战1
233 0
|
存储 JavaScript
解锁Vuex高级玩法:模块化与插件共舞,让你的Vue项目状态管理如虎添翼!
【8月更文挑战第27天】Vuex是一款专为Vue.js应用程序设计的状态管理模式及库,它通过集中管理组件状态来确保状态变更的可预测性。在大型应用中,采用模块化管理可以让代码结构更加清晰,同时利用插件增强功能。模块化管理允许将store拆分为包含各自state、mutations、actions和getters的独立模块。插件则能监听状态变化,实现诸如日志记录或数据持久化等功能。本文通过具体示例介绍了如何在Vuex中实现模块化管理和插件的高级应用。
243 1
|
存储 Java 编译器
心得经验总结:源代码、目标代码、可执行代码、本地代码的区别
心得经验总结:源代码、目标代码、可执行代码、本地代码的区别
1071 0
|
UED 开发者
Harmony Next 动画大全01-属性动画
Harmony Next 动画大全01-属性动画
211 2
Harmony Next 动画大全01-属性动画
|
5天前
|
云安全 人工智能 算法
以“AI对抗AI”,阿里云验证码进入2.0时代
三层立体防护,用大模型打赢人机攻防战
1339 4
|
2天前
|
存储 弹性计算 应用服务中间件
2026年阿里云服务器新手租用全流程完整步骤教程(最新版)
2026年阿里云服务器新手租用全流程完整步骤教程,阿里云服务器提供自定义租用、一键租用、云市场租用和活动租用四种核心方式,适配不同配置需求、技术能力和预算场景。无论是需要精准配置的专业用户,还是追求快速部署的新手,都能找到合适的租用方案。以下是详细的适用场景和操作流程,助力高效上云。
270 146
|
6天前
|
人工智能 Rust 运维
这个神器让你白嫖ClaudeOpus 4.5,Gemini 3!还能接Claude Code等任意平台
加我进AI讨论学习群,公众号右下角“联系方式”文末有老金的 开源知识库地址·全免费
|
2天前
|
弹性计算 固态存储 大数据
2026年阿里云服务器租用费用_阿里云最新轻量、ECS、GPU云服务器价格表
2026年阿里云服务器租用费用多少钱?小编通过查询全网最新关于阿里云服务器租用价格的资料,整理了今年最新的云服务器租用价格表,包括轻量应用服务器、云服务器ECS和GPU服务器。现在最新阿里云服务器租用费用价格表,轻量2核2G轻量服务器一年68元,折合5.6元1个月,新老用户同享99元一年服务器,2核4G5M服务器ECS优惠价199元一年(企业专享),2核4G4M轻量服务器298元一年,4核8G服务器955元一年,4核16G10M服务器70元1个月、210元3个月,8核32G服务器160元1个月、480元3个月,整理2026阿里云服务器租用费用价格表,包括一年优惠价格、一个月和1小时收费明细表:
225 152