《设计模式之美》(2)面向对象理解和实战

一、面向对象是什么

记录学习王铮的课程笔记:《设计模式之美》,感兴趣的可以购买下面链接课程。

https://time.geekbang.org/column/intro/250

面向对象有两个名称概念:
(1)面向对象编程
(2)面向对象的编程语言
面向对象编程的英文缩写是OOP,全称是 Object Oriented Programming。 面向对象编程有两个非常重要、基础的概念:那就是类(class)和对象(object);同时面向对象编程拥有四大特性:封装、抽象、继承、多态。然后,市面上流行的Java、Python,C#等等就是典型的面向对象的编程语言。

  • 面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石 。
  • 面向对象编程的语言(如:Java、Python,C#)是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。

与面向对象编程相关的还有两个概念,那就是面向对象分析(OOA)和面向对象设计(OOD)。面向对象分析英文缩写是 OOA,全称是 Object Oriented Analysis;面向对象设计的英文缩写是 OOD,全称是 Object Oriented Design。OOA、OOD、OOP 三个连在一起就是面向对象分析、设计、编程(实现),正好是面向对象软件开发要经历的三个阶段。简单点说明:

  • 面向对象分析就是搞清楚做什么(根据原型图分析页面上设计成几个类,也就是定义接口的过程,每个类有哪些属性和方法)
  • 面向对象设计就是搞清楚怎么做(根据设计好的类再去设计出类与类之间如何做交互)
  • 面向对象编程就是将分析和设计得结果翻译成代码。(最后根据设计好的类,和类的交互逻辑实现具体的业务代码)

提到面向对象,我们不得不提一个概念:UML(Unified Model Language),统一建模语言。我们常用他来画图表达面向对象或者设计模式的设计思路,但是UML的规范要求比较大,学习成本也是。所以更多时候,我们能够画出类似UML规范的图,能够表达清楚自己的意思即可。如果想要学习,推荐一本书《大话设计模式》,里面讲的挺好的。

二、面向对象的特性

1、封装特性

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数,或者叫方法)来访问内部信息或者数据。封装主要讲究的是如何隐藏信息、保护数据。

对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制。 private、public 等关键字就是 Java 语言中的访问权限控制语法。

private 关键字修饰的属性只能类本身访问,可以保护其不被类之外的代码直接访问。如果 Java 语言没有提供访问权限控制语法,所有的属性默认都是 public 的,那任意外部代码都可以通过类似 实例对象.id=123; 这样的方式直接访问、修改属性,也就没办法达到隐藏信息和保护数据的目的了,也就无法支持封装特性了。

封装的意义:类仅仅通过有限的方法暴露必要的操作,提高类的易用性。同时,保证代码的可读性和可维护性
封装解决了什么编程问题:减少类的调用者负担,屏蔽掉业务细节,减少其使用错的概率。防止其他开发人员在不了解业务逻辑的情况下,修改类里面的一些字段值,导致数据异常情况。(这就好比,如果一个冰箱有很多按钮,你就要研究很长时间,还不一定能操作正确。相反,如果只有几个必要的按钮,比如开、停、调节温度,你一眼就能知道该如何来操作,而且操作出错的概率也会降低很多。)

2、抽象特性

抽象讲的是如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

在面向对象编程中,我们常借助编程语言提供的接口类(比如 Java 中的 interface 关键字语法)或者抽象类(比如 Java 中的 abstract 关键字语法)这两种语法机制,来实现抽象这一特性。

实际上,抽象这个特性是非常容易实现的,并不需要非得依靠接口类或者抽象类这些特殊语法机制来支持。之所以这么说,那是因为,类的方法是通过编程语言中的“函数”这一语法机制来实现的。通过函数包裹具体的实现逻辑,这本身就是一种抽象。调用者在使用函数的时候,并不需要去研究函数内部的实现逻辑,只需要通过函数的命名、注释或者文档,了解其提供了什么功能,就可以直接使用了。

抽象这个概念是一个非常通用的设计思想,并不需要编程语言提供特殊的语法机制来支持,只需要提供“函数”这一非常基础的语法机制,就可以实现抽象特性。

