架构师修炼 III - 掌握设计原则

关于软件的设计原则有不少,对于设计原则的掌握、理解、实践及升华是架构师的一项极为之必要的修炼。 记得在12年前第一次阅读《敏捷开发》时,五大基本设计原则就深深地植入到个人脑海中一直影响至今,我也由此获益良多。设计原则固然不止只有五种,最主要的面向对象的设计原则有如下这些:
 
  • 单一职责原则 (SRP) - 就一个类而言,应该仅有一个引发它变化的缘由
  • 开-闭原则 (OCP)- 软件实体(类,模块,函数等)应该是能够扩展的,可是不能够修改
  • 里氏替换原则 (LSP)- 子类必须可以替换它们的基类型
  • 依赖倒置原则 (DIP)- 抽象不该该依赖于细节。细节应该依赖于抽象。
  • 接口隔离原则 (ISP)- 不该该强迫客户依赖于它们不用的方法。接口属于客户,不属于它所在的类层次结构。
  • 重用发布等阶原则 (REP)- 重用的粒度就是发布的粒度。
  • 共同封闭原则 (CCP)- 包中的全部类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的全部类产生影响,而对于其余的包不形成影响。
  • 共同重用原则(CRP)-  一个包中全部类应该是共同重用的。若是重用了包中的一个类,那么就要重用包中的全部类。
  • 无环依赖原则(ADP)- 在包的依赖关系图中不容许存在环。
  • 稳定依赖原则 (SDP)- 朝着稳定的方向进行依赖。
  • 稳定抽象原则(SAP)- 包的抽象程度应该和其稳定程度一致。
  • 合成/聚合 复用原则(CARP)- 要尽可能使用合成/聚合 ,尽可能不要使用继承
  •  …..
 
固然面向对象的设计原则远远不止这些,设计原则是伴随着开发语言的发展应用和软件开发经验的累加总结得出的经验汇总,随着语言的演变、开发方法的进步还会不断地衍生和进化出更多的的设计原则。应用设计原则能够避开不少的设计中的陷阱与误区,但在应用设计原则的同时须要紧记一点:设计原则本质上是一些经验条框,是设计的导盲杖而不要让它们成为束缚设计思想的牢笼。
 
每一个架构师在经历长期的实践后也会慢慢创建属于本身的设计原则。多年来我也总结出了一些设计原则,并将上面这些这种可用于代码设计的原则概括为:“代码设计原则”,另一些应用于意识与设计过程当中的原则称为“意识-行为原则”。如下我将会分别讲述我对这些设计原则的理解与运用的经验。
 

意识 - 行为原则

 
  意识决定行为,不少的设计失误并不单纯源自于对设计原则的把握不足,而更多可能源自于架构师在乎识指导上的错误, 因此在开始设计以前应该先创建正确的思想与意识引导。如下的这些意识-行为原则是我从不少次的跌倒中总结出的一些心得,将期做为原则是为了时刻引导本身不会在相似问题中犯错。

坚持创新原则

  首先谈谈模板式设计,我相信模板对于每一位开发人员和设计人员来讲都是很是好的东西,由于它能够“快速”构建出“成熟”的代码、结构或UI。“拿来主义”在业界盛极不衰,对于架构师而言模板也有这种功效,在设计的过程当中咱们会常常遇到不少必须而不重要的“鸡肋”模块,没有它们系统会变得不完整,而它们的存在并不能为系统增长任何的“特点功能”,如:用户管理、角色管理或系统设置等。常见作法是,直接采用第三方模块或是从已有的其它项目中复用相似的模块,你是这样的吗 ?至少我是常常这样作的,由于咱们的中国式项目一般是“验收驱动”,能经过验收、成熟可用就好。若是整个项目都只是由各种模板化的模块所构成,那么这个项目其实不须要架构师,由于不存在任何设计,全部的工做只是一种“融合”(Fusion)。可能这样说会有不少人会吐槽说这是一种“资源整合”能力,从“赶项目”的角度来讲这无可口非,但从技术含量与本质上说确实不存在任何设计成分,这类拼装性或是“复制”性的项目只须要项目经理配备几个高级程序员就能完成了。html

