呵,小话设计模式之设计心得

kazex 发布于 2014/10/25 14:54
阅读 562
收藏 13

经常看到各个论坛经常有人讨论设计模式,进去一看,基本都是造搬一些设计模式书上的例子。看完之后,往往有一个疑问,他们写完之后在实际工作到底能不能用上呢?

今天闲着无聊,说说自己工作上使用过的心得。欢迎拍砖!

首先,设计模式到底是什么东西?

(本来想留最后写,写完发现不知道怎么写了,就不写了)

使用设计模式到底为了什么?

(本来想留最后写,写完发现不知道怎么写了,就不写了)

怎么把设计模式应用到工作中去?

这个我觉得最重要的,犹想当年刚开始学习设计模式时,把一本几百页的书看完,里面提到的30左右个设计模式。能用到工作中的只有2,3个比较常用的,比如:单例,工厂这些基本简单到连没有学过设计模式的人都会的模式。心中真的非常不爽,明明心中只有还有很多其他模式,但是就是用不上,我想很多人都有这种心里。

当我意识这个问题之后,我开始思考,设计模式到底是为什么而生?

经过一段时间思考之后,我突然发觉模式都有一个共同点,就是为了扩展性。何为扩展性?狭义且简单地说就是你写一段代码之后,以后需要增加某个功能的时候,可以不用或者尽量少地修改以前的代码而完成功能。举些例子:开源中国发表分享时,需要填写分类,现在有技术问题,技术分享等,以后可能还有MM征婚等。这些就是需要扩展性的东西,当你的程序中有考虑到这些东西后,你的程序将会慢慢健壮起来(当然例子可能有点简单,可能不用使用模式!)

意识到扩展性之后,犹如在无助的世界找到一根救命草。从此每当有需求时,都会先考虑一些,这个功能完成之后,以后还不会增加类似于的功能呢?举几个例子(我的工作主要是C++服务端开发):

1.每个用户会发送一些请求到服务器,现在只有Login,Chat,Logout3个动作,以后肯定也会增加。所有这个点上面绝对需要有扩展性!结合以前的模式,命令模式(Command)好像可以用到。用户每个动作都抽象成一个Comand。简单的C++代码为:

class User {
typedef std::map<UInt32, ICommand *> commands_;

User() {
commands_[LoginCommand::TYPE] = new LoginCommand(this);
commands_[ChatCommand::TYPE] = new ChatCommand(this);
}

void OnReadData() {
//1.解析数据包头,查看请求的TYPE
//2.根据TYPE,从commands_查找ICommand调度
}

};

class IComamnd {
virtual void OnCommand(const std::string &data);
};

class LoginCommand : public IComamnd {
public static UInt32 TYPE = 0;
};

class ChatCommand : public ICommand {
public static UInt32 TYPE = 1;
};

具有简单的扩展性的一段代码就这样蛋生了。但是我觉得设计模式就是这样自然地使用。我们再来分析一下,上面的其他其实还是不健壮的,为什么?因为3个请求其实性质不一样。因为一个用户需要Login之后,才能Chat或者Logout。此时,我们需要继续改造代码。思考一下,用户整个生命周期中,好像隐隐约约的叫“状态”的东西在里面:初始化状态->工作状态。那好,继续改进代码,并不要忘了!这个状态时时刻刻有可能改变,我们需要扩展性:


class User {
User() {
state_ = new InitState();
}

void ChangeState() {
state_ = new WorkState();
}

void OnReadData(const std::string &data) {
state_->OnData(data);
}
IState *state_;
};

class IState {
void OnData(const std::string &data);
};

class InitState : public IState {
//1.从数据库验证用户的合法性
//2.验证成功之后,转换User的状态,User::ChangeState()
};

class WorkStats : public IState {
typedef std::map<UInt32, ICommand *> commands_;
//1.和第一份代码的User差不多,只不能不再调度LoginCommand
};




拥有2个扩展性的代码也蛋生了,此时可以完成的东西就很多了,比如此时用户多了一个设置个人信息(SetInfo),或者用户登录之后,加载它的好友LoadState,都是方便增加进去。

最后再考虑一个需求,当用户上下线或信息改变了,它的所有好友都需要知道,在客户端可以改变显示。思考一下,好像有一种模式叫"观察者模式(Observer)",当信息改变后,就通知它的好友。


