适合简单聊天应用的设计模式

11
我正在设计一个简单的聊天应用程序(仅仅是为了好玩)。我一直在考虑聊天应用程序的简单设计。以下是规则概述:
  1. 匿名用户只需使用昵称进入聊天室。 用户ID由系统在后台分配。
  2. 他们可以加入(订阅)聊天对话,并在指定区域看到其他用户的聊天文本。
  3. 他们可以回复特定对话,其他人也能看到。
这就是全部!(你看,我告诉你它是一个简单的聊天应用程序)。因此,我的目的不是真正的应用程序,而是其中使用的设计模式和对象。
现在这是我如何设计的(我用Java编码..如果那真的很重要):
  1. User Object - 两个属性id和nickname
  2. Message Object - 简单的消息接口和实现(目前)作为SimpleMessage,具有一个字符串作为包含消息的属性。
  3. Chat Window Object - 基本上是用户和信息的组合。 它有一个用户对象和消息列表。
  4. Chat Session - 再次组合。基本上,它将拥有一个聊天窗口列表。每个聊天窗口都向聊天会话注册。 ChatSession负责通知所有聊天窗口,当出现新消息时。(观察者模式?)
好的,现在我已经通过让ChatWindow实现“ChatListener”模式来实现观察者模式,其中有一个名为“notify(Message)”的方法。因此,ChatSession通知每个注册的ChatWindow。
现在这里有几件事情我想要澄清/想要你的意见:
1. 我需要为所有聊天窗口也有一个“取消注册”方法,以防聊天窗口关闭并且不希望再获得任何通知。这可能意味着,我应该有一个“静态”的中央注册管理器,它只有一个实例,然后任何聊天窗口都应该能够通过提供“聊天会话”ID来注销自己。由于这个原因,每个聊天会话都应该有一个ID。(包括在此处)。或者我也可以在Chat Window中维护一个ChatSession实例,始终准备好一个实例。(我讨厌单例,因为我认为它们违反了OOPS)。另一种方法是不使用聊天窗口的注销控制,而是直接将窗口关闭的通知发送到ChatSession,并且ChatSession应该执行其应该执行的操作!
2. 这种设计有意义吗?如果你说它很烂并给我一个更好的方法,你肯定会得到我的感谢。除了观察者模式之外,还可以在这里使用哪些模式来简化它或使其更好。此设计的任何弱点,如果它是合适的但可以改进。当用户在自己的聊天窗口中输入新消息时,需要将该消息传播到所有聊天窗口,这就是聊天会话所做的事情,但同时,这是否意味着聊天会话需要获得“聊天窗口ID”和消息?然后将其传播到所有窗口,包括拥有该消息的窗口?有没有更好的处理方式?我的意思是一种窗口让聊天会话知道消息,然后聊天会话将消息传递给所有其他窗口。(我想它会需要一些if...但我也不喜欢它们)
无论如何,请让我知道您的评论。此外,请注意工作应用程序并非目的,我正在寻找良好的讨论、良好的设计模式实践和用法。
如果以下完整代码让您兴奋,请随意分解并提出与几乎任何语义相关的问题。
package com.oo.chat;

public class User {

    private Long userId;
    private String nickname;

    public User(Long userId, String nickname) {
        this.userId = userId;
        this.nickname = nickname;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public Long getUserId() {
        return userId;
    }

    public String getNickname() {
        return nickname;
    }

    public boolean equals(Object objectToCompare) {
        if (!(objectToCompare instanceof User)) {
            return false;
        }
        User incoming = (User) objectToCompare;
        if (incoming.getNickname() != null && incoming.getUserId() != null) {
            if (incoming.getNickname().equalsIgnoreCase(this.nickname)
                    && incoming.getUserId().equals(this.userId))
                return true;
        }
        return false;
    }
}


package com.oo.chat;

public interface Message {

    public String getValue();

    public void setValue(String value);

}

package com.oo.chat;

public class SimpleMessage implements Message {

    private String value;

    public SimpleMessage() {

    }

    public SimpleMessage(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}

package com.oo.chat;

public interface ChatListener {

    public void notify(Message newMessage);

}

package com.oo.chat;

import java.util.ArrayList;
import java.util.List;

public class ChatWindow implements ChatListener {

    private User user;
    private List<Message> messageList;
    private Long id;

    public User getUser() {
        return user;
    }