我曾在“表达思惟与驾驭方法论”一文中提到与销售的沟通方法,其中就有一条:“至少说出系统的三个特点”,这个表述对销售具备市场意义之外 , 其实对于架构师是起到一个重要的提醒做用同时也是在创建一种设计原则:程序员

  架构设计中模板的拼装是不可避免的,重要的是必须加入属于你的特点设计数据库

很难有人记得住整个软件的设计师,而却很容易记住某项极具特点功能的设计者。“特点” 是架构师在软件中所留下的一种重要的印记,也是在团队中配备架构师的意义所在。设计出彻底可被模板化重用的设计是一功力,而当中小型企业内出现这样的设计之日就是架构师离开企业之时,或许这也是当下中国架构师之殇。保持特点保住饭碗,你懂的。编程

 

固守本质原则 

  惟一不变的就是变化自己  —  Jerry Marktos《人月神话》
  不变只是愿望,变化才是永恒  —  Swift
 
看到这两句经典是否是猜到我想就“变化”二字来一次老生常谈 ?其实否则,这两个字在业内估计也讨论了20多年了,也说烂了。我之因此引用这两位大师的名言只是想无时无刻提醒本身要了解身边的每个变化,对他们的源头产生兴趣,从而深刻了解。世界上不会有平白无故的爱,也没有平白无故的恨一切皆有根源,那是 “本质”。咱们来将 “本质” 与 “变化” 这两个哲学性的问题应用到软件开发的范畴内来看一个软件产品的迭代:
  • 用户的需求在变 - 他们须要增长更多的功能,要求更高质量的用户体验。
  • 代码在变 - 不断的重构、测试,持续集成,让代码变得容读,稳定。
  • 老板的想法在变 - 由于市场需求在变,须要为软件加入更多的特点知足市场。
  • 架构在变 - 采用更新式的技术体系,得到更高效的生产力,更为稳定、安全的运行环境。
 
而惟一不变的是:软件的核心。正如:Windows 变了N个版本最后仍是操做平台,Office 衍生了多代后若然在处理文档文件 。
 
  变化是表像,不稳定且可定制的;本质是核心,必须稳定,可扩展而不可修改;被固定的变化则可归入核心。
 
  架构应从本质入手,一切复杂的事物都应可被分解为简单的原理和构成,本质以外的内容皆可变化。咱们来举例说明,设计一个电子商务网站,其核心就可被分解为 “购物车” 与 “订单状态跟踪”这是不可变的除非大众的总体购物行为发生了本质上的改变,为了增长用户体验咱们选用美观温馨的界面套件如BootStrap,若是进一步提高用户体验则能够采用SPA的架构让客户在Web上得到Native式的使用体验;为了让用户使用不一样的支付方式,咱们就须要定义支付网关接口(引入变化)支持已有的支付平台,也为未来“可能”出现的支付平台留有扩展。为了加强网站对用户的粘性,咱们就须要增长社区模块,并采用云存储或是其它的BigData技术以支撑大数据量的运转;....  最后,一切的本质仍然不变,电商网站,变的是扩展性、易用性、伸缩性等等。架构师能够向其中添加的功能太多太多,但必须固守本质才能让整个产品不会成为一个由高技术打造出来的怪物,在增长新功能时参考 “代码商人”原则的指引。
 

“代码商人” 原则

永远不要投资将来,毫不设计没有回报的功能设计模式