抽象的意义:解决了我们在面对复杂系统的时候阅读、维护代码的难度,保证了代码的可读性,可维护性。
抽象解决了什么问题:让我们能够忽略掉非关键的实现细节,过滤掉无用信息,只聚焦于方法的功能,对于方法内部的细节可以忽略。(例如:我们在定义(或者叫命名)类的方法的时候,也要有抽象思维,不要在方法定义中,暴露太多的实现细节,以保证在某个时间点需要改变方法的实现逻辑的时候,不用去修改其定义。举个简单例子,比如 getAliyunPictureUrl() 就不是一个具有抽象思维的命名,因为某一天如果我们不再把图片存储在阿里云上,而是存储在私有云上,那这个命名也要随之被修改。相反,如果我们定义一个比较抽象的函数,比如叫作 getPictureUrl(),那即便内部存储方式修改了,我们也不需要修改命名。)

3、继承特性

继承是用来表示类之间的 is-a 关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物,又是爬行动物。

为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如 Java 使用 extends 关键字来实现继承,而Java则只支持继承。

继承的意义:反应类之间的真实关系,增加代码的可读性,同时保证了代码的可复用性。
继承解决了什么问题:最大的作用就是代码复用,假如两个类有一些相同的属性和方法,我们就可以将这些相同的部分,抽取到父类中,让两个子类继承父类。这样,两个子类就可以重用父类中的代码,避免代码重复写多遍。

4、多态特性

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。
多态这种特性也需要编程语言提供特殊的语法机制来实现。
(1)第一个语法机制是编程语言要支持继承
(2)第二个语法机制是编程语言要支持父类对象可以引用子类对象
(3)第三个语法机制是编程语言要支持子类可以重写(override)父类中的方法

//代码例子说明
//子类是动态数组
List arrayList = new ArrayList();
arrayList.add("first");
arrayList.add("second");
//子类是链表
List linkList = new LinkList();
linkList.add("first");
linkList.add("second");
String first1 = getFirstNode(arrayList);
String first2 = getFirstNode(linkList);
//不做集合是否为空的判断
public String getFirstNode(List list){
	return list.get(0);
}

多态的意义:提高代码的可拓展性和复用性。
多态解决了什么问题:利用的多态的特性,我们能够提高代码的复用性。例子中:利用多态的特性,我们只需要写一个getFirstNode(List list)函数,就能满足动态数组和链表获取第一个元素的逻辑,能够应对各种线性表数据。

三、与面向过程编程对比

1.面向过程编程概念

面向过程编程也是一种编程范式或编程风格。它以过程(可以理解为方法、函数、操作)作为组织代码的基本单元,以数据(可以理解为成员变量、属性)与方法相分离为最主要的特点。面向过程风格是一种流程化的编程风格,通过拼接一组顺序执行的方法来操作数据完成一项功能。
面向过程编程语言首先是一种编程语言。它最大的特点是不支持类和对象两个语法概念,不支持丰富的面向对象编程特性(比如继承、多态、封装),仅支持面向过程编程。
面向过程和面向对象最基本的区别:
(1)代码的组织方式不同。面向过程风格的代码被组织成了一组方法集合及其数据结构,方法和数据结构的定义是分开的。
(2)面向对象风格的代码被组织成一组类,方法和数据结构被绑定一起,定义在类中。

2.面向对象的优势

(1)面向对象更加能够应对复杂系统的开发

  • 面向对象编程是以类为思考对象,遇到复杂的流程,我们是先思考如何给业务建模,接着将需求翻译为类,然后给类之间建立交互关系,最后再去具体实现里面的类的业务代码。这样的开发模式、思考问题的方式,能够让我们再应付复杂程序开发的时候,思路更加清晰。

(2)面向对象的代码易复用、易拓展、易维护

  • 面向对象编程提供一种更加清晰的、更加模块化的代码组织方式。一个复杂的金融交易系统,代码量很大,业务复杂。我们可以将根据函数的功能,数据结构封装到对应的类中,因此类是一种非常好将代码模块化的手段,能够增加代码的可复用性;将数据和方法绑定在一起,限制像面向过程编程那样,数据可以在任意方法随意修改,增加代码的可读性,以及可维护性。
  • 面向对象提供的基于接口的抽象特性,可以让我们在不改变原有的实现的情况下,轻松替换新的实现逻辑,提高代码的可拓展性。
  • 面向对象提供的多态特性,让我们在需要修改一个功能实现的时候,可以通过实现一个新的子类的方式,在子类中重写原来的功能逻辑,用子类替换父类。在实际的代码运行过程中,调用子类新的功能逻辑,而不是在原有代码上做修改。这就遵从了“对修改关闭、对扩展开放”的设计原则,提高代码的扩展性。除此之外,利用多态的特性,不同的类对象可以传递给相同的方法,执行不同的代码逻辑,提高了代码的复用性。

