序
从大学毕业开始,已经工作了一年多的时间,在一家公司做了一年JAVA工程师(CRUD工程师),最近学习了极客时间的王争老师的《设计模式之美》,深有体会,特此记录一些自己看完文章的总结笔记,方便日后查阅,这里将会每读完一章节的文章后,对对应的文章梳理总结提炼,加深自己的印象,如有侵权,请联系删除,
这一章节将会理解 “设计原则与思想:面向对象”, 会从 是什么,为什么,怎么做三个角度去剖析面向对象这一概念,文章如有不足,欢迎指出,
理解什么面向对象(What)
我们一直说面向对象面向对象,那这个面向对象到底是啥,是我们相亲对象,还是象棋中的一对象,还是我们处的对象,在我们程序中就是我们new 出来的一个对象。然后我们这个对象定义是由我们程序员自己定义,从简单的整数对象到复杂的飞机都可以被我们程序员看作对象,它不仅仅能表示具体是事物,我们还能表示抽象的规则,计划或时间,具体定义来说,
对象:指的是客体,所谓的客体是指客观存在对象实体和主观抽象的概念
面向对象:就是我们要从对象的角度去思考问题,去思考如何解决需求业务的问题,
面向对象编程:面向对象编程是一种编程范式或编程风格,它以类或对象(对象)作为组织代码(思考)的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石
面向对象的作用(Why)
面向对象是为了解决系统的可维护性、可扩展性,可重用性,我们进一步思考,面向对象为什么能解决系统可维护性,可扩展性,可重用性?
面向对象产生的历史原因有下面两点:
1、 计算机是帮助人们解决问题的,然而计算机终究是个机器,他只会按照人所写的代码,一步一步的执行下去,最终得到了结果,因此无论程序多么的复杂,计算机总是能轻松应付,结构化编程,就是按照计算机的思维写出的代码,但是人看到这么复杂的逻辑,就无法维护和扩展了。
2、 结构化设计是以功能为目标来设计构造应用系统,这种做法导致我们设计程序时,不得不将客体所构成的现实世界映射到由功能模块组成的解空间中,这种转换过程,背离了人们观察和解决问题的基本思路。
就像下面图示所示,计算机会按照我们给排定的步骤,一步一步的执行
可见结构化设计在设计系统的时候,无法解决重用、维护、扩展的问题,而且会导致逻辑过于复杂,代码晦涩难懂。于是人们就想,能不能让计算机直接模拟现实的环境,用人类解决问题的思路,习惯,步骤来设计相应的应用程序,这样的程序,人们在读它的时候,会更容易理解,也不需要再把现实世界和程序世界之间来回做转换。
与此同时,人们发现,在现实世界中存在的客体是问题域中的主角,所谓客体是指客观存在的对象实体和主观抽象的概念,这种客体具有属性和行为,而客体是稳定的,行为不稳定的,同时客体之间具有各种联系,因此面向客体编程,比面向行为编程,系统会更稳定,在面对频繁的需求更改时,改变的往往是行为,而客体一般不需要改变,所以我们就把行为封装起来,这样改变时候只需要改变行为即可,主架构则保持了稳定。
于是面向对象就产生了。
然而人们追求的系统可维护性,可扩展性,可重用性又是怎么在面向对象中体现出来的呢?
首先看看面向对象的特征:
封装:隐藏信息,保护数据,就像上图所示,我们在面向对象编程中,会将现实世界中存在的客体(人),封装成编程中的一个对象,一些内置的信息数据,比如年龄,性别等数据封装在这个对象中,
抽象:隐藏方法的具体是实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的
继承:子类继承父类,可以继承父类的方法及属性,实现了多态以及代码的重用,因此也解决了系统的重用性和扩展性,但是继承破坏了封装,因为他是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,所以继承需要慎用,只有明确的IS-A关系才能使用,就比如人类是哺乳动物。
多态:接口的多种不同的实现方式即为多态。接口是对行为的抽象,刚才在封装提到,找到变化部分并封装起来,但是封装起来后,怎么适应接下来的变化?这正是接口的作用,接口的主要目的是为不相关的类提供通用的处理服务,我们可以想象一下。比如鸟会飞,但是超人也会飞,通过飞这个接口,我们可以让鸟和超人,都实现这个接口,这就实现了系统的可维护性,可扩展性。
因此面向对象能实现人们追求的系统可维护性,可扩展性,可重用性。面向对象是一种编程思想,起初,“面向对象”是专指在程序设计中采用封装、继承、多态等设计方法,但面向对象的思想已经涉及到软件开发的各个方面,比如现在细分为了面向对象的分析(OOA),面向对象的设计(OOD),面向对象的编程实现(OOP)
面向如何实现(How)
面向对象编程是一张编程范式或编程风格,它以类或对象最为类组织代码的基本单元,并将类或对象作者代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现基石(以对象为中心,而不是以过程为中心),
上面已经解释这四个特性是什么,接下来将会讲解,这四个特性,在JAVA语言编程中,是如何实现
封装: 暴露有限的接口和属性,通过JAVA语言的权限访问控制的语法(private,protected,public)
抽象:接口,抽象类(interface、abstract)
继承:子类继承父类(extends)
多态:子类可以替换父类
这四个特性的也实现了系统可维护性、可扩展性、可重用性,
其中封装提高代码可维护性,降低接口复杂度,提供类的复用性,
抽象提高代码的可扩展性、维护性,降低复杂度,减少细节负担,
继承提高代码的复用,提高代码的可重用性,
多态提高代码的扩展性,子类可以替换父类
面向对象编程步骤:
- 面向对象分析(OOA):分析需求,找出客观主体(类),行为(方法),主体之间的关系
- 面向对象设计(OOD):画出UML,类图这些,确认类之间的关系(泛化、实现、组合、依赖)
- 面向对象编程(OOP):根据设计,结合特性,进行面向对象编程
转变思路
接到需求,第一部不是考虑如何实现需求,而是进行需求分析,根据需求找到其中的实体,再找到这些实体之间的联系,再通过UML画出来,然后再思考通过这些实体,去考虑如何完成你的需求,
面向对象编程与面向过程编程的区别
面向过程编程:面向过程编程也是一种编程范式或编程风格,它以过程(可以理解为方法,函数,操作)作为组织代码的基本单元,以数据(可以理解成为变量,属性)与方法相分析为主要特性,面向过程风格是一样流程化的编程风格,通过凭借一组顺序执行的方法来操作完成一项功能
以下是《设计模式之美》例子说明摘抄
首先,我们先来看,用面向过程这种编程风格写出来的代码是什么样子的。注意,下面的代码是用 C 语言这种面向过程的编程语言来编写的。
struct User {
char name[64];
int age;
char gender[16];
};
struct User parse_to_user(char* text) {
// 将text(“小王&28&男”)解析成结构体struct User
}
char* format_to_text(struct User user) {
// 将结构体struct User格式化成文本("小王\t28\t男")
}
void sort_users_by_age(struct User users[]) {
// 按照年龄从小到大排序users
}
void format_user_file(char* origin_file_path, char* new_file_path) {
// open files...
struct User users[1024]; // 假设最大1024个用户
int count = 0;
while(1) { // read until the file is empty
struct User user = parse_to_user(line);
users[count++] = user;
}
sort_users_by_age(users);
for (int i = 0; i < count; ++i) {
char* formatted_user_text = format_to_text(users[i]);
// write to new file...
}
// close files...
}
int main(char** args, int argv) {
format_user_file("/home/zheng/user.txt", "/home/zheng/formatted_users.txt");
}
然后,我们再来看,用面向对象这种编程风格写出来的代码是什么样子的。注意,下面的代码是用 Java 这种面向对象的编程语言来编写的。
public class User {
private String name;
private int age;
private String gender;
public User(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public static User praseFrom(String userInfoText) {
// 将text(“小王&28&男”)解析成类User
}
public String formatToText() {
// 将类User格式化成文本("小王\t28\t男")
}
}
public class UserFileFormatter {
public void format(String userFile, String formattedUserFile) {
// Open files...
List users = new ArrayList<>();
while (1) { // read until file is empty
// read from file into userText...
User user = User.parseFrom(userText);
users.add(user);
}
// sort users by age...
for (int i = 0; i < users.size(); ++i) {
String formattedUserText = user.formatToText();
// write to new file...
}
// close files...
}
}
public class MainApplication {
public static void main(String[] args) {
UserFileFormatter userFileFormatter = new UserFileFormatter();
userFileFormatter.format("/home/zheng/users.txt", "/home/zheng/formatted_users.txt");
}
}
面的代码中,我们可以看出,面向过程和面向对象最基本的区别就是,代码的组织方式不同。面向过程风格的代码被组织成了一组方法集合及其数据结构(struct User),方法和数据结构的定义是分开的。面向对象风格的代码被组织成一组类,方法和数据结构被绑定一起,定义在类中。
贫血模型VS充血模型
贫血模型: 数据与业务逻辑被分割到不同的类中 (典型例子:MVC三层结构)
充血模型: 数据和相应业务逻辑被封装到同一个类中(DDD,领域驱动设计)
基于充血模型的DDD开发模式跟基于贫血模型的传统开发模式相比,主要区别在于Service层,在基于充血模型的开发模型下,我们将原来在Service类中的业务逻辑转移到了一个充血Domain领域模型中,让Service类的实现依赖这个类,
基于充血模型的DDD开发模型下,Service类并不完全移除,而是负责放一些不适合放在Domain的功能,比如负责与Reoisitory层打交道,跨领域模型的业务聚合功能,幂等事务等非功能性的工作,
基于充血模型的DDD开发模型跟基于贫血模型的传统开发模型相比,Controller层和Repository层的代码基本相同,这是因为Repostitory层的Entity的生命周期有限,Controller层的VO只是单纯作为DTO,两部分业务逻辑都不会太复杂,所以类同贫血模型没有问题
设计思想
基于接口而非实现编程(基于抽象而非实现编程):
我们在做软件开发的时候,一定要有抽象意识,封装意思,接口意识,越抽象,越顶层,越脱离具体实现的设计,越是能提供代码的灵活性,扩展性,可维护性
多用组合少用继承:
继承主要又三个作用,表示is-a关系,支持多态特性,代码复用,而这三个作用都可以通过组合,接口,委托三个技术手段来达成,除此之外,利用组合还能解决层次过深,过复杂的继承关系,影响代码的可维护性问题
Reference
《设计模式之美》:https://time.geekbang.org/column/intro/100039001?tab=catalog
什么是面向对象?为什么要面向对象?怎么样面向对象?:https://mp.weixin.qq.com/s/thCux5W6uTkSaGTMZesPbA