基石
- OOD是用客观现实(真实世界)的规律和方式去告诉计算机做什么(编程)。
从需求开始谈起
- 需求总是不完整的,错误的,容易让人产生误解
- 需求一直在变化
- 用户对需求的看法,可能在与开发人员讨论以及看到软件新的可能性后发生变化
- 随着对问题的熟悉, 开发人员对问题领域的看法也会变化
- 不是需求在变, 而是人们对需求的理解在变化
- 如何去应对变化?
例子一:听课
假设你在一个会议上担当讲师, 听课的人在课后还要去听其他课程, 但他们不知道下一堂课的听课地点,
你的责任就是, 确保大家都知道下一节课去哪儿上。
- 结构化的分析方法
- 你很有可能会这么做
到底谁来负责?
- 责任的转移
- 第一种方法: 直接给每个人提供指示, 责任全在老师自己身上
- 第二种方法:只给出通用的指示(调用接口), 让每个人自己去完成任务 (面向对象的思路), 责任在各个学生身上
- 让责任划分到合适的对象当中!
面向对象的好处
老师只需要对Student发出一个“笼统”的指令:gotoNextClassroom() 即可, 不用关心实现细节
思考: 假设出现了需求变更, 你讲的课有研究生作为助教, 它们需要把本节课的反馈收集一下,先交到会议办公室,然后再去下一个教室。 对于结构化设计和OO 怎么应对?
应对变更
对于结构化设计, 不得不对控制程序进行修改, 加上if else 判断以区分研究生和普通学生, 给研究生以特殊指令 – 修改控制程序,容易产生bug
对于OOD , 根本不区分研究生和普通学生, 只是对Student这个抽象概念说: gotoNextClassRoom()
for (Student student : students){
student.gotoNextClassRoom()
}
//Student 可能是普通学生,也可能是研究生助教
总结一下
职责转移
- 把职责划分到合适的类中去
- 把细节封装起来
只对接口进行操作
- gotoNextClassroom() !
问题
等等 ! 你讲的例子不就是封装和多态吗? 我已经掌握了OO的三大特性:封装、继承、多态 ,学会了定义类,封装属性, 我还会定义类的继承体系, 这难道还不够吗?
为什么还要学习面向对象的设计? 先看示例二
例子二:选课
科目(Course): ID, 描述, 时长,最多学生数, 先修科目
学生(Student): ID, 姓名, 已修科目
课程(CourseOffering): 科目, 上课地点, 老师,选这门课的学生
public class Course {
private String id;
private String desc;//课目描述
private int duration;//课目时长
List< Course > prerequisites;//先修科目
public List<Course> getPrerequisites () {
return prerequisites;
}
public boolean equals( Object o ){
if( o == null || !( o instanceof Course ) ) {
return false;
}
Course c = ( Course ) o;
return ( c != null ) && c.id.equals( id );
}
}
public class Student {
private String id;
private String name;//名字
private List< Course > coursesAlreadyTaken = new ArrayList< Course >();//已经上过的课程
public List< Course > getCoursesAlreadyTaken() {
return coursesAlreadyTaken;
}
}
public class CourseOffering {
private Course course;//上课课目
private String location;//上课地点
private String teacher;//上课老师
private int maxStudents;
List< Student > students = new ArrayList< Student >();//学生
public int getMaxStudents() {
return maxStudents;
}
public List< Student > getStudents() {
return students;
}
public Course getCourse() {
return course;
}
}
业务代码:
这段代码如果不加注释读起来相当费尽,所以需要重构。有些责任不应该在这里做。
开始重构:
- 思路更加清晰
例子三:《Head First 设计模式》模拟鸭子游戏
主管要求加上飞行的行为
可怕的问题发生了, 一个橡皮鸭也能飞行了! 看看是怎么回事
解决办法: 把fly()方法也给覆盖了
向父类添加一个方法,导致很多子类需要改动,这是继承给我们带来的好处吗? 这还是继承吗?!
可是将来加入一个诱饵鸭该怎么办, 诱饵鸭是木头假鸭,不会飞也不会叫
是不是受够方法覆盖了!! !
换个思路: 用接口怎么样?
面向对象设计的准则
- 发现变化并且封装变化
- 找出可变之处, 把它独立出来,不要和那些不需要变化的代码混在一起。
- 一个抽象的过程。
针对接口编程而不是针对实现编程
优先使用组合而不是继承
发现变化并且封装变化
- 鸭子类的fly()和quack()会随着鸭子的不同而改变
呱呱叫的行为
针对接口编程
总结一下
面向对象的三大特性:封装、继承、多态 只是提供了一种工具, 如何使用这样的工具才真正的考验码农的功力。
日常的工作主要是在一个框架下填充代码, 很少有机会去提升OOD的能力。
OOD 是写类库,框架等软件的必备技能
软件开发的不同视角
在概念层次上,对象是一组责任
- 这个对象要负责什么?
在规约层次上,对象是一组可以被其他对象或者自己调用的方法
在实现层次上, 对象是代码和数据
一个好的面向对象系统:
面向对象的,可以复用的
可以用最小的代价去修改系统
不用修改(或者很少的修改)现有代码就可以扩展
面向对象设计的原则
- 单一职责原则 (SRP)
- 开闭原则 (OCP)
- Liskov 替换原则 (LSP)
- 接口隔离原则 (ISP)
依赖倒置原则 (DIP)
SOLID:写出优雅代码的关键所在
PS:上面的每个原则将会后续发文
练习
public class UserSettingService{
public void changeEmail(User user) {
if(checkAccess(user)) {
//改变EMail
}
}
public boolean checkAccess(User user) {
//验证用户是否有效
}
}
重构后
public class UserSettingService{
public void changeEmail(User user) {
if(SecurityService.checkAccess(user)) {
//改变EMail
}
}
}
public class SecurityService{
public static boolean checkAccess(User user) {
//检查访问权限
}
}
//真的要这么做拆分吗?
结束语言:站在更高的层次看自己的代码