(3)面向对象语言更加人性化、高级、智能

  • 跟二进制指令、汇编语言、面向过程编程语言相比,面向对象编程语言的编程套路、思考问题的方式,是完全不一样的。前三者是一种计算机思维方式,而面向对象是一种人类的思维方式。我们在用前面三种语言编程的时候,我们是在思考,如何设计一组指令,告诉机器去执行这组指令,操作某些数据,帮我们完成某个任务。进行面向对象编程时候,我们是在思考,如何给业务建模,如何将真实的世界映射为类或者对象,这让我们更加能聚焦到业务本身,而不是思考如何跟机器打交道。

四、看似面向对象代码,其实是面向过程

1.滥用getter和setter方法

定义完类之后,使用Lombok插件自动生成所有属性的getter、setter方法。这其实违反了面向对象编程的封装特性,相当于将面向对象编程风格退化成了面向过程编程。

  • 暴露许多没有意义的方法,将所有定义成private的私有属性都提供了getter和setter方法,那其实跟定义成public公有属性,没什么两样了,因为外部可以通过setter方法随意修改这两个属性的值。
  • 面向对象封装的意义是在于:通过对访问权限的控制,隐藏内部数据,外部仅能通过类提供的有限接口访问、以及修改数据。所以,暴露不应该暴露的setter方法,明显潍坊了面向对象的封装特性。

2.滥用全局变量和全局方法

在面向对象编程中,常见的全局变量有单例类对象、静态成员变量、常量等,常见的全局方法有静态方法。如下:
(1)单例类对象在全局代码中只有一份,所以,它相当于一个全局变量。
(2)静态成员变量归属于类上的数据,被所有的实例化对象所共享,也相当于一定程度上的全局变量。
(3)而常量是一种非常常见的全局变量,比如一些代码中的配置参数,一般都设置为常量,放到一个 Constants 类中。
(4)静态方法一般用来操作静态变量或者外部数据。你可以联想一下我们常用的各种 Utils 类,里面的方法一般都会定义成静态方法,可以在不用创建对象的情况下,直接拿来使用。
然而静态方法将方法与数据分离,破坏了封装特性,是典型的面向过程风格,但其实Utils类在软件开发中还是很有作用的,能解决代码复用问题,我们并不排斥使用,但是在定义之前多去思考,是否真的有必要。然后对于常量Constants类有以下建议:
(1)将Constants类拆解为功能单一的多个类,比如跟 MySQL 配置相关的常量,我们放到 MysqlConstants 类中;跟 Redis 配置相关的常量,我们放到 RedisConstants 类中。这样处理的话,某个项目依赖于其中的一个Constants类只需要引入特定类型的常量,而不会把大而全的Constants类引入。
(2)如果这些常量只是特定的某个类使用,其实我们可以把常量定义到类中,提高了类设计的内聚性和代码的复用性。

3.定义数据和方法分离的类

数据定义在一个类中,方法定义在另一个类中。传统的 MVC 结构分为 Model 层、Controller 层、View 层这三层。不过,在做前后端分离之后,三层结构在后端开发中,会稍微有些调整,被分为 Controller 层、Service 层、Repository 层。Controller 层负责暴露接口给前端调用,Service 层负责核心业务逻辑,Repository 层负责数据读写。而在每一层中,我们又会定义相应的 VO(View Object)、BO(Business Object)、Entity。一般情况下,VO、BO、Entity 中只会定义数据,不会定义方法,所有操作这些数据的业务逻辑都定义在对应的 Controller 类、Service 类、Repository 类中。这就是典型的面向过程的编程风格。
实际上,这种开发模式叫作基于贫血模型的开发模式,也是我们现在非常常用的一种 Web 项目的开发模式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

L-百川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值