class IUserListaner {
virtual void OnLogout(User *user);
virtual void OnSetInfo(const UserInfo &info, User *user);
};

class User {
typedef std::set<IUserListener *> Listeners;

Listeners listeners_;
IState *state_;
};

class SetInfo : public ICommand {
void OnSetInfoDone() {
foreach (user_->listeners_ as listener) {
listener->OnSetInfo(this);
}
}
};


不知不觉写了一大段,是时候总结一下了:

想要应用好设计模式,其实最主要的是对需求的分析,对模型的抽象,之后再对每个需要的“点”用上相应的模式。

本来想结束,但是上面所提到的对需要的分析,和模型的抽象,虽然短短的2句话,但是如果要写地清清楚楚地话,估计没有1本完整的书也难于说清楚。在这边我在谈谈平时工作上的一些例子吧:

何为需求的分析?

简单地说“需求”就是别人想要的东西或者说问题,分析就是对该问题的分析,之后最后给出一个解决方案(很拗口)。举个例子:

1.某天服务器因为某些原因,错误地广播了大量数据包给客户端,导致很多用户断线,甚至登录不了。此时,技术部就提出一个需求“对广播在一段时间需要限制次数,比如1秒最多5次”。

2.服务器架构是:[C++服务器]+[PHP脚本],C++只处理用户的登录,维护用户的好友关系,而具体的一些请求是给[PHP脚本]处理,也就是广播数据包给用户的其实是PHP触发的,C++只是相应的把数据给用户。那上面的“需求”至少有2中解决方案:1.在C++服务器限制,2.在PHP脚本限制。经过分析之后,决定在PHP脚本做,原因是:

A。这个请求其实是PHP某个业务导致的

B。每个业务一秒触发的广播的最大次数可能不一样,很难统一处理。

C。C++服务器是有维护用户的某些“状态”,最好24小时不重启,如果最大广播次数不合理导致需要重启服务器就麻烦了。

何为模型地抽象?

抽象就是把现实的问题通过分析对应得到一个和程序逻辑相似的模型(再次拗口)。举个例子:

我们现在的需求是编写一个QQ群功能的软件。通过分析,我们能得到一个模型(用C++代码表现出来):


//用户类
//1.用户有会N个状态,上面已经提过
//2.用户有N个群,User与Group之间,通过UserGroup绑定在一起
class User {
typedef std::map<UInt32, UserGroup *> Groups;
IState *state_;
};

//User与Group之间的绑定
//1.用户在每一个Group可能有不同的备注,一些自己的设置
//2.用户针对群的请求也会派遣到Commands中去
class UserGroup {
typedef std::map<UInt32, ICommand *> Commands;
User *user_;
Group *group_;
};

//群的信息
//1.拥有N个用户
class Group {
typedef std::map<UInt32, User *> Users;

};

//所有用户的管理
class UserManager {
typedef std::map<UID, User *> Users;
};

//所有群的管理
class GroupManager {
typedef std::map<GID, Group *> Groups;
};

//一些异步任务的抽象,比如SetGroupInfo,是需要异步访问数据
class ITask {
};

//具体任务
class SetGroupInfoTask : public ITask {
};

//具体请求
//1.请求对应的一个UserGroup中
//2.请求可能有N个Task
class SetGroupInfoCommand : public ICommand {
typedef std::set<ITask *> Tasks; 
UserGroup *userGroup_;
};



上面是直接用C++代码描述出整个需求的模型,当然也可以用其他的方法,但是都无所谓。不知道你看了上面的模型之后有什么想法。如果你用一张纸把每个类的关系画出来的话,你会发现是一个很神奇,很清晰的图案。比如一个用户拥有N个群(UserGroup),而UserGroup里面又包含了N个Command,Command又包含了N个Task等等,结构非常之清晰。


怎么判断一个程序代码好或者坏?简单的方法就是叫作者把程序画一个图出来,看整个图的漂亮程度便可知道程序设计是否合理。

通过对需求的分析,数据模型的抽象,再加上自然优雅的设计模式,一个健壮,优美的代码终将出现在你面前。

加载中
返回顶部
顶部