不知道你是否拥有相似的经历:安全

  • 在与客户的交流中,你的老板和经理在不断地向客户描绘“将来”图景,而在其中包含了不少几乎是客户没有须要的特点 ?
  • 在你设计总体架构时,有一种冲动让你很想将某项由灵感触发对于系统“未来”的扩展须要颇有用的功能或模块加入其中呢 ?
  • 在你的代码里面有多少个方法或类是能够被删除,但你认为他们能够用于“之后”扩展而“手下留码”的呢 ?
  • 你是否曾与经理或项目组长为了是否增长某个颇有可能成为特点且被你实现出来的功能争论不休呢 ?

  衡量标准的尺子掌握在架构师手中,若是设计中出现林林总总的这些“将来功能”您会如何来对待呢 ?是直接砍掉仍是将其包装成为“特点”呢 ?此时架构师不仅仅是须要做为一名技术人员的角度考虑这个功能是否在未来可用,而更多的是须要考虑“成本”。每一个功能甚至每行代码都须要付出“人-月”成本,一旦成本失控,软件就会化身“人狼”吞掉你的项目,而最后也只能后悔没有找到“银弹”。每一个“将来”功能如何不能对现有项目带来即时性的回报,必须砍掉!即便这个功能有如何的美妙、高深或是在未来具备非凡的意义,仍是将它放入“研究室”成为其它项目的技术储备吧。站在商人的立场:每一分钱的成本投入,都须要有足够的利益回报架构

  将来永远是美好的、丰满的同时也是浮云,而现实却每每是充满骨感。在架构或代码中透支将来极少数可得到回报,由于这些“投资”都具备不可预见性只是一些尝试,在产品中除了“市场策略”须要外的这类过度投资就得有陷入“维护将来”的心理觉悟。新的功能、将来的特点更应该收集起来,做为一下版本中可选项,经过详细的市场研究再考虑加入到产品中。固然,对于大型软件企业这个原则基本上是多余的,由于不少成熟的软件企业对需求的控制极其严格与规范。但若是你所在的企业尚未这样的管理意识,或具备超脱性的设计自由,那么这条原则是很是重要的,咱们是用代码换钱的人,更少的代码换更多的钱才是咱们最基本的生存须要。异步

 

重构优先原则

在没有代码的时候就应该重构,重构是写出优雅代码的方法而不单纯是修改代码的理论。async

骆驼与账篷的故事ide

  在风沙弥漫的大沙漠,骆驼在四处寻找温暖的家。后来它终于找到一顶账篷,但是,账篷是别人的(也许你的处境跟它同样)! 

  最初,骆驼哀求说,主人,个人头都冻僵了,让我把头伸进来缓和暖和吧!主人可怜它,答应了。过了一阵子,骆驼又说,主人,个人肩膀都冻麻了,让我再进来一点吧!主人可怜它,又答应了。接着,骆驼不断的提出要求,想把整个身体都放进来。 

  主人有点犹豫,一方面,他惧怕骆驼粗大的鼻孔;另外一方面,外面的风沙那么大,他好像也须要这样一位伙伴,和他共同抵御风寒和危险。因而,他有些无奈地背转身去,给骆驼腾出更多的位子。等到骆驼彻底精神并能够掌握账篷的控制权的时候,它很不耐烦地说,主人,这顶账篷是如此狭小以至连我转身都很困难,你就给我出去吧

  这是一个颇有寓意故事,若是将其比喻为开发过程也颇有意思。对于“发臭”甚至“腐烂”代码咱们会立刻说“重构”,但重构是否能解决一切问题 ?你是否试太重构失败呢 ?重构在什么状况下是不可用的呢 ?若是这些问题在你心中是没有准确答案的话, 我建议能够从新去阅读一次《代码重构》一书。我认为重构不单纯是一种开发期与代码回顾期所使用的方法,而是一种设计与编码的思想指导!在设计期就应运用重构中的原则,那是否就能够“防腐”呢 ?答案显然是肯定的。重构的每每不单纯是代码,而是开发人员、设计人员的思想,不执行甚至没有代码规范、随意命名、随意复制/粘贴、随意调用这些都必须被杜绝。我并非指在设计重构就不须要重构,只是这样作的意义能够大量减小因为发现“臭”代码而去重构的成本 。

  这也能够说是一个团队性的开发原则,在项目之始就得有统一的编码规范(直接使用官方规范),并将重构中的基本代码重构方法也归入规范中,在开发过程当中强制执行规范,对任何可能“腐化”的代码绝对的“零”容忍,痛苦只是一时,但好处倒是长久的。

 

