我工作两年后,有一次不成功的富途证券的面试,印象非常深刻,面试官提出了一个看似简单实则充满深意的问题:如何用面向对象的思想设计一个人关门的场景?
我当时是这样设计的,创建两个类:Person
和Door
:
public class Person {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public void closeDoor(Door door) {
if (door.isOpen) {
door.isOpen = false;
System.out.println("门已关闭");
} else {
System.out.println("门已经关闭");
}
}
}
public class Door {
public boolean isOpen; // 门的状态
public Door(boolean isOpen) {
this.isOpen = isOpen;
}
}
我完成之后,面试官接着问了我一个问题:你觉得关门这个逻辑,是人对象实现还是门对象实现比较好?这两种设计的区别是什么?
当时我对封装的理解还不是很深入,导致设计和回答的都不理想。
一,封装
在面向对象编程中,封装是最基本的原则之一,它指的是将对象的状态(属性)和行为(方法)封装在一起,并对外界隐藏其内部实现细节的过程。
这样做有两个主要目的:
- 一是增强代码的安全性,防止外部对内部数据的不当修改
- 二是提高模块间的解耦,使得代码更加易于理解和维护
封装的实现手段:
-
访问控制符:通过public、private、protected等访问修饰符来控制类成员的访问级别。最佳实践是成员变量尽可能用private修饰,对外提供方法修改成员变量
-
抽象接口:提供简洁的公共接口,隐藏实现细节,使得外部只需知道如何使用而无需关心如何实现
基于上述知识,有一个设计原则:对象代表什么,就必须封装相关的状态,并提供操作状态的方法。
二,正确的设计是怎样的呢?
1,我的设计的不足之处
代码展示
public class Door {
public boolean isOpen; // 门的状态
public Door(boolean isOpen) {
this.isOpen = isOpen;
}
}
public class Person {
public void closeDoor(Door door) {
if (door.isOpen) {
door.isOpen = false;
System.out.println("门已关闭");
} else {
System.out.println("门已经关闭");
}
}
}
此设计违背封装原则:
- ①
Door
类的isOpen
属性直接被设为public,任何外部类都可以直接访问并修改它,这不仅破坏了数据的安全性,也使得Door
的状态管理变得混乱 - ②
Person
类直接操作Door
的状态,增加了类之间的耦合度
2,正确设计
代码展示
public class Door {
private boolean isOpen; // 私有化状态
public Door(boolean isOpen) {
this.isOpen = isOpen;
}
public boolean isOpen() {
return isOpen;
}
public void close() {
if (isOpen) {
isOpen = false;
System.out.println("门已关闭");
} else {
System.out.println("门已经是关闭状态");
}
}
}
public class Person {
public void attemptToCloseDoor(Door door) {
if (door.isOpen()) {
door.close();
}
}
}
在这个设计中,明显加强了封装性:
- 门的状态现在是私有的,外部不能直接访问或修改,只能通过提供的
isOpen()
方法来检查门的状态 - 关门操作被封装在
Door
类内部的close()
方法中,确保了对门状态的更改遵循预定逻辑 Person
类通过调用Door
的公共接口(即isOpen()
和close()
方法)来尝试关门,而不是直接操作门的状态,这减少了类间的耦合,提升了代码的可维护性和扩展性
也就是说,虽然是人关门,关门逻辑应由门对象实现,而不是人对象实现。因为开
和关
是门的状态,这个状态的改变,必须由门对象完成,才符合封装的设计理念。
3,自己GG还是被GG
为了加深对上述原则的理解,举个不和谐的例子:张三把李四砍了,李四GG,那么请问,从面向对象的角度,让李四GG的方法是由张三提供还是李四提供呢?
显然,按照封装思想,李四被砍导致生命状态从活着变成GG,应该由李四自己提供改变生命状态的方法,张三砍人的动作就是调用李四提供的这个方法。
乍看起来有点怪异,都从面向对象的封装思想来看,就得这么设计。