面向对象设计 - 入门
OOD 面向对象设计 对于 Entry level SDE 考察综合素质. 实现系统的 Viability, 典型例题是 Design Elevator System.
前置知识
封装 Encapsulation
Class
Object
class Animal {};
Animal a = new Animal();
class Employee {
// access modifier
private String name;
private float salary;
private int level;
// 外部可调用
public void raiseSalary();
public void printName();
public void promoteLevel();
public Sring getName();
}
继承 Inheritance
- It describes a IS-A relationship.
- 子类继承父类里所有不是 private 的 attributes.
- 父类 =
Base class
/Parent class
, 子类 =Sub class
. - super keyword will call Base class's same name method.
- final class 不能被继承.
- abstract class can not 初始化.
class Animal {
public void description(){
System.out.println("This is a general animal");
}
protected String name;
public int id;
private String privacy;
}
class Dog extends Animal {
// override
public void description() {
System.out.println("This is a Dog");
System.out.println("Name -> " + name);
System.out.println("Id -> " + id);
// System.out.println("Privacy -> " + privacy); // WRONG
}
// overload
public void description(String type) {
System.out.println("This is a " + type);
}
}
Dog dog = new Dog();
dog.description();
dog.description("Cat");
class Dog extends Animal {
public void description() {
super(); // This will call Base class's same name method.
}
}
Interface
- Interface can have only abstract methods.
- Interface can't provide the implementation of abstract class.
interface Service {
// No constructor
// interface里的函数都是抽象函数,不可以有Implement.
// Interface can have only abstract methods.
public void serve();
public void retire();
}
class Dog implements Service {
public void serve() {
// dog in service
}
public void retire() {
// dog retire from service
}
}
多态 Ploymorphism
abstract class Animal {
public abstract void makeSound();
}
final class Dog extends Animal {
public void makeSound() {
System.out.println("Woof !");
}
}
class Cat extends Animal {
public void makeSound() {
System.out.println("Meeow !");
}
}
Animal animal1 = new Dog(); // CORRECT
Animal animal2 = new Cat(); // CORRECT
// the same Animal class, the same makeSound() method -> the different output.
animal1.makeSound(); // Woof !
animal2.makeSound(); // Meeow !
枚举变量 Enum
- 可读性好.
- 防止被外部传入错误的参数, like
4, 5, 6
.
public enum TrafficSignal {
// defined in compile time
RED, YELLOW, GREEN
}
public class Testing {
TrafficSignal signal = TrafficSignal.RED;
}
异常处理 Exception
- Checked Exception (IO Exception, Compile time exception)
- Unchecked Exception (Runtime, Exception, NPE)
class MyException extends Exception {
public MyException(String s) {
super(s);
}
}
class Testing {
public void test() {
try {
throw new MyException("My exception");
}
catch (MyException ex) {
System.out.println(ex.getMessage()); // you will get "My exception"
}
}
}
Testing test1 = new Testing();
test1.test();
public class ExceptionTest2 {
public void test() throws MyException {
if (true) {
throw new MyException("My exception");
}
}
public void test1() {
// test(); // WRONG
}
public void test2() throws MyException {
test(); // CORRECT
}
// 用try catch handle exception
public void test3() {
try {
test(); // CORRECT
} catch (MyException ex) {
System.out.println(ex.getMessage());
}
}
public static void main(String[] args) throws MyException {
ExceptionTest2 t = new ExceptionTest2();
t.test2();
t.test3();
}
}
S.O.L.I.D 原则
Single responsibility principle 单一责任原则
一个类应该有且只有一个去改变它的理由,意味着一个类应该只有一项工作.
// 只负责 calculate area
public class AreaCalculator {
private float result;
public float getResult() {
return this.result;
}
public float calculateArea(Triangle t) {
this.result = h * b / 2;
}
}
// 只负责 print
public class Printer {
public printInJson(float number) {
jsonPrinter.initialize();
jsonPrinter.print(this.result);
jsonPrinter.close();
}
}
Open close principle 开放封闭原则
对象或者实体应该对扩展开发,对修改封闭 (Open to extension, close to modification).
public interface Shape {
public float getArea();
}
public class Triangle implements Shape {
public float getArea() {
return b * h / 2;
}
}
// 不需要改变,只需要取定义新的形状
public class AreaCalculator {
private float result;
public float getResult() {
return this.result;
}
public float calculateArea(Shape s) {
this.result = s.getArea();
}
}
Liskov substitution principle 里氏替换原则
任何一个子类或者派生类应该可以替代它们的基类或者父类.
Interface segregation principle 接口分离原则
不应该强迫一个类去实现它用不上的接口.
public class 2DShape {
abstract public float calculateArea();
}
public class 3DShape {
abstract public float calculateVolumn();
}
public class Rectangle extends 2DShape {
// Good design
}
public class Cube extends 3DShape {
// Good design
}
Dependency inversion principle 依赖反转原则
抽象不应该依赖于具体实现,具体实现应该依赖于抽象.
// interface是抽象的class
// 有class想实现接口的时候, 需要实现接口内所有的函数
public interface Shape {
public float getArea();
}
public class Triangle implements Shape {
public float getArea() {
return b * h / 2;
}
}
public class AreaCalculator {
private float result;
public float getResult() {
return this.result;
}
public float calculateArea(Shape s) {
this.result = s.getArea();
}
}
UML 类图
UML 类符号 (Symbols)
类名称 (Class Name):
- 类的名称出现在第一个分区中。
类属性 (Attribute):
- 属性显示在第二个分区中。
- 属性类型显示在冒号后面。
- 属性映射到代码中的成员变量(数据成员)。
类操作 Operation (方法 Method):
- 操作显示在第三个分区中。它们是班级提供的服务。
- 方法的返回类型显示在方法签名末尾的冒号后面。
- 方法参数的返回类型显示在参数名称后的冒号后面。操作映射到代码中的类方法
类可见性 (Visibility)
- "+" 表示
public
- "-" 表示
private
- "#" 表示
protected
- 不带符号表示
default
实战 - Design Elevator System
5C 解题法
Clarify
: 与面试官交流,去除题目中的歧义,确定答题范围.Core objects
: 确定题目中涉及的类,以及类之间的映射关系.Cases
: 确定题目中所需要实现的场景和功能.Classes
: 通过类图的方式,具体填充题目中涉及的类.Correctness
: 检查自己的设计,是否满足关键点.
Clarify
Example: Glass of water?
water or juice or tea?
Step1. What
通过关键词(一般是名词),来确定题目范围.
关键词 1: Evelator
- AirStairs 客梯 or Goods elevator 货梯
- 是否需要设计两种类,如果需要它们之间是什么关系?
关键词 2: Building
- 楼有多大/楼有多高/多少人? 通用属性,对于题目帮助不大.
- 是否有多处可以搭乘的电梯口? 当收到电梯请求的时候,有多少电梯能够响应.
Step2. How
针对问题的规则来提问,帮助自己明确解题方向.
- 按下电梯时,如果有多台电梯,哪个电梯会响应? 同方向 > 静止 > 反向.
- 是否能按反向的楼层? 不能.
- 信息: 电梯 至少需要三种状态,并且要知道当前在哪一层.
Step3. Who
Optional, 通过思考是否会有人的出现,来帮助确定解题范围. 一般可以考虑人的角色和人的属性,看是否题目需要.
- 电梯系统如何获取每个乘客的重量? 电梯能够自动感应.
Core Object
如何定义 Core Object?
- 以一个 Object 作为基础,线性思考.
- 确定 Objects 之间的映射关系.
Cases
为什么要写 Use cases? 将要实现的功能写下来给面试官看,帮助理清思路,按照 Case 实现,作为检查的标准. 如何写 Use cases? 利用定义的 Core Object, 列出每个 Object 对应产生的 use case, 并用一句简单的话来描述.
ElevatorSystem
- Handle request
Request
- N/A
Elevator
- Take external request 接受电梯系统的分配任务
- Take internal request 电梯内部可以按按钮
- Open gate
- Close gate
- Check weight
ElevatorButton
- Press button
Class
为什么要画类图?
- 可交 付, Minimal Viable Product.
- 节省时间, 不容易在 Coding 上挣扎.
- 建立在 use case 上,和之前的步骤层层递进,条例清晰,便于交流和修改.
- 如果时间允许,便于转化为 code.
怎么画类图?
- 遍历所有列出的 use cases.
- 对于每一个 use case, 更加详细的描述这个 use case 在做什么事情.
- 针对这个描述, 在已有的 Core objects 里填充进所需要的信息.
Use case: Take external request
An elevator takes an external request, inserts in its stop list.
如果电梯目前在 1L,有人按下了 5L 向上,之后又有人按下了 3L 向上,电梯会怎么样行动?
- stops will be 3
- Expected is 5 因为 3 楼更近,我们希望先去 3 楼
- Solution: use priority queue (heap) instead of list
如果电梯目前在 1L,有人按下了 5L 向上,之后又有人按下了 3L 向上,紧接着这台电梯又被分配了一个 2L 向下的 request。这台电梯会如何行动?
- stops will be 5 如果使用了 heap 会变成
2,3,5
但是这是不合理的 - Expected is 2 向上的动作是先按的,所以我们应该先执行向上,再执行向下的动作
- Solution: 创建
List<Integer> upStops
存储所有向上的 request, 创建List<Integer> downStops
存储所有向下的 request. 创建Status status
保存当前电梯的状态,当 5L 向上的 request 进来时,我们更新status = UP
, 当upStops
被清空了之后,我们再将status = DOWN
来执行downStops
中的动作
Use case: Take internal request
An elevator takes an internal request, determine if it's valid, inserts in its stop list.
- 定义
private boolean isRequestValid(InternalRequest r)
函数来判断请求是否合法,因为其是由void handleInternalRequest(InternalRequest r)
来调用,所以不需要public
如何判断一个 Internal request 是否为 Valid?
-
If elevator going up?
- requested level lower than current level? invalid
-
If elevator going down?
- requested level higher than current level? invalid
-
需要新状态
int currentLevel
来表示当前的楼层
Use case: Open gate
An elevator reaches the destination level, open gate
- 设置
void openGate
函数控制开门,boolean gateOpen
状态表明门是否开了
Use case: Close gate
- An elevator checks if overweight -> close the door;
- then check stops corresponds to current
status
如果当前status = UP
并且upStops
中没有剩余楼层了,则去检查downStops
是否还有,如果downStops
不为空,则修改status = DOWN
if no stops left, check the reserve direction stops; - change status to reserve direction or
IDLE
如果有没有楼层了,则status = IDLE
Use case: check weight
An elevator checks its current weight and compare with limit to see if overweight
- 添加新的方法
private float getCurrentWeight()
与 限重属性private float weightLimit
进行比较判断是否超重了
Use case: press button
A button inside elevator is pressed, will generate an internal request and send to the elevator. 在电梯内部按下按钮,会发送内部请求给电梯
ElevatorButton
类需要private int level
和private Elevator elevator
,来表明楼层和 request 发送给哪一台电梯- 使用
public InternalRequest pressButton()
来实现按下按钮
Correctness
从以下方面检查
- Validate use cases 是否支持所有的 use case
- Follow good practice 加分项,展现程序员的经验
- E.g.,
InternalRequest
和ExternalRequest
均可以继承Request
类
- E.g.,
- S.O.L.I.D
- Design pattern
Challenge
What if I want to apply different ways to handle external requests during different time of a day?
Solution: Strategy design pattern 策略设计模式
可参考设计模式-策略模式
- 封装了多种 算法/策略, 算法/策略之间能够相互替换
- 你可以将算法的实现和使用算法的代码隔离开来
- 开闭原则。 你无需对上下文进行修改就能够引入新的策略