代码设计原则

 

开放-封闭原则 

开放封闭原则又称 开-闭原则 Open-Closed Principle (OCP) 

软件实体(如类,模块,函数等)应该是能够扩展的,可是不能够修改。

  OCP是一个极为之出名的设计原则,简单的一句话就归纳了可时该“开放”可时该“封闭”。这句话看起来很简单,一看彷佛也会以为本身领悟了什么,仔细咀嚼却以为内中深意无限,到底应怎样理解这句话且将其应用于设计中呢 ? 我参考了很多国内的资料对此原则的总结,感受就是雾里看花,没有办法找到最为贴切的解释。

我想分几个方面来诠释这个原则:

从类设计的角度

  在类设计的应用中开-闭原则是一种对类的“多态”控制原则。开闭原则在基类或超类的设计中由为重要, 能够简单地理为对 成员对象的做用域 和 可“重载”成员 的控制指引原则。按 “里氏替换原则” 基类成员一般对于子类都应该可见,也就是说基类成员的做用域的最小做用范围应该是 protect , 若是出现大量的 private 成员时就应该考虑将private 成员们分离成其它的类,由于些成员都不适用于其子代而违反了“替换原则”,而更适用“合成/聚合原则“。

  在运用 virtual 关键字时需甚重考虑,除了针对某些特殊的设计模式如 ”装饰“模式须要大量 virtual 的支持之外,在没有必要的状况下尽可能避免。定义可重写的成员为子类预留了”改变行为“的余地,但同时也是为子类违反”替换原则“埋下了地雷。当子类中出现大量重写成员的时候就得考虑该子类是否还应该继承于此类族,由于子类在大量地违反”替换原则“时就意味着它知足了被分离出类族的条件。同理,在C#内一但须要在子类内部实现基类接口时也须要做出一样的考虑。

 

 注:里氏替换原则是开-闭原则的一种重要补充,在类设计中通常是同时使用。

 

从模块设计的角度

  模块设计的“开-闭原则”是侧重于对接口的控制。而这个在整个架构中也尤其重要,由于模块间的“开-闭”是直接影响系统级的耦合度。模块间的开闭须要“衡量成本”,并非将全部的细节都开放使用模块具备极强的可扩展性就会有很高的重用度。首先要看了解几点:

开放性与维护成本成正比关系

  接口的开放必须带有使用说明,这会增长团队开放的沟通成本同时一但接口发生改变将可能带来额外的“说明性重构”成本。在某些状况下咱们很容易被“高扩展性”所引诱将不少“可能”被复用的功能经过扩展接口暴露出来。当这种高扩展性的诱惑主导了设计师的思惟,随着模块的增多项目的变大、慢慢地设计师就会进入本身所建立的“注释恶梦”中。

 

开放性与耦合度成正比关系

  模块的开放性接口是具备耦合传导效应的,控制模块间的耦合度就能在很大程度上控制了系统的耦合度。模块间的依赖性越小,耦合度越低才更易于变化尽可能将耦合度集中在某一两个模块中(如:Facade 模式),而不是分散在各模块间。耦合度高的模块天然而然地成为“核心”模块,而其实的“外部”模块则须要保持自身的封闭性,这样的设计就不少容易适对未知的变化。

 

由这两个正比关系结合对实现成本的控制上咱们作出两个最为简单可行的推论:

推论1:“正常状况下请保持封闭,没有必要的状况下毫不开放”。

推论2:“集中开放性,让模块间保持陌生”

 

开-闭原则从理论上来谈会有不少内容,但实现起来却很简单, 就以C#为例控制模块开放性的最简单办法就是控制做用域:internal , public。

 

3.从函数/方法设计的角度

我为认为OCP用到极至的状况就是应用于方法级,众所周知:参数越少的方法越好用。开-闭原则能够简单地理解为参数的多寡与返会值的控制

 

