CS-Notes/notes/笔记/面向对象程序设计.md.txt
2018-02-22 14:47:22 +08:00

403 lines
15 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[TOC]
# 解决思路
面向对象设计问题要求求职者设计出类和方法,以实现技术问题或描述真实生活中的对象。可以按照以下步骤来进行解决:
## 1. 处理不明确的地方
面向对象设计问题往往会故意放些烟幕弹,意在检验你是武断臆测,还是提出问题以厘清问题。毕竟,开发人员要是没弄清楚自己要开发什么,就直接挽起袖子开始编码,只会浪费公司的财力物力,还可能造成更严重的后果。
碰到面向对象设计问题时你应该先问清楚谁是使用者、他们将如何使用。对某些问题你甚至还要问清楚“5W1H”也就是 Who、What什么、Where哪里、When何时Why为什么、How如何。举个例子假设面试官让你描述咖啡机的面向对象设计。这个问题看似简单明了其实不然。这台咖啡机可能是一款工业型机器设计用来放在大餐厅里每小时要服务几百位顾客还要能制作 10 种不同口味的咖啡。又或者它可能是设计给老年人使用的简易咖啡机只要能制作简单的黑咖啡就行。这些用例将大大影响你的设计。
## 2. 定义核心对象
比如,假设要为一家餐馆进行面向对象设计。那么,核心对象可能包括员工、服务员、厨师、餐桌、顾客、订单、菜。
## 3. 分析对象关系
可以利用 UML 类图来分析。其中
- 服务员和厨师都继承自员工类;
- 一个服务员要为多个餐桌的顾客提供服务;
- 一个餐桌可以有很多顾客;
- 一个餐桌可以有一个订单;
- 一个订单有多个菜。
- 一个厨师要处理多个订单;
![](index_files/97c1d566-ae3a-4ddd-ba86-e8378e26d49b.png)
## 4. 研究对象的动作
可以用时序图来找出对象的动作。
多个顾客坐到一个餐桌,服务员来到餐桌前提供服务,并等待顾客填写订单。订单填写完成后,交给厨师处理订单,并等待厨师处理完成,完成之后服务员将上菜。
![](index_files/4f901988-481c-48bf-a313-35778e2211bc.png)
# 示例
## 1. 聊天服务器
题目描述:请描述该如何设计一个聊天服务器。要求给出各种后台组件、类和方法的细节,并说明其中最难解决的问题会是什么。
设计聊天服务器是项大工程,绝非一次面试就能完成。毕竟,就算一整个团队,也要花费数月乃至好几年才能打造出一个聊天服务器。作为求职者,你的工作是专注解决该问题的某个方面,涉及范围要够广,又要够集中,这样才能在一轮面试中搞定。它不一定要与真实情况一模一样,但也应该忠实反映出实际的实现。
这里我们会把注意力放在用户管理和对话等核心功能:添加用户、创建对话、更新状态,等等。考虑到时间和空间有限,我们不会探讨这个问题的联网部分,也不描述数据是怎么真正推送到客户端的。
另外,我们假设“好友关系”是双向的,如果你是我的联系人之一,那就表示我也是你的联系人之一。我们的聊天系统将支持群组聊天和一对一(私密)聊天,但并不考虑语音聊天、视频聊天或文件传输。
### 1.1 需要支持的特定动作
这也有待你跟面试官探讨,下面列出几点想法。
- 显示在线和离线状态。
- 添加请求(发送、接受、拒绝)。
- 更新状态信息。
- 发起私聊和群聊。
- 在私聊和群聊中添加新信息。
这只是一部分列表,如果时间有富余,还可以多加一些动作。
### 1.2 核心组件
这个系统可能由一个数据库、一组客户端和一组服务器组成。我们的面向对象设计不会包含这些部分,不过可以讨论一下系统的整体概览。
数据库将用来存放更持久的数据比如用户列表或聊天对话的备份。 SQL 数据库应该是不错的选择或者如果可扩展性要求更高可以选用 BigTable 或其他类似的系统。
对于客户端和服务器之间的通信使用 XML 应该也不错。尽管这种格式不是最紧凑的你也应该向面试官指出这一点它仍是很不错的选择因为不管是计算机还是人类都容易辨识。使用 XML 可以让程序调试起来更轻松这一点非常重要。
服务器由一组机器组成,数据会分散到各台机器上,这样一来,我们可能就必须从一台机器跳到另一台机器。如果可能的话,我们会尽量在所有机器上复制部分数据,以减少查询操作的次数。在此,设计上有个重要的限制条件,就是必须防止出现单点故障。例如,如果一台机器控制所有用户的登录,那么,只要这一台机器断网,就会造成数以百万计的用户无法登录。
### 1.3 关键对象和方法
代码参考:[Github](https://github.com/careercup/ctci/tree/master/java/Chapter%208/Question8_7)
系统的关键对象包括用户、对话和状态消息等我们已经实现了 UserManagement 类。要是更关注这个问题的联网方面或其他组件我们就可能转而深入探究那些对象。
```java
public class UserManager {
private static UserManager instance;
private HashMap<Integer, User> usersById = new HashMap<Integer, User>();
private HashMap<String, User> usersByAccountName = new HashMap<String, User>();
private HashMap<Integer, User> onlineUsers = new HashMap<Integer, User>();
public static UserManager getInstance() {
if (instance == null) {
instance = new UserManager();
}
return instance;
}
public void addUser(User fromUser, String toAccountName) {
User toUser = usersByAccountName.get(toAccountName);
AddRequest req = new AddRequest(fromUser, toUser, new Date());
toUser.receivedAddRequest(req);
fromUser.sentAddRequest(req);
}
public void approveAddRequest(AddRequest req) {
req.status = RequestStatus.Accepted;
User from = req.getFromUser();
User to = req.getToUser();
from.addContact(to);
to.addContact(from);
}
public void rejectAddRequest(AddRequest req) {
req.status = RequestStatus.Rejected;
User from = req.getFromUser();
User to = req.getToUser();
from.removeAddRequest(req);
to.removeAddRequest(req);
}
public void userSignedOn(String accountName) {
User user = usersByAccountName.get(accountName);
if (user != null) {
user.setStatus(new UserStatus(UserStatusType.Available, ""));
onlineUsers.put(user.getId(), user);
}
}
public void userSignedOff(String accountName) {
User user = usersByAccountName.get(accountName);
if (user != null) {
user.setStatus(new UserStatus(UserStatusType.Offline, ""));
onlineUsers.remove(user.getId());
}
}
}
```
 User 类中 receivedAddRequest 方法会通知用户 BUser B用户 AUser A请求加他       B             UserManager.approveAddRequest  rejectAddRequest UserManager 则负责将用户互相添加到对方的通讯录中。
 UserManager 要将 AddRequest 加入用户 A 的请求列表时会调用 User 类的 sentAddRequest 方法。综上整个流程如下。
1. 用户 A 点击客户端软件上的“添加用户”发送给服务器。
2. 用户 A 调用 requestAddUser(User B)。
3. 步骤 2 的方法会调用 UserManager.addUser。
4. UserManager 会调用 User A.sentAddRequest  User B.receivedAddRequest。
```java
public class User {
private int id;
private UserStatus status = null;
private HashMap<Integer, PrivateChat> privateChats = new HashMap<Integer, PrivateChat>();
private ArrayList<GroupChat> groupChats = new ArrayList<GroupChat>();
private HashMap<Integer, AddRequest> receivedAddRequests = new HashMap<Integer, AddRequest>();
private HashMap<Integer, AddRequest> sentAddRequests = new HashMap<Integer, AddRequest>();
private HashMap<Integer, User> contacts = new HashMap<Integer, User>();
private String accountName;
private String fullName;
public User(int id, String accountName, String fullName) {
this.accountName = accountName;
this.fullName = fullName;
this.id = id;
}
public boolean sendMessageToUser(User toUser, String content) {
PrivateChat chat = privateChats.get(toUser.getId());
if (chat == null) {
chat = new PrivateChat(this, toUser);
privateChats.put(toUser.getId(), chat);
}
Message message = new Message(content, new Date());
return chat.addMessage(message);
}
public boolean sendMessageToGroupChat(int groupId, String content) {
GroupChat chat = groupChats.get(groupId);
if (chat != null) {
Message message = new Message(content, new Date());
return chat.addMessage(message);
}
return false;
}
public void setStatus(UserStatus status) {
this.status = status;
}
public UserStatus getStatus() {
return status;
}
public boolean addContact(User user) {
if (contacts.containsKey(user.getId())) {
return false;
} else {
contacts.put(user.getId(), user);
return true;
}
}
public void receivedAddRequest(AddRequest req) {
int senderId = req.getFromUser().getId();
if (!receivedAddRequests.containsKey(senderId)) {
receivedAddRequests.put(senderId, req);
}
}
public void sentAddRequest(AddRequest req) {
int receiverId = req.getFromUser().getId();
if (!sentAddRequests.containsKey(receiverId)) {
sentAddRequests.put(receiverId, req);
}
}
public void removeAddRequest(AddRequest req) {
if (req.getToUser() == this) {
receivedAddRequests.remove(req);
} else if (req.getFromUser() == this) {
sentAddRequests.remove(req);
}
}
public void requestAddUser(String accountName) {
UserManager.getInstance().addUser(this, accountName);
}
public void addConversation(PrivateChat conversation) {
User otherUser = conversation.getOtherParticipant(this);
privateChats.put(otherUser.getId(), conversation);
}
public void addConversation(GroupChat conversation) {
groupChats.add(conversation);
}
public int getId() {
return id;
}
public String getAccountName() {
return accountName;
}
public String getFullName() {
return fullName;
}
}
```
Conversation 类实现为一个抽象类因为所有 Conversation 不是 GroupChat 就是 PrivateChat同时每个类各有自己的功能。
```java
public abstract class Conversation {
protected ArrayList<User> participants = new ArrayList<User>();
protected int id;
protected ArrayList<Message> messages = new ArrayList<Message>();
public ArrayList<Message> getMessages() {
return messages;
}
public boolean addMessage(Message m) {
messages.add(m);
return true;
}
public int getId() {
return id;
}
}
```
```java
public class GroupChat extends Conversation {
public void removeParticipant(User user) {
participants.remove(user);
}
public void addParticipant(User user) {
participants.add(user);
}
}
```
```java
public class PrivateChat extends Conversation {
public PrivateChat(User user1, User user2) {
participants.add(user1);
participants.add(user2);
}
public User getOtherParticipant(User primary) {
if (participants.get(0) == primary) {
return participants.get(1);
} else if (participants.get(1) == primary) {
return participants.get(0);
}
return null;
}
}
```
```java
public class Message {
private String content;
private Date date;
public Message(String content, Date date) {
this.content = content;
this.date = date;
}
public String getContent() {
return content;
}
public Date getDate() {
return date;
}
}
```
AddRequest  UserStatus 两个类比较简单功能不多主要用来将数据聚合在一起方便其他类使用。
```java
public class AddRequest {
private User fromUser;
private User toUser;
private Date date;
RequestStatus status;
public AddRequest(User from, User to, Date date) {
fromUser = from;
toUser = to;
this.date = date;
status = RequestStatus.Unread;
}
public RequestStatus getStatus() {
return status;
}
public User getFromUser() {
return fromUser;
}
public User getToUser() {
return toUser;
}
public Date getDate() {
return date;
}
}
```
```java
public class UserStatus {
 private String message;
 private UserStatusType type;
 public UserStatus(UserStatusType type, String message) {
 this.type = type;
 this.message = message;
 }
 
 public UserStatusType getStatusType() {
 return type;
 }
 
 public String getMessage() {
 return message;
 }
}
```
```java
public enum UserStatusType {
Offline, Away, Idle, Available, Busy
}
```
```java
public enum RequestStatus {
Unread, Read, Accepted, Rejected
}
```
### 4. 最难解决或最有意思的问题
**问题 1如何确定某人在线**
虽然希望用户在退出时通知我们,但即便如此也无法确切知道状态。例如,用户的网络连接可能断开了。为了确定用户何时退出,或许可以试着定期询问客户端,以确保它仍然在线。
**问题 2如何处理冲突的信息**
部分信息存储在计算机内存中,部分则存储在数据库里。如果两者不同步有冲突,那会出什么问题?哪一部分是“正确的”?
**问题 3如何才能让服务器在任何负载下都能应付自如**
前面我们设计聊天服务器时并没怎么考虑可扩展性,但在实际场景中必须予以关注。我们需要将数据分散到多台服务器上,而这又要求我们更关注数据的不同步。
**问题 4如何预防拒绝服务攻击**
客户端可以向我们推送数据——若它们试图向服务器发起拒绝服务DOS攻击怎么办该如何预防
# 参考资料
- GAYLELEAKMANNMCDOWELL. 程序员面试金典 [M]. 人民邮电出版社 , 2013.