代码设计

设计经验

  1. 解耦合的关键点在于模块化,怎么才能完全解耦合和,特殊模块和特殊模块之间要分离,比如declaration模块的方法就别去用order模块的东西,哪怕是type之类的,否则解耦合就会非常麻烦,type那些让order模块在调用前先处理完了。否则以后模块迁移的时候会发现乱七八糟的依赖一大堆。
  2. 这种方式究竟好不好呢?感觉很难说,如果用了这种方式,模块虽然干净了,但是迁移过去的装配成本比较高,比如你的一些接口没有涉及到处理方的话。两者的区别在于,一种接口定制性高,另外一种接口通用性高。各有各的好处吧,现在来说,还是稍微通用一些的比较好,自我感觉上更加干净一些。迁移成本方面基本也是一致的,你把装配部分也跟着迁移过来就没事了。但是不会使得代码依赖混乱。当然这针对的只是核心模块,像user这种的你不可能不去依赖。
  3. user那个是涉及到依赖层级,特殊模块依赖基础设施是正常的,但是特殊模块之间相互依赖则是不正常的。感觉上我们现在做的基础平台也是这么个道理,不做太多事情,实现一个通话化的接口。
  4. 业务层的东西不要放到基础层去处理,哪怕是处理起来有可能很方便,但是基础层不能去依赖业务层。调用层级不要太多,最好的选择是调用全部是平行的,只有少量而明确的层级关系,否则整个系统会很复杂。最好是不同模块的功能在handler层组装,特殊模块尽量只处理自己的事情,尽可能避免特殊模块依赖于其他特殊模块。组装法优于依赖法。
  5. 对于游戏来说,使用观察者模式有利于需求的扩展,比如谁关心一个事件只要订阅一下就好了,而我现在的做法还需要依次去调用逻辑
  6. 看了一篇面向过程设计(CO)的文章,面向过程利用组合子进行设计,其适用范畴为:问题域有比较少的概念,概念简明清晰(比如logger, predicate, factory,command),但是对概念的实现要求非常灵活的场合。
  7. 使用CO进行设计就是找组合子,其实很像设计一门语言,以顺序/分支结构作为基础就能实现很多功能
  8. 设计过程中记住单向依赖原则,不然就会出现互相引用这种情况
  9. 设计模式的本质就是一句话:将软件中变化的部分和不变的部分分开
  10. 在设计过程当中,对象并不一定非得是某种实体,也可是是对某种现象的抽象,比如说重构第一章中的租赁
  11. 一个场景:计算费用既需要影片类型数据也需要租期长度数据,那么应该将这个方法放在哪里呢?一般来说应该放在会增加新类型的类中,新类型的增加导致计算方式改变,这种改变需要进行控制,如果放在影片类中,那么这种改变只会影响影片类,而如果放在租借类中会同时影响两边。所以新类型带来的逻辑改变,这个被改变的逻辑要放在新类型所被包含的类中。
  12. 关于mvc和mvvm结构的文章http://www.cnblogs.com/indream/p/3602348.html
  13. 关于依赖注入,之前一直觉得依赖注入的好处在于其类似注册制,也确实是这样,但是注册制并没有那么美好,因为当你需要扩展一个功能的时候并不能简单通过注册来搞定,最初的装配是可以通过注入来解决的,但是注入并非万能的。(即在多层级调用的场景下要改变底层的功能还是需要通过传递参数来实现)于是又看了几篇文章,发现依赖注入的形式类似注册制,但其好处主要是由spring控制对象的生成与回收,提高效率,并且通过spring-aop等机制可以有效地实现aop等增强。昨天说起过什么是依赖,依赖其实就是一方如果需要调用另一方,那么就是一方依赖另一方,spring将依赖实体转为依赖接口,然后将实体注入进去。那什么是控制反转呢?控制反转就是原先是顶层控制底层的实例化,现在是反过来底层先实例化完,然后注入进上一层。所有其实注册制就等同于控制反转。控制者由调用者变成了spring框架。好处是所有的控制没有耦合在代码中,而是可以提取到一个公共的地方。

设计模式

  1. builder设计模式适用于构造复杂对象
  2. 关于设计模式可以看看这篇文章https://www.zhihu.com/question/39972591/answer/156167182

UML

../_images/21A72F17-DDE4-403F-86AF-587BE06FB9B3.png

一般性逻辑模型总结