    public List<Message> getMessageList() {
        return messageList;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public void setMessageList(List<Message> messageList) {
        this.messageList = messageList;
    }

    public void addMessageToList(Message newMessage) {
        if (this.messageList == null) {
            this.messageList = new ArrayList<Message>();
        }
        this.messageList.add(newMessage);
    }

    public void notify(Message newMessage) {
        addMessageToList(newMessage);
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

package com.oo.chat;

import java.util.ArrayList;
import java.util.List;

public class ChatSession {

    private List<ChatListener> registeredChatListeners;

    public void register(ChatWindow chatWindow) {
        if (registeredChatListeners == null)
            registeredChatListeners = new ArrayList<ChatListener>();
        registeredChatListeners.add(chatWindow);
    }

    public List<ChatListener> getRegisteredChatListeners() {
        return registeredChatListeners;
    }

    public void setRegisteredChatWindows(
            List<ChatListener> registeredChatListeners) {
        this.registeredChatListeners = registeredChatListeners;
    }

    public void incomingMessage(Long chatListenerId, Message message) {
        publish(message);
    }

    protected void publish(Message messageToPublish) {
        if (registeredChatListeners != null) {
            for (ChatListener eachListener : registeredChatListeners) {
                eachListener.notify(messageToPublish);
            }
        }
    }
}

预先感谢所有贡献者。祝福大家

3个回答

6
基本的设计对我来说看起来很不错。显然,要完成这个项目,你需要添加更多的功能。当前的设计将所有消息无限期地保存在内存中,但是在某个时候,你需要编写代码来清除旧消息。
我发现几个重要的设计问题:
1. 消息接口没有链接到消息发送者——大多数聊天都会显示谁说了什么,如果消息中没有用户字段,这将会很困难。 2. 消息接口没有时间属性,这将使清除旧消息变得更加困难。

是的,我同意这两点。然而,如果你看一下,目前的消息甚至可以告诉你是哪个用户发送的,但没有时间戳。我计划为消息接口做一个更全面的实现类。这可能会附加时间戳和发送者。至于持久化消息,是的,我也考虑过,但我没有实现那部分或UI,因为我真的不想开发应用程序,只是为了现在正确地设计基本的东西。更多的是学习设计模式。话虽如此,评论仍然非常欢迎。谢谢。 - Priyank

5
仅看“User”对象,为什么相等性依赖于id和nickname?这似乎有点违反直觉。如果您拥有一个id,则期望它是对象的标识,因此您将在相等条件中使用它。
我还看到您对用户id设置了setter。那么您真的想更改用户id吗?我看到您可以更改昵称,这很有意义。但是我希望id保持不变。
请注意,由于您正在重写equals(),因此还应重写hashCode()
现在,如果hashCode()和equals()依赖于不可变字段(例如id),则hashCode()的结果不会更改,如果您将User放入哈希集合(例如HashMap)中,则不会丢失它(这非常令人困惑)!
最后(!)我会保护构造函数和设置器来防止空昵称(使它们抛出IllegalArgumentExceptions),这样像equals()这样的代码就不必担心空昵称了(除非“null”对于昵称有意义)。由于您将其作为Long(对象)使用,因此我会对id执行相同的操作。但是,这不可能是原始的长整型吗?

非常感谢您宝贵的反馈。这正是我一直在寻找的答案。我本来可以立即接受您的答案,但我会等一段时间,以防其他人提出更多的答案。 :) - Priyank
没问题。我没有像你要求的那样关注整体模式,所以我认为其他答案更适合“接受”,但知道上面的内容有用是很好的。 - Brian Agnew

2
我建议您查看消息框架,而不是使用观察者模式。
请查看这个简单的实现,它足以满足您的玩具项目 - eventbus(已不再提供)。或者,您可以选择全面的JMS实现,例如ActiveMQ
基本上,它允许您定义一个公共总线,您可以在其中注册和注销参与者,并发送所有参与者都将看到的消息。与观察者模式相比,其最大优势在于参与者之间的耦合度非常低 - 您不需要向每个对象注册以获取其消息 - 您只需一次向总线注册即可。此外,您还可以获得异步处理 - 假设您有1000个聊天会话 - 如果使用观察者模式,则每个消息完成需要更新1000个会话。使用消息框架,消息发送非常快速,并且通知所有1000个会话是在后台完成的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接