首先谈谈模板式设计,我相信模板对于每一位开发人员和设计人员来讲都是很是好的东西,由于它能够“快速”构建出“成熟”的代码、结构或UI。“拿来主义”在业界盛极不衰,对于架构师而言模板也有这种功效,在设计的过程当中咱们会常常遇到不少必须而不重要的“鸡肋”模块,没有它们系统会变得不完整,而它们的存在并不能为系统增长任何的“特点功能”,如:用户管理、角色管理或系统设置等。常见作法是,直接采用第三方模块或是从已有的其它项目中复用相似的模块,你是这样的吗 ?至少我是常常这样作的,由于咱们的中国式项目一般是“验收驱动”,能经过验收、成熟可用就好。若是整个项目都只是由各种模板化的模块所构成,那么这个项目其实不须要架构师,由于不存在任何设计,全部的工做只是一种“融合”(Fusion)。可能这样说会有不少人会吐槽说这是一种“资源整合”能力,从“赶项目”的角度来讲这无可口非,但从技术含量与本质上说确实不存在任何设计成分,这类拼装性或是“复制”性的项目只须要项目经理配备几个高级程序员就能完成了。html
我曾在“表达思惟与驾驭方法论”一文中提到与销售的沟通方法,其中就有一条:“至少说出系统的三个特点”,这个表述对销售具备市场意义之外 , 其实对于架构师是起到一个重要的提醒做用同时也是在创建一种设计原则:程序员
架构设计中模板的拼装是不可避免的,重要的是必须加入属于你的特点设计数据库
很难有人记得住整个软件的设计师,而却很容易记住某项极具特点功能的设计者。“特点” 是架构师在软件中所留下的一种重要的印记,也是在团队中配备架构师的意义所在。设计出彻底可被模板化重用的设计是一功力,而当中小型企业内出现这样的设计之日就是架构师离开企业之时,或许这也是当下中国架构师之殇。保持特点保住饭碗,你懂的。编程
惟一不变的就是变化自己 — Jerry Marktos《人月神话》
不变只是愿望,变化才是永恒 — Swift
变化是表像,不稳定且可定制的;本质是核心,必须稳定,可扩展而不可修改;被固定的变化则可归入核心。
永远不要投资将来,毫不设计没有回报的功能设计模式
不知道你是否拥有相似的经历:安全
衡量标准的尺子掌握在架构师手中,若是设计中出现林林总总的这些“将来功能”您会如何来对待呢 ?是直接砍掉仍是将其包装成为“特点”呢 ?此时架构师不仅仅是须要做为一名技术人员的角度考虑这个功能是否在未来可用,而更多的是须要考虑“成本”。每一个功能甚至每行代码都须要付出“人-月”成本,一旦成本失控,软件就会化身“人狼”吞掉你的项目,而最后也只能后悔没有找到“银弹”。每一个“将来”功能如何不能对现有项目带来即时性的回报,必须砍掉!即便这个功能有如何的美妙、高深或是在未来具备非凡的意义,仍是将它放入“研究室”成为其它项目的技术储备吧。站在商人的立场:每一分钱的成本投入,都须要有足够的利益回报。架构
将来永远是美好的、丰满的同时也是浮云,而现实却每每是充满骨感。在架构或代码中透支将来极少数可得到回报,由于这些“投资”都具备不可预见性只是一些尝试,在产品中除了“市场策略”须要外的这类过度投资就得有陷入“维护将来”的心理觉悟。新的功能、将来的特点更应该收集起来,做为一下版本中可选项,经过详细的市场研究再考虑加入到产品中。固然,对于大型软件企业这个原则基本上是多余的,由于不少成熟的软件企业对需求的控制极其严格与规范。但若是你所在的企业尚未这样的管理意识,或具备超脱性的设计自由,那么这条原则是很是重要的,咱们是用代码换钱的人,更少的代码换更多的钱才是咱们最基本的生存须要。异步
在没有代码的时候就应该重构,重构是写出优雅代码的方法而不单纯是修改代码的理论。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#的方法参数的种类。
在C#中咱们则须要从“注入”这方面来思考和充分发挥语言自身的特性,以达到简化代码,加强易读性的效果。 这里谈的“注入”主要指两个方面,一 是 “代码注入”,二是 “类型注入”。
“代码注入”就是向方法传入“代理”类就是在方法内部开辟出某一“可扩展”的部分以执行未知、可变的功能 ,那么咱们就能够对相对“封闭”的方法加强其“开放”性。
经过泛型方法的使用,咱们能够在对类型“开放”的状况下对类型的通用操做相对地“封闭”起来,这样能够在很大程度上利用泛型复合取代类继承,下降类的多态耦合度。
凡是基类适用的地方,子类必定适用
要依赖抽象,不要依赖具体。
使用多个专门的接口比适用单一的接口要好
要尽可能使用合成/聚合 ,尽可能不要使用继承
只有当如下Coad条件所有被知足时,才应当使用继承关系:
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()); } }
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将构造移到一个集中的地方,防止构造耦合散播。