在此我更想谈谈“开-闭原则”在C#中的应用。首先在方法设计上,C# 给了设计人员与开发人员一个极大的空间,到了4.5咱们甚至可使用async 方法来简单控异步方法,那么先来总结一下C#的方法参数的种类。

  • 固定参数:public void methodName(string a, out b, ref c);
  • 动态参数:public void methodName(string a, string b=“defautlString”) 
  • 可变参数:public void methodName(params string[] a);
  • 表达式参数(方法注入):public void methodName(Func<string> func, Action act);
  • 泛型参数:public void methodName<T>( T a) where a : class;

在C#中咱们则须要从“注入”这方面来思考和充分发挥语言自身的特性,以达到简化代码,加强易读性的效果。 这里谈的“注入”主要指两个方面,一 是 “代码注入”,二是 “类型注入”。

 

“代码注入”就是向方法传入“代理”类就是在方法内部开辟出某一“可扩展”的部分以执行未知、可变的功能 ,那么咱们就能够对相对“封闭”的方法加强其“开放”性。

经过泛型方法的使用,咱们能够在对类型“开放”的状况下对类型的通用操做相对地“封闭”起来,这样能够在很大程度上利用泛型复合取代类继承,下降类的多态耦合度。

 

里氏替换原则(LSP) 

  凡是基类适用的地方,子类必定适用
 
