ZX编程随笔(三)

  1. 2010-09-07
    MVC = BMVC
    MVC思想几乎存在于软件设计中的所有层面,宏观的、微观的...等等,在任意尺度的软件设计中,都能够体悟到这条古老原则的指导性意义。软件设计的关键问题是是否能够很好的捕获M,将它和V、C相分离,也就是将相分离,概念和功能相分离,抽象和表象相分离,内存与外设相分离...大部分失败的软件,都是将M与V相混淆,犹如把握不住本体,而被浮光掠影所迷惑。
    MVC的实践中,从来离不开B(Builder)的作用——M在C的控制下发生V的功能,这指的是模型的生存期,而在此之前,必然还有一个模型的构建期。构建期的重要性,研读过设计模式就知道;构建期的复杂性,看看Spring做成了框架就可知道。没有B就没有M,所以,MVC即BMVC。
    由于B与M处于不同的运行时刻,实践中,应将B与M作清晰的划分,它们具有不同的设计,不同的代码,不同的功能与性能要求,甚至不同的用户,不同的环境,进而不同的产品。
  2. 2010-09-14
    最重要的决策:“哪些不做?”
    在前篇诸多片段中,都有涉及到控制范围之重要性的内容。最近的开发,使我再次强烈地意识到这一点。我们不能仅仅局限于类或包这些小级别的范围控制,在大级别如确定软件的功能清单方面,更须如此。去掉那些不太重要的特性,以显露真正的架构思想。失败的做法通常是:弄巧成拙,画蛇添足,软件迟迟不能投入使用,用上了又发现太多无用的功能。
  3. 2010-09-24
    为什么有了类还要有接口?
    接口(interface)代表对象“能干什么”,而类(class)则代表对象“是什么”。在大多数对象的调用者(容器或算法)中,并不关心所处理的对象到底“是什么?”,而只关心它“能干什么?”。由于类具有继承和多态性,这使它似乎也可以用来描述“能干什么?”的问题,但这样做通常是笨拙、不合理的,因为类的职责主要是定义概念、刻画关联等,以构建完整的概念模型;在行为方面,应交给接口去负责。
  4. 2010-09-30
    关于Ruby
    Ruby有很多令人过目不忘的优秀品质,如内迭代、算法块、类开放等等,但也有令人极其遗憾的地方,最令人不安的是它的变量竟然没有类型,这使我们这些以模型为根本的C++程序员非常的不适应。我承认Dave Thomas讲的也很好,也很有道理——“像鸭子那样编码”,但确实很不适应,尤其是读别人代码的时候。我觉得Ruby这种模糊类型的做法尽管很高效,但的确与概念模型(即领域模型)的思想相悖。也许这就是动态语言的特点吧,我不知道其他几种动态语言是不是也这样,但C++、Java、C#这几种主要的静态语言都不这样。有没有好的办法呢?折中一下,或许我们只能再写一个Ruby-C++编译器。
    不知道路过的人对编译方法有否研究,但其实开发新语言可能是最有效的创新方式之一,至少对锻炼大脑极有效果:)
  5. 2010-10-20
    Java的“单根继承+接口”比C++的“多重继承”更优越吗?
    从简单性来说是这样,Java用户只能使用单根继承和接口,这就可以更自然地遵循面向对象设计原则:不滥用继承;关注对象的行为而不是类型等等...并且,由于不支持多重继承,避免了诸多麻烦,如基类成员的名字冲突、构造次序不易掌握等等...另外,Java的编译器构造也更简单,因为Java接口等同于C++中没有任何状态和实现的虚基类,由于非常规范,就更容易控制。
    但接口与虚基类的区别应该主要是在设计层面上的,即前篇所分析的“是什么”和“能干什么”两个问题上的区别。熟练的C++程序员一样能够使用虚基类进行“面向接口编程”,且毫无疑问,虚基类比接口更为强大,接口仅仅是虚基类中约束较多的一种,有时候,这些约束不见得就合适,比如不能定义实现方法。
    最后,我推测,C++的虚基类应该比Java接口的性能更好。
  6. 2010-10-27
    C++的模板能用来干什么?
    印象里,C++模板都是用来实现容器(Container)类的,如vector、map、list等,还没有发现模板在领域对象设计方面有很好的应用,因此可以说,模板适于容器设计——这和继承适于领域对象设计一样直观、容易理解。本质上,模板并不是面向对象的,而是面向行为,这是它适合于容器的根本原因,因为容器也是非面向对象的,容器丝毫不会关心它里面放的到底“是什么”...
    另外,容器还有两大特点:1)种类很少;2)行为很单调。种类:数组、树、哈希表;行为:遍历、查找、过滤。但尽管如此,我们还是会发现:我们的大部分代码都是在处理这些容器,尤其是在C++代码中几乎到处都是for、iterator这些语句,这一点也波及到Java、C#这些“新型”语言,可以说,C++在容器方面的一贯做法是令人非常失望的...
    不错,模板适于设计容器,但容器不仅仅需要模板,还需要其他更灵活的机制,比如Ruby中的块...
  7. 2010-11-22
    权衡设计的成本和收益!
    我们有许多昂贵的设计,为了它们我们付出大把的时间和心血,但后来,我们发现它们并没起多大的作用,我们期望中的灵活性根本没用上,我们预先安排的可替换的类以及可扩展的接口统统没有任何需要变更的迹象...太令人沮丧了!更令人难过的是,也许我们还丧失了简单性,我们的程序反而比那些没过脑子的程序更难理解...
    在引入那些华丽的设计之前,最好先想想简单性原则,是不是应该先让它工作起来,然后在以后的重构中慢慢注入灵活性,也许那些花个一两天写出的滥代码也可以一直用到软件被换掉...
    特别是,那些因为追求性能而反复重构的代码,到后来证明只不过是交给一名慢吞吞的后台管理员使用的小工具,或者是一两个月才需要运行一次的批处理程序,优化个什么劲啊...
  8. 2010-11-26
    面向对象为什么难用?
    通常一个系统只有20%是模型部分,而其他大部分都是视图或者服务方面的内容。只有模型才需要使用面向对象的建模技术,如果处处都用,则为滥用,过犹不及,感觉难用也就是很自然的事情。
    结构化才是软件的真正基础,面向对象属于高端(或者说成本较高)的技术,不是全都适用的。
  9. 2010-12-06
    最简单的面向对象法则!
    所有的循环语句(for-do-while)都是框架——容器上的迭代,所有的分支语句(if-else-switch)都是算法路径——对象的组装。
    最简单的设计法则!
    简单性既是设计的出发点,也是终点;28原则适用于任何问题——先关注并且只关注那最容易看出来的20%,其他的80%或许迎刃而解,或许最终证明无足轻重。
  10. 2010-12-27
    慎用继承!几乎在任何时候使用接口都是对的...
    基类强迫其所有派生类具有死板的、不可更改的数据成员(因为派生类本来就包含基类),这使得继承关系也非常的死板,你很难用继承来关联几个仅在某一切面上具有相似性的概念;接口则天生具有这方面的灵活性,接口里面没有数据,但通常会用接口方法的返回值来代表数据,而接口方法又具有无限的伸缩性...这听起来像是稀松平常的道理,但我还是经常会发现在构造模型时(尤其是在早期)轻易地选择了类的继承关系而不是接口的实现关系,大部分原因是那些早期出现的几个概念碰巧拥有相同的数据,我们会很自然地会把它们提炼到到基类(超类)中,但随着系统的演化,接口的本质开始显现,新发现的概念不再包含明显相同的数据...
    结论:类侧重于数据的包含,接口侧重于行为的提炼。
  11. 2011-01-09
    对象并非全部…
    面向对象强调方法的设计,方法体现了对象的职责。然而,方法总是依附于对象的,这使得它们只能通过层层调用、层层转发来实现复杂的事务(这一过程即对象协作)。特别地,当某一事务需要众多的对象参与时,为了实现复杂流程中的各个环节,硬性地安排一个个职责到参与的对象中,不见得是明智的做法,也违背了单一职责法则。一个经验是,凡在固有关联之上解决问题的算法,多半应封装成服务。服务调用模型中大量的对象,但并不改变对象的关联关系。这和视图有些类似,视图差不多是在模型的基础上迭代输出一个临时的结构,通常仅包括模型的一个切面。
    模型的目的是为了输出视图和服务,但模型有时也需要服务的支持,在许多深度对象结构中,一些底层对象需要调用高层服务,才能构成易于理解而又高效的系统。
    切记:对象并非全部,还有大量非对象的合理存在。
  12. 2011-01-11
    对象模型在内存中的映像总有点像“一簇葡萄”,不管你是采用单子(singleton)模式,或者简单的全局变量,总得有一个“柄”(handle)来作为整个模型的入口(entry)或者根(root)。
    在引用对象模型的时候,比如通过传参调用函数时,基本上都必须传递整个模型的“柄”,而不能只是其中的某一对象。只有这样才有利于维护模型的完整性,或者实现顺畅的调用,又能很好地表达对象之间的依赖关系。
    如果场景确实要求访问模型中的某一特定对象,那么最好让模型中的所有对象都持有该模型的“柄”,这样做完美地实现了局部和整体的统一,并且简洁、易于理解。
    参考:
    《领域驱动设计》(清华大学出版社 陈大峰 张泽鑫等译) P.88 6.1 聚合
    《重构》(中国电力出版社 侯捷 熊节译) P.288 10.7 保持对象完整
  13. 2011-02-11
    “面向对象就是面向接口”的来由
    总感觉面向对象方法就是构建在结构化方法上的一组经验法则——最终总是收敛到结构化方法。。。我们知道,数据大概就是所有访问的终点,到达数据之后,任何复杂的访问过程也就结束了,任何关于灵活性、伸缩性的努力也可以结束了,这就是面向对象千方百计避免直接访问数据的原因!从最初引入private隐藏数据,到使用interface拒绝数据成员,再到service、SOA、REST...等等,似乎都在沿着这条主线演进,真可谓大道至简、大道至简!数据与算法(代码)的关系,在MVC思想下,大致类似于模型与视图的关系;在OO方法中,大致类似于对象和接口的关系。
    回顾在由C向C++发展的过程中,最明显的变化就是C++引入了类(class)来替代C的结构(struct),很明显,这仍然是“数据+算法”的思想,对象差不多就是数据!而当我们发现数据的呆滞、笨重、阻碍之时,接口的概念就自然而然产生了……
    参见:
    《REST架构实质》板桥里人 http://www.jdon.com/jivejdon/thread/36506
  14. 2011-02-16
    系统架构趋于稳定的标志
    ……终于发现一个最最重要的概念,该概念(的实现)对应一个简单、灵活、完整的框架,框架内包含一组定义良好的接口,系统内其他对象都通过实现接口而纳入框架的管理之下,除了接口的规定之外,没有任何额外的职责(方法)
    显而易见,困难之处就在于如何找到那个最最重要的概念——也就是体现架构思想的概念……可能需要经过大量的重构,删除大量的代码,然后它才会慢慢地浮现……还有,你必须持续不懈地进行抽象——忽略细节、放大本质、自底向上……
  15. 2011-03-01
    数据库还是有用的
    对象模型与关系模型的“阻抗不匹配”,似乎是一个已成定论的命题。但真正的系统开发,还是要认真对待数据库的问题,至少在数据源这一层,数据库仍是绝对的主宰,对数据库的编程,也是决定成败的因素之一。
    这与那些汗牛充栋的论述并不矛盾,关键问题是:对象是对象,数据是数据,你要分得清,分得开,这才是真正需要水平的事情。打个粗略的比方:对象模型是人的大脑,则关系模型就是人的双脚;或者说,对象是高高在上的建筑,数据就是下面那坚实的大地……前篇不是已有论述:“数据是呆滞、笨重、阻碍的”,而这正是所谓“基础”的特征啊。
    使用数据库主要出于两个目的:
      1、共享:包括不同权限、不同空间、不同次序的种种访问;
      2、管理:和批量有关的大量操作,包括备份、恢复、迁移、转换、导入、导出、审计、校验等等;
    除此之外,数据库似乎还有其他一些事务相关的功能,诸如防脏读、脏写等等……好了,就到这儿吧,千万不能再继续下去,数据库也就只能做这些事儿了!超出这个范围,数据库必然开始侵蚀领域模型,这才是真正得不偿失的事情——如果无法避免,干脆就别用数据库了。
  16. 2011-05-04
    服务和一般对象的区别
    前篇《对象并非全部…》中提到:
      “服务调用模型中大量的对象,但并不改变对象的关联关系。”
      “在许多深度对象结构中,一些底层对象需要调用高层服务,才能构成易于理解而又高效的系统。”
    那么,作为面向对象模型的重要组成部分,服务和一般的对象相比到底有哪些区别呢?
    如果再综合《“面向对象就是面向接口”的来由》一篇的思路,很自然的推论即是:对象是数据的抽象,服务是算法的抽象。
    但千万不要把服务理解为结构化程序设计中的通用例程——服务来自于领域,是领域模型的重要组成部分,服务蕴含着客户领域的重要概念,但不像其他领域对象一样拥有数据(状态);通用例程则是抽象算法的实现,属于底层,不包含任何领域含义。
    小结,服务的特性:
      高层:面向领域,蕴含领域概念;
      无状态:不包含数据,可以灵活配置、传递、调用——这是服务最为强大的特征;
      多协作:服务调用两个或更多的领域对象(接口),但不改变被调用对象之间的关联关系;
  17. 2011-07-22
    重构与突破
    不要企图“制造”突破,那会使您无法动弹。通常,只有在执行了许多细微的重构之后才可能出现突破。我们的大部分时间都会花在细微的改进上,连续的每次精化使我们对模型的理解节节进步。
    为了为突破创造条件,我们必须集中精力消化领域知识,并提炼出一套稳定的通用语言。寻找那些重要的领域概念,并将它们显式地体现到模型中。接着,精化设计使之更具有柔性,最后精炼模型。利用这些更易于把握的手段来增进我们的理解——这往往能成为突破的先驱。
    不要犹豫作一些小的改进。即使这些改进并没有突破原来的通用概念框架,也会逐步深化我们的模型。不要企图看得太远,那样只会反受其害。只要随时注意可能出现的机会就行了。
        ——《领域驱动设计:软件核心复杂性应对之道》(美)Eric Evans 
          清华大学出版社 2006-3-1第1版 P.151
    深入阅读:
    第⑼章 隐含概念转变为显式概念:概念挖掘、如何建模不太明显的概念……
    第⑽章 柔性设计:释意接口、无副作用函数、断言、概念轮廓、孤立类、操作封闭、声明性设计……
    第⒂章 精炼:核心领域、精炼的逐步升级、通用子域、领域愿景声明、突出核心、内聚机制、精炼到声明性风格……
  18. 2011-11-12
    测试代码属于产品的一部分
    在产品代码中包含测试、托架代码,且包含在正式发布包中的方法,是敏捷过程迭代式开发的一大特征,这是因为:
    敏捷测试,并不是独立于开发的另一过程,敏捷测试与开发,是敏捷过程中紧密联系的两个环节,与传统测试不同的是,敏捷测试并不是基于"验证"的观点,而是基于"反馈"的观点,敏捷过程,本质上是一种"迭代-反馈-再迭代-再反馈……"的循环过程,著名的最佳实践TDD--测试驱动开发,就是这种思想的极致体现。
    测试的重要性在于,它不仅仅是对上一步迭代成果的验证,更多的情况下,它是下一步迭代的开始或原动力,因为,我们关于领域中的许多深层次知识,正是或者必须由频繁的测试所获得。
    在一个迭代开发的产品中,测试代码是其中最重要的组成成分之一,这既在于产品的质量和可靠性,更关乎产品的长远未来。
  19. 2011-11-12
    使用模板(template),同时使用接口(interface)?
    前篇有述:“模板适于设计容器”,用模板设计的容器,具有良好的表达能力,同时具有严格的约束性和更高的性能,但在用模板设计的容器中存放接口实例,也就是模板与接口混用,则会产生极不协调的重复性问题,会显得“不伦不类”,因为模板与接口一样,都是面向行为设计的技术,它们对所容纳或实现的对象在类型方面没有任何要求。在模板容器中,完全可以直接保存任何具体类,没有必要再利用抽象接口,如果那样,反而会削弱模板机制的带来的好处。
    模板和接口,都是良好的面向行为设计的技术,但它们之间的区别还是很明显的:接口,需要严格的声明,属于显示的约束,适于构建庞大而又易于扩展的体系;模板,不需要任何正式声明,只在编译时刻进行检查,因此可以说模板需要的是一种非代码的“规范”。
    毫无疑问,即使没有任何代码,“规范”也是架构设计的重要组成部分。

你可能感兴趣的:(设计模式,编程,算法,领域模型,Ruby)