2017编程提高第1节课——面向对象设计(1)
LanyuanXiaoyao's Blog ヽ(✿゚▽゚)ノ

2017编程提高第1节课——面向对象设计(1)


从需求开始谈起

  • 需求总是不完整的,错误的,容易让人产生误解
  • 需求一直在变化
    • 用户对需求的看法,可能在与开发人员讨论以及看到软件新的可能性后发生变化
    • 随着对问题的熟悉, 开发人员对问题领域的看法也会变化
    • 不是需求在变, 而是人们对需求的理解在变化
  • 如何去应对变化?

软件工程层面→敏捷开发(常沟通,常迭代),通过多沟通来及时获取需求的变更,然后快速迭代来适应需求的变更 编码层面→封装变化

例子

假设你在一个会议上担当讲师, 听课的人在课后还要去听其他课程, 但他们不知道下一堂课的听课地点,你的责任就是, 确保大家都知道下一节课去哪儿上。

结构化的分析方法

自顶向下,逐层分解

  1. 获取听课人的名单
  2. 对于名单上的每个人 , 做如下事情
    1. 找到他要听的下一堂课
    2. 找到该课程的听课地点
    3. 找到从你的教室到下一堂课的路线图
    4. 告诉这个人怎么走

结构化分析解决这个问题ok,但是人多了怎么办,如果10个人ok,那100个人 10000个人怎么办? 数据的规模往往是一开始设计时候没有想到的

你很有可能会这么做

  1. 在教室的后面贴一张地图, 标出每个教室的位置和路线
  2. 告诉大家: “下课后大家自己去看后面的地图, 自己找下一节课的位置”

到底谁来负责?

责任的转移

第一种方法: 直接给每个人提供指示, 责任全在老师自己身上 第二种方法:只给出通用的指示(调用接口), 让每个人自己去完成任务   (面向对象的思路), 责任在各个学生身上 让责任划分到合适的对象当中!

面向对象的好处

老师只需要对Student发出一个“笼统”的指令:gotoNextClassroom() 即可, 不用关心实现细节

思考: 假设出现了需求变更, 你讲的课有研究生作为助教, 它们需要把本节课的反馈收集一下,先交到会议办公室,然后再去下一个教室。对于结构化设计和OO怎么应对?

应对变更

  • 对于结构化设计, 不得不对控制程序进行修改, 加上if else 判断以区分研究生和普通学生, 给研究生以特殊指令–修改控制程序,容易产生bug
  • 对于OOD ,  根本不区分研究生和普通学生, 只是对Student这个抽象概念说: gotoNextClassRoom()
for (Student student : students){
	student.gotoNextClassRoom()
}

Student 可能是普通学生,也可能是研究生助教

总结一下

  • 职责转移
    • 把职责划分到合适的类中去
    • 把细节封装起来
  • 只对接口进行操作
    • gotoNextClassroom()

问题

等等 !你讲的例子不就是封装和多态吗? 我已经掌握了OO的三大特性:封装、继承、多态 ,学会了定义类,封装属性, 我还会定义类的继承体系, 这难道还不够吗?
为什么还要学习面向对象的设计?

少啰嗦,先看代码

例子1: 选课

科目(Course): ID, 描述, 时长,最多学生数, 先修科目
学生(Student): ID, 姓名, 已修科目
课程(CourseOffering): 科目, 上课地点, 老师,选这门课的学生

在这个例子中,判断课程是否已满是课程要做的事情,就要放到课程这个类中去解决

例子2: 模拟鸭子游戏

模拟鸭子游戏

主管要求加上飞行的行为

飞行鸭

可怕的问题发生了, 一个橡皮鸭也能飞行了! 看看是怎么回事

可怕的问题

解决办法

把fly()方法也给覆盖了

把fly()方法也给覆盖了 向父类添加一个方法,导致很多子类需要改动,这是继承给我们带来的好处吗?这还是继承吗?! 可是将来加入一个诱饵鸭该怎么办, 诱饵鸭是木头假鸭,不会飞也不会叫

木头鸭 我已经受够方法覆盖了!

属性→静态的,用继承 动作→动态的,用接口

换个思路: 用接口怎么样?

接口

面向对象设计的准则

  • 发现变化并且封装变化
    • 找出可变之处, 把它独立出来,不要和那些不需要变化的代码混在一起。
    • 一个抽象的过程。
  • 针对接口编程而不是针对实现编程
  • 优先使用组合而不是继承

    发现变化并且封装变化

    鸭子类的fly()和quack()会随着鸭子的不同而改变

发现变化并封装变化

呱呱叫的行为

呱呱叫

针对接口编程

针对接口

针对接口

总结一下

  • 面向对象的三大特性:封装、继承、多态 只是提供了一种工具, 如何使用这样的工具才真正的考验码农的功力。
  • 日常的工作主要是在一个框架下填充代码, 很少有机会去提升OOD的能力。
  • OOD 是写类库,框架等软件的必备技能

    软件开发的不同视角

  • 在概念层次上,对象是一组责任
    • 这个对象要负责什么?
  • 在规约层次上,对象是一组可以被其他对象或者自己调用的方法
  • 在实现层次上, 对象是代码和数据

OOD(面向对象设计)

  • 对中级程序员的要求
    • 理解OOD设计原则
    • 写出符合OOD原则的代码
    • 掌握设计模式
    • 掌握基本的UML
    • 给一个模块,能做出符合面向对象的设计

一个好的面向对象系统

  • 面向对象的,可以复用的
  • 可以用最小的代价去修改系统
  • 不用修改(或者很少的修改)现有代码就可以扩展

面向对象设计的原则

  • 单一职责原则 (SRP)
  • 开闭原则 (OCP)
  • Liskov 替换原则 (LSP)
  • 接口隔离原则 (ISP)
  • 依赖倒置原则 (DIP)
  • SOLID:写出优雅代码的关键所在

SRP: 单一职责原则

  • 职责: 是引起变化的原因
    • 如果有多于一个的动机去改变一个类,这个类就具有多于一个职责
    • 把多个职责耦合在一起,一个的变化可能会削弱或者抑制这个类完成其他职责的能力
  • SRP : 对一个类而言, 应该仅有一个引起它变化的原因。

    SRP例子

    SRP

SRP

SRP

事实上不要因为设计而过度设计,如果一个类中的多个变化总是同时发生改变,那么久不需要分离他们

SRP

SRP

如果有一个方法是其他类也不需要用到的,那么它也没有必要独立出来 所以说,单一职责原则并不是只能有一个职责,而是职责不能有太多,要遵循不同时变化又要被公用的方法要被独立出来

public interface Modem{
	public  void dail(String phoneNum);
	public void hangup();
	public void send(String msg);
	public void recv();
}

三分钟练习

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)  {
        //检查访问权限
    }
}

本周作业

重构一个项目使之符合SRP


评论