Notes
Search…
Notes
Introduce
Go
Grammar
System Library
Concurrency in Go
The Go Memory Model
Rust
The Rust Programming Language
Rust by Example
JAVA
Preface
Grammar
Class Libraries
Concurrency
JVM
Web Container
Spring
Tuning
Computer Science
Computer Organization
Algorithm
Network Protocol
Operating System
Design Patterns
UML
OOP
Principle
Refactoring & Specification
Creational
Structural
Behavioral
Microservice
Distributed System
database
InfluxDB
MySQL
Redis
Elasticsearch
HBase
Kafka
ZooKeeper
MongoDB
Reading
RocketMQ
演说之禅
Other
v2ray
Kubernetes
Git
Maven
Anaconda And Conda
Fuck! Shit!
Open source contribution
Powered By
GitBook
OOP
面向对象编程
(OOP,Object Oriented Programming)是一种编程范式或编程风格,以类(class)和对象(object)作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性作为代码设计和实现的基石。
四大特性
封装、抽象、继承、多态,有些人也认为只有三大特性,把抽象排除在外。
面向对象编程语言
(OOPL,Object Oriented Programming Language)是支持类或对象的语法机制,并有现成的语法机制,能方便地实现面向对象编程四大特性(封装、抽象、继承、多态)的编程语言。
按照严格的定义,很多语言都不是面 OOPL,比如 JavaScript 不支持封装和继承,Go 摒弃了继承。但是按照不严格的定义,很多语言都可以说是 OOPL,只要某种编程语言支持类或对象的语法概念,并且以此作为组织代码的基本单元,那就可以被粗略地认为它就是 OOPL 了。
OOP 一般使用 OOPL 来进行,但是,不用 OOPL,我们照样可以进行 OOP。反过来讲,即便我们使用 OOPL,写出来的代码也不一定是 OOP 风格的,也有可能是面向过程编程风格的。
面向对象分析
(OOA,Object Oriented Analysis)和
面向对象设计
(OOA,Object Oriented Design)的产出是类的设计,包括程序被拆解成哪些类、类有哪些属性方法、类于类之间的关系。
四大特性
封装(Encapsulation)
也叫信息隐藏或数据访问保护。封装的意义:
提高代码的可维护性。如果对类中的属性访问不做限制,那么则类不可控,属性被随意修改,修改的逻辑散落在代码各个角落。
类通过仅暴露必要的操作,可以提高类的易用性,调用者不需要了解过多的细节。
抽象(Abstraction)
封装讲的是如何隐藏信息、保护数据;而抽象讲的是隐藏方法的具体实现,调用者只关系方法提供了哪些功能,并不需要知道这些功能是怎么实现的。
通常使用接口类或抽象类两种语法机制实现。
上文讲到有时候抽象被排除在四大特性之外。原因如下: 实现抽象不一定需要接口类或抽象类,因为类的方法是通过编程语言的函数这一语法机制本省就是抽象,使用者在调用函数的时候,并不需要知道内部逻辑。
抽象的意义:
抽象和封装都是处理复杂性的有效手段,面对复杂系统,人脑有限,所以必须忽略掉非关键的细节。
提高代码的可扩展性、维护性,修改实现不需要修改定义。
继承(Inheritance)
继承表示类之间 is-a 的关系。继承分为单继承和多继承。
继承最大的好处是代码复用。不过,过度使用继承会导致代码的可读性,可维护性变差,父类和子类耦合度过高。所以继承这个特性非常有争议,我们应该尽量少用。
多态(Polymorphism)
多态是指子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。
多态的实现一般需要三种语法机制:
父类对象引用可以指向子类对象。
支持继承。
子类可以重写(override)父类方法。
多态可以提高代码的可扩展性和复用性。
面向过程
What?
面向过程编程
也是一种编程范式或编程风格。它以过程(方法、函数、操作)作为组织代码的基本单元,以数据与方法相分离为最主要的特点。面向过程风格是一种
流程化
的编程风格,通过拼接一组顺序执行的方法来操作数据完成一项功能。
面向过程编程语言
首先是一种编程语言。它最大的特点是不支持类和对象两个语法概念,不支持丰富的面向对象编程特性(比如继承、多态、封装),仅支持面向过程编程。
OOP 有哪些优势?
OOP 能够应对大规模复杂程序开发。对于简单程序,面向过程、面向对象差别不大,有时面向过程更简单。
OOP 更易复用、易扩展、易维护。由上文的四大特性可见。
OOP 语言更加人性化,更接近于人的思维,而机器语言、汇编、面向过程是按照计算机的思维方式。
本质是面向过程的代码
在实际开发中,不是把代码塞到类里,就表示在面向对象编程了。很多情况下,有很多表面上像面向对象的代码,本质上时面向过程的风格。
滥用 getter、setter 方法
我们经常会遇到定义完类后,随手就把所有 属性的 getter、setter 加上去,或者用 lombok 等。这样违反了面向对象封装的特性。如下面的例子:
@Data
public
class
ShoppingCart
{
private
int
itemsCount
;
private
double
totalPrice
;
private
List
<
ShoppingCartItem
>
items
=
new
ArrayList
<>
();
public
void
addItem
(
ShoppingCartItem
item
)
{
items
.
add
(
item
);
itemsCount
++
;
totalPrice
+=
item
.
getPrice
();
}
// ...省略其他方法...
}
上面的代码有很多问题:
items、totalPrice 定义为私有属性,但是又提供 getter、setter 方法,本质上就是 public 属性了。任何地方都可以修改这两个值,会造成和 items 的数据不一致问题。
items 提供了 getter 方法,返回的是 List,外部拿到这个容器后,可以修改容器内数据。
解决方案
:可以使用
Collections.unmodifiableList()
。
就算用不可变容器,还是有问题,因为用户拿到容器后,再拿容器内的某个元素,还是可以修改元素的某个字段值。
总结一下:
能不暴露 setter 方法就尽量不要暴露。
getter 方法如果返回的是容器或者类,也要注意内部的数据被修改。
滥用全局变量和全局方法
全局变量:单例类对象、静态成员变量、常量,比如 Constants;全局方法:静态方法,比如 Utils。
全局变量
我们会见到把程序中所有的常量都集中方法一个 Constants 类中,这种大而全的常量类并不好,原因是:
可维护性差,一个项目有很多工程师开发,那么就都可能修改这个类,这个类就会很大,查找修改某个常量也会比较耗时,还可能引起代码冲突。
增加编译时间,依赖这个 Constants 的类很多,所以只要这个常量类有修改,很多类都需要重新编译。
复用性差,如果有另一个项目也依赖 Constants 类,但是只依赖一小部分,这样就引入了很多无关的常量。
解决方案:
把大的 Constants 拆分,如 MySQLConstants、RedisConstants 等。
不设计 Constants 类,哪个类使用了常量,就把这个常量定义在这个类中。
全局方法
为什么会出现 Utils 类呢?因为存在一种情况,两个类有一段逻辑是重复的,虽然继承可以解决代码复用问题,但是这两个类又有没 is-a 的关系,这个时候就出现了 Utils 类。
我们并不是要测地杜绝 Utils 类,只是不要滥用。另外,Utils 也可以细化。
数据和方法分离
传统的 MVC 是 Model、Controller、View。前后端分离后,后端的三层结构变为 Controller、Service、Repository,在每一层都有对应的 VO、BO、Entity,其实这就是数据和方法分离。比如,BO 只定义了数据,而操作数据的方法都在 Service 中,这就是典型的面向过程的编程风格。
这种开发模式叫做基于
贫血模式
的开发方式。
面向过程的取舍
虽然上文讲了 OOP 的各种优势,也讲了哪些代码表面上是面向对象,本质上是面向过程。但是面向过程并不是完全无用武之地。
若是开发微小程序,或者做一些数据处理(算法为主、数据为辅),那么面向过程更加适合。
面向对象和面向过程可以并存的,甚至在一些标准库(JDK、Apache Commons、Guava)中都可以见到面向过程风格的代码。
所以,我们最终的目的是写出易维护、易读、易复用、易扩展的高质量代码。
贫血模型
MVC 架构指的是 Model、View、Controller,表示展示层、逻辑层、数据层,现在多是前后端分离,后端分为 Repository、Service、Controller 三层。
一般在写代码的时候,Entity 和 Repository 类组成数据层,Bo 和 Service 类组成业务逻辑层,Vo 和 Controller 类组成接口层。可以发现,Entity、Bo、Vo 是一个纯粹的数据结构,不包含任何业务逻辑,业务逻辑放在另一个类中,这是一种典型的面向过程的编程风格。像 Bo 这种只包含数据,不包含业务逻辑的类叫做
贫血模型
(Anemic Domain Model)。
充血模型
(Rich Domain Model)刚好相反,数据和业务被封装在同一个类中。基于充血模型的 DDD 开发与贫血模型的主要差别在于 Service 层,DDD 的 Service 层包含Service 类和 Domain 类,Domain 类包含数据和业务逻辑,而 Service 类很单薄。
为什么贫血模型这么受欢迎
?
系统业务简单,贫血模型足以应付,不需要费心思设计充血模型。
充血模型更有难度。
思维已固化,转型有成本。
那什么项目应该考虑用充血模型呢
?
业务复杂的系统,比如包含各种利息计算模型、还款模型等复杂业务的金融系统。
Previous
UML
Next
Principle
Last modified
2yr ago
Copy link
Outline
四大特性
封装(Encapsulation)
抽象(Abstraction)
继承(Inheritance)
多态(Polymorphism)
面向过程
What?
OOP 有哪些优势?
本质是面向过程的代码
面向过程的取舍
贫血模型