总结逻辑模型这个想法是我在写复刻属性计算器的过程当中想到的,先说复制一下之前说的一些东西

  1. 经常出现的场景是需要对一个函数进行封装,因此会需要使用到将一个参数经过一定的外层处理后传入被封装的函数,为了一致性,常常会用相同变量名进行处理,平时不会有问题但是一旦到了循环当中就会出现大问题,原先的变量已经改变不能用来做下一轮的判断,这个坑一定要牢记。
  2. 其实搭建一个流程的核心点还是应该着重于数据的处理,你首先要知道这个黑盒的输入输出,然后大致确定黑盒当中的流程,再确定每个步骤的输入输出,然后去根据这些确定的内容来写。所以过程本身是处理数据的过程,将输入输出确定了那么对于过程的构造也就得心应手了,对子过程的构造也是要通过一般性的模型确定核心的中转数据,反正归根结底就是逻辑和数据。比如对于这个项目,首先要确定的是通过一系列影响因子,确定最终的属性值,那么大的函数可以先确定下来,然后如何进行分解呢?通过一般性模型,在大的函数当中需要确定6项属性值,因此可以拆分出一个确定属性值的函数,通过循环去做。然后确定这个确定属性值的函数的输入输出。确定之后发现这个函数已经没有必要细分下去了,其流程不存在可拆分的一般性模型。
  3. 还有一个点是从java当中学到的,不要在函数当中写死数据,需要被重用的数据都写入全局数据结构变量当中,这样要修改可以统一修改,函数当中要定义的变量必须是确定只有这一个函数才会使用到的。从这个角度来看java的枚举的确要比python清晰一些,要改的时候可以通过idea一键修改掉,python如果用一个字典来代替的话就不具备这个功能,最好的方式可能是某种数据结构,应该在标准库当中是有替代品的。

先写一下我目前浅显的理解

  1. 工厂模型:需要被处理的数据可以被拆分为一系列同类数据的组合,且这些数据可以以大致相同的模式进行处理,这样就只需要一个统一的工厂函数作为内核,将拆分后的数据包进行加工,之后再将加工后的数据包进行组合,产生输出即可。在这种模型当中,步骤为拆分数据-依次加工-组合数据。(怎么没有例子呢?)
  2. 工作池模型:数据需要进行两步骤的处理,比如爬虫程序,第一步需要爬取,第二步需要存储,如果直接爬取下来存储效率在某些情况下是会降低效率的(哪些情况呢?好像有点疑惑),如果存储阻塞会导致爬取无法进行(在速度不稳定的情况是容易降低效率),所以最好的方式是分成两个线程,一个负责存,一个负责取,两者通过一个线程安全的队列来共享数据(也就是操作的时候都会加锁,操作完成才释放),公司app的消息队列就是这样一个存储池,需要发通知的所有业务都是属于这个逻辑模型范畴的

目前我遇到的应该只有那么些,如果有新的就记录下来。

设计案例

  1. 使用缓存解决标签浏览量的更新

首先需求是拉一次数据要批量增加标签浏览量,但一次数据可能是存在多个不同的标签,所以需要更新多个标签的数据。

解决方式:使用缓存在存储标签浏览量的增加值,每次需要获取最新浏览次数的时候更新一下这个值,这样就能做到对于n次刷新页面,数据更新次数小于n。

设计思路:其实本质上就是要减少一定次数的请求下的数据库处理次数,所以采用先用缓存临时存储,只在迫切需要的时候进行更新来降低更新次数。

  1. 事务级别问题的缓存解决方案

这个我自我感觉已经用得比较纯熟了,问题的根源在于很多事务是先查询后执行的,一个事务的周期偏长,如果一个业务逻辑只允许产生一条记录,那么在一个事务结束之前有可能有另外一个并发事务进来,而又由于mysql默认事务级别无法可重复读(可重复读的话岂不是会造成死锁?),在读取时不加锁,因此会造成一次插入了两条记录的问题。解决的方式是并发抑制,一个事务开始了,终结掉另外一个事务或者推迟另外一个事务。

要注意的点:首先一般使用的是终结掉另外一个事务,但是要做到开始一个事务之前查缓存,查完立即存缓存,这样就能保证最小的时间间隔,但是有时候由于不适合立即终结,可能需要在事务中进行,这里选点要选好,并且时刻要牢记保持锁与存的最小时间间隔。ps:如果要实现推迟可以使用一下thread.sleep,但是可能会有更好的阻塞式方案。

  1. 缓存方案与数据库方案的比较

首先界定一下场景,主要是基于业务中的数据以什么方案进行存储,比如置顶女生状态的数据。

其次缓存方案与数据库方案相比较而言区别点主要在于读写速度、sql与nosql、事务级别、落地与非落地上,缓存的优势是读写快,而数据库方案的优势是支持结构化查询、有事务级别(在复杂事务的处理中有绝对优势)、能够数据落地。

所以什么时候要使用缓存呢?主要是读写比高的地方,如果一个地方需要频繁改动,那其实不适合使用缓存。还有一个用途就是分布式锁,上文有讲,再另外一个是用于一些不需要落地的中间数据的存储。

至于置顶状态这个东西,我当时设计的时候选型其实错了,这个东西根本不适合用缓存做,首先速度快的优势没有发挥,其次查询的时候由于缓存的nosql特性,每次查和改都是对同一条记录做改动,增加了高并发下的压力,虽然不需要落地这点是有意义的,但是没太大用。而且使用缓存设计上就会比较复杂,每次都要对数据进行遍历,非常麻烦,遍历过程中还要做一些复杂的操作,远没有使用数据库要简单,总而言之,这是一次非常失败的设计经历。导致这一问题的主要原因是我没做之前傻傻地认为缓存方案适合高并发,no,其实是缓存实现的分布式锁适合高并发,缓存并不能单独用来维护一堆状态。而且还是读写比不高的情况下。