实现登录功能,能够储存多个账户密码。有三种不同的登陆模式,可以分别生成对应的题目并且输出到文档之中。出题有查重功能,同一个人出的题不能重复。
这位同学使用了一个状态模式去实现几种不同的登录状态(以及正在登录的状态),由一个指令接受类在命令行接受命令,在这个环境经过内建命令的处理,然后发送给当前状态之中。
登录、输出两个模块是独立的。各自独立的几个模块通过发布订阅模式来联系。他实现了一个事件中心,实现了不同的类均可以通过事件中心单例去订阅和发布事件。
其中,命令接受、事件中心、输出以及登录几个模块是单例的。
namespace EventSystem { // 标准的回调函数委托接口 public interface IEventCallBack { } // 回调函数事件的两个重载 public class EventCallBack : IEventCallBack { public Action actions; public EventCallBack(Action action) { actions += action; } } public class EventCallBack<T> : IEventCallBack { public Action<T> actions; public EventCallBack(Action<T> action) { actions += action; } } // 本项目的事件中心,实现完全解耦合的发布订阅模式 public class EventCenter { // 单例 private static EventCenter? instance = null; // 事件管理字典 private Dictionary<EventType, IEventCallBack> _events = new Dictionary<EventType, IEventCallBack>(); private EventCenter() { // do nothing } public static EventCenter GetInstance() { if(instance==null) { instance = new EventCenter(); } return instance; } /// <summary> /// 订阅事件 /// </summary> /// <param name="type">事件类型</param> /// <param name="action">以委托形式传递的回调函数</param> public void EventSubscribe(EventType type, Action action) { if(_events.ContainsKey(type)) { (_events[type] as EventCallBack).actions += action; } else { _events.Add(type, new EventCallBack(action)); } } public void EventSubscribe<T>(EventType type, Action<T> action) { if(_events.ContainsKey(type)) { (_events[type] as EventCallBack<T>).actions += action; } else { _events.Add(type, new EventCallBack<T>(action)); } } /// <summary> /// 退订事件 /// </summary> /// <param name="type">事件类型</param> /// <param name="action">以委托形式传递的回调函数</param> public void EventUnsubscribe(EventType type, Action action) { if (_events.ContainsKey(type)) { (_events[type] as EventCallBack).actions -= action; } } public void EventUnsubscribe<T>(EventType type, Action<T> action) { if(_events.ContainsKey(type)) { (_events[type] as EventCallBack<T>).actions -= action; } } /// <summary> /// 发布事件 /// </summary> /// <param name="type">事件类型</param> public void EventTrigger(EventType type) { if (_events.ContainsKey(type)) { (_events[type] as EventCallBack).actions?.Invoke(); } } /// <summary> /// 发布事件 /// </summary> /// <param name="type">事件类型</param> /// <param name="args">传递参数</param> public void EventTrigger<T>(EventType type, T args) { if(_events.ContainsKey(type)) { (_events[type] as EventCallBack<T>).actions?.Invoke(args); } } /// <summary> /// 清空事件列表 /// </summary> public void Clear() { _events.Clear(); } } }
public class StateManager { // 当前生效的状态 private IState _state ; // 当前登录的用户名 public string Username; // 该用户的出题历史 public HashSet<string> UserTestHistory = new HashSet<string>(); // 该用户的出题总数 private int _count=0; // 可能存在的四种状态 private LoginState _loginState = new LoginState(); private PrimaryState _primaryState = new PrimaryState(); private JuniorState _juniorState = new JuniorState(); private SeniorState _seniorState = new SeniorState(); // 构造函数 public StateManager() { // 初始状态为待登录状态 _state = _loginState; _state.Handle(this); EventCenter.GetInstance().EventSubscribe<Account>( EventType.LoginSuccess, (account) => LoginSuccess(account) ); } // 切换当前状态 public void StateSwitch(Grade type) { switch(type) { case Grade.None: _state = _loginState; break; case Grade.Primary: _state = _primaryState; break; case Grade.Junior: _state = _juniorState; break; case Grade.Senior: _state = _seniorState; break; default: break; } _state.Handle(this); } /// <summary> /// 主要操作类接收指令的入口 /// </summary> /// <param name = "command">命令行读取入的指令</param> public void CommandRecv(string command) { _state.CommandRecv(command); } // 登录成功后的处理 public void LoginSuccess(Account account){ Username = account.UserName; // 检查是否已经存在对应用户文件并进行创建 if( Directory.Exists(@".\File\"+Username) == true) { // 存在用户文件夹 if(!File.Exists(@".\File\"+Username+@"\"+Username+".txt")) { FileStream fs = new FileStream(@".\File\"+Username+@"\"+Username+".txt", FileMode.Append); StreamWriter wr = null; wr = new StreamWriter(fs); wr.WriteLine("0"); wr.Close(); } } else { // 创建一个记录为0条的用户出题历史 Directory.CreateDirectory(@".\File\"+Username); FileStream fs = new FileStream(@".\File\"+Username+@"\"+Username+".txt", FileMode.Append); StreamWriter wr = null; wr = new StreamWriter(fs); wr.WriteLine("0"); wr.Close(); } // 读取用户出题数据 StreamReader rd = File.OpenText(@".\File\"+Username+@"\"+Username+".txt"); string s = rd.ReadLine(); // 出题历史的总条数 _count = int.Parse(s); for (int i = 0; i < _count; i++) //读入数据并赋予数组 { string line = rd.ReadLine(); UserTestHistory.Add(line); } rd.Close(); } // 保存出题历史 public void SaveHistory() { System.IO.File.WriteAllText(@".\File\"+Username+@"\"+Username+".txt", string.Empty); FileStream fs = new FileStream(@".\File\"+Username+@"\"+Username+".txt",FileMode.Append); StreamWriter wr = null; wr = new StreamWriter(fs); wr.WriteLine(_count); foreach(string test in UserTestHistory) { wr.WriteLine(test); } wr.Close(); } /// <summary> /// 检查试题是否重复 /// </summary> public bool AddTestHistory(string test) { string code = MD5.GetInstance().StringToMD5_UTF8(test); if(UserTestHistory.Contains(code)) { return false; } _count++; UserTestHistory.Add(code); return true; } }
系统的结构有设计,易于拓展,条例相对清晰,不同模块之间耦合度低(发布订阅模式),便于后期代码维护和拓展。
注释非常全面,许多跨类调用的函数都有精确到每个参数的注释,便于代码阅读。
命名比较符合c#的命名规则(参考网络上的谷歌开源c#代码规范),阅读起来比较方便。
有基于安全性考虑而采用的哈希算法加密。
代码过多,有一些功能稍显无用。
单例模式过多,许多独立的模块都是单例,其中一些是可以改善的。
资源相对开销较大。
对于结构体和接口的使用,一部分仍存在一些问题,需要重构优化一下。
有的注释比较繁琐,不够精炼,应当注意注释的简洁明了。
没有使用数据库,而是在本地用字典保存用户信息,这一部分值得拓展一下。
虽然考虑到了安全性以及系统的结构,但是没有充分考虑到应用过程中的数据传输结构,数据打包方式等等。在真正把它做成实用的系统的时候,可能还需要大规模的重构。