里氏代换原则 (Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类能够出现的地方,子类必定能够出现。 LSP是继承复用的基石,只有当衍生类能够替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也可以在基类的基础上增长新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,因此里氏代换原则是对实现抽象化的具体步骤的规范。
 
在前文”开-闭原则“关于类设计应用部分已经基本叙述过”替换原则“的用法。 这个原则,我一直是反向理解的,这样就很是容易运用,我是这样使用的:
  • 凡是出现大量子类不适用的成员,子类就应该脱离继承关系
  • 基类中凡是出现大量虚成员,该类就失去成为基类的条件
 
 
 

依赖倒转原则(DIP) 

   要依赖抽象,不要依赖具体。
 
  DIP 就像LSP同样,原文与译文其实都很是坑爹,这里我就不直接引入原文了,由于我但愿每一个读这篇文章的朋友都能理解并应用这些原则而不是在玩文字游戏。DIP 用最为简单的表述就是:“面向接口编程”。子类能够引用父类方法或成员,而父类则绝对不能调用任何的子类方法或成员。一但上层类调的方法调用了子类的方法就会造成依赖环,通常上编译器会“放过”依赖环认为这不属于逻辑错误,但具备依赖环的类结构是没法序列化的(在C#中会直接抛出环状引用的异常)。
 
通俗点:“ 规矩是祖宗定的,子孙只能执行和完善”,用这个口决就能够彻底掌握此原则 。
在过去(10年前)开发工具还比较落后,这是原则十分重要,而现在能够借助VS.net去找到出这种设计错误,也能够直接使用IoC 和 DI 就会天然而充分地尊守此原则 。
  

接口隔离原则 (ISP)  

  使用多个专门的接口比适用单一的接口要好
 
  架构师在逻辑世界就是神,设计软件的过程就是创造逻辑世界,每个接口就是这个世界中的一种规则,类则是实现规则的作法,实例就是执行规则的人。 在实现工做中,咱们会常常遇到这样的现象:一个PM可能同时在跟进好几个项目,或是一个PM要同时充当架构师、PM、程序员甚至售前的角色,这些苦B们是公司内最累的人,同时也是失败率最高的群体,为何? 答案显而易见:人的精力是有限的,专一于某一件事才能真正有成果。同理,在逻辑世界也是同样的,当接口要承载多种的任务,被众多不一样的类所调用时就会出现“接口过载”或者”接口污染“,实现这些接口的类将会产生很高的耦合度,从而代码会变得难以阅读,难以理解,也难以变化。分离接口就是隔离了客户(接口的使用者),隔离客户就天然下降耦合度。
 
  一个完美的世界就应该是专人专项,让擅长的人作其擅长的事,在现实不可能但逻辑世界却能够。那么在设计中如何来把握这种原则呢 ?很简单,当一个接口上的方法被多个类调用时就要警觉了,若是这些方法间没有依赖关系,甚至是不一样类别(在作不一样的事)的方法那么就得考虑使用ISP原则将接口分离成两个独立的接口,使接口的耦合度从1..n 下降至 1..1. 
 
 

合成/聚合 复用原则(CARP)

  要尽可能使用合成/聚合 ,尽可能不要使用继承
 
  复用原则是一个很容易被忽略而又极其重要的原则,这个原则具备很是深远的架构意义。对于小型项目(类库规模小)即便违反此原则也不会带来什么危害,但当构建大规模的类库(数百甚至数千个类)时,这个原则就能够防止出现“继承失控”、过分膨胀、没法重构等的风险,也决定了整个结构的可重用性和可维护性。在定义中它只是一句简单的话,但从“继承”、“合成”与“聚合”就引出了一系列的内容,涵盖多种设计模式和附带多个更小层级的应用原则。
 
(注:关于合成/聚合的好处请去百度吧,关于“白箱复用”与“黑箱复用”都被转烂了)
 
  首先要正确的选择合成/复用和继承,必须透彻地理解里氏替换原则和Coad法则。Coad法则由Peter Coad提出,总结了一些何时使用继承做为复用工具的条件。 
 
Coad法则:

只有当如下Coad条件所有被知足时,才应当使用继承关系:

  1. 子类是超类的一个特殊种类,而不是超类的一个角色。区分“Has-A”和“Is-A”。只有“Is-A”关系才符合继承关系,“Has-A”关系应当用聚合来描述。 
    • “Is-A”表明一个类是另一个类的一种;
    • “Has-A”表明一个类是另一个类的一个角色,而不是另一个类的特殊种类。 
  2. 永远不会出现须要将子类换成另一个类的子类的状况。若是不能确定未来是否会变成另一个子类的话,就不要使用继承。 
  3. 子类具备扩展超类的责任,而不是具备置换掉(override)或注销掉(Nullify)超类的责任。若是一个子类须要大量的置换掉超类的行为,那么这个类就不该该是这个超类的子类。 (注:在C# 中 含有 new 的方法、属性和内部实现单一基类接口就至关于Nullify)
  4. 只有在分类学角度上有意义时,才可使用继承。不要从工具类继承。 
 
对于一个老手Coad法则只是一种总结,很容易理解与运用,但若是你是一个架构新手Coad法则就非常坑爹(个人理解力很低,当年我就被坑了好久)!因此我想另辟蹊径从其它角度来尝试解释这个原则。
 
继承控制
继承是面向对象的一种重要构型,复用原则只告诉咱们“尽可能不使用继承”而不是将继承魔鬼化,在不少场景下,小结构继承是很是常见与易读的。只是,咱们须要了解继承的子代的增长是以整个类结构的复杂度增长n次方在递增,随着子代层级的增多“类家族”结构的变化就愈来愈难。其实,咱们能够找一些自已手上的例子来看看,若是有3代以上继承关系的类,看看最小的子孙类与基类之间是否已经有点“面目全非”?这一点与人类的繁衍与继承是很相似的。再深刻一点就是若是向最顶层的基类进行扩展,是则能彻底适用“替换原则”呢 ?更改高层级结构时是否有“挥舞大刀”般的沉重感 ? 对是否有勇气对稳定的祖代类重构 ?
 
推论:“尽量避免出现三代之外的继承关系,不然应考虑合成/聚合”
 
 
“合成”与“聚合”从字面意义上去理解是我一直以来都没法正确理解的内容。多是我语文水平实在过低的缘故吧,对 Composite 和 Aggregation 两个单词我反而能在维基百科上找到准确的定义。
 

合成  ( Composite )   - 值聚合 (Aggregation by value)

个人通俗定义:合成的过程是在类的构造过程当中(构造函数或外部的构造方法)在运行期将值或其它类实例组装到合成类内(经过变量或属性Hold住)
 
如:
public class Keyboard{}
public class Mouse{}
public class Monitor{}
 
public class Computer
{
    private Keyboard keyboard;
    private Mouse mouse;
    private Monitor monitor;
 
    public Computer() 
    {
         this.keyboard=new Keyboard();
         this.mouse=new Mouse();
         this.monitor=new Monitor();
    }
}

由这个例子可见,所谓的“值(Value)”经过构造函数合成为 “Computer”的内部成员,有如将各个功能单一的部件装配成为一个功能强大的产品。全部的依赖都被“关在”构造函数内,若是将依赖外置就能够运用工厂(Factory Pattern)和合成模式(Composite Pattern)进行演变。

public class Item{};
 
public class Keyboard:Item{}
public class Mouse:Item {}
public class Monitor:Item{}
public ComputerFactory 
{
   public Item Keyboard() { return new Keyboard(); }
   public Item Monitor() { return new Monitor(); }
   public Item Mouse() { return new Mouse(); }
}
 
public class Computer
{
    public List<Item> Items{get;set;}
    
    public Computer(ComputerFactory factory) 
    {
        this.Items.Add(factory.Keyboard());
        this.Items.Add(factory.Mouse());
        this.Items.Add(factory.Monitor()); 
    }
} 
经过简单的演变,就能够将Computer 1-3的耦合变成 1-1 的耦合,全部的依赖都集中到ComputerFactory上,只须要继承ComputerFactory建立更多的工厂传入Computer类就能够生产出各类各样的Computer实例,而无需更改Computer的任何代码,这就是所谓的“黑箱复用”。
 
思考:试试用Builder模式改写上面的例子,会有不一样的效果。
 
这里只是有3个部件,但若是将部件变成30个或者更多时改变的也只是 “合成的构造者” ,应对再复杂的场景:树型合成结构也只是将构造者演变为递归式构造。因而可知到“合成”原则的运行对大量类组合的强大之处。
 
 

聚合  ( Aggregation )  - 引用聚合(Aggregation by reference)

 
聚合在面向对象的实现上是一个极为简单的代码,说白了就是:对象属性。以上面第一个范例说明 (不继承Item基类)
 
public class Computer
{
   public Mouse Mouse{ get;set; }
   public Monitor Monitor{ get; set; }
   public Keyboard Keyboard {get;set;}
}
 
public class Host
{
    public static void Main()
    {
         var computer=new Computer()
         {
              Mouse=new Mouse(),
              Monitor=new Monitor(),
              KeyBoard=new KeyBoard()
         };
    }
}

  聚合类中Hold住的是实例化类的引用,不单是值。聚合类的意义在于将引用依赖集中一处,从某意义上说这个Computer类也是一个Facade 模式。这种方式常见于大规模对象模型的入口类,如Office的 Application 对象,这样的设计能够便于开发者“寻找”类的引用。同时也能够用做 上下文的设计 如:.net中的System.Web.HttpContext。值得注意的是:聚合类是须要慎用的,对于类自己是收敛类引用耦合,同时聚合类也具备耦合传导的特性,由其是构造函数。就拿EF说事吧,咱们用EF访问数据库都须要这样的代码:

public void OrderManager
{
    public List<Order> GetOrder()
    {
        using (var ctx=new DbContext( )
        {
            //
        }
    }
}

当这个new 在代码各处出现时就坏菜了!构造引用的耦合度每调用一次就增长一分,当遍及整个访问层甚至系统时 DBContext就是一个不可变动的超巨型耦合肿瘤!要解决这个问题能够采用单件模式自构造或是用IoC、DI将构造移到一个集中的地方,防止构造耦合散播。 

小结

  若是你是一位.net 体系的开发人员,只要你打开vs.net的代码检查规则你就会发现一个新的世界,一个基于原则/规范 的世界,若是你的代码能80%地经过vs.net中最高级别的代码检查准则,那么事实上你的代码已是很是优质。内中的每一条代码检查准则都值得咱们细细地去品味与学习。
相关文章
相关标签/搜索