精华内容
下载资源
问答
  • 设计原则

    千次阅读 2019-11-07 13:51:50
    本文章只是介绍与讲解各种设计原则的基本概念,以便以后复习使用。具体的原则在代码中的使用我打算在以后整理各种设计模式的时候,再讲讲。

    设计原则

    本文章只是介绍与讲解各种设计原则的基本概念,以便以后复习使用。具体的原则在代码中的使用我打算在以后整理各种设计模式的时候,再讲讲。

    可维护性与可复用性

    具有可维护性的设计目标

    一个好的系统设计应该有如下的性质:

    1. 可扩展性:新的性能可以很容易的加入现有的系统中,而又不会对该系统的其它模块造成影响。
    2. 灵活性:代码修改不会波及其他的模块。
    3. 可插入性:可以很容易的将一个类用另一个有同样接口的类代替。

    系统的可复用性

    系统复用的优点:

    • 复用可以提高生产效率
    • 复用可以提高软件质量
    • 复用可以改善系统的可维护性。
    传统复用的方式
    1. 代码的剪贴复用,这种方式在具体实施时,要冒着产生错误的风险。
    2. 算法的复用
    3. 各种数据结构的复用
    面向对象设计的复用

    面向对象的语言中,数据的抽象化、继承、封装和多态性等语言特性使得一个系统可在更高的层次上提供可复用性。

    • 数据的抽象化和继承关系使得概念和定义可以复用
    • 多态性使得实现和应用可以复用
    • 抽象化和封装可以保持和促进系统的可维护性

    复用的焦点,就不再集中在函数和算法等具体实现细节上,而是几种在最重要的含有宏观商业逻辑的抽象层次上。这并不是说具体实现细节的复用不再重要。

    可维护性复用

    在面向对象的设计中,可维护性复用是以设计原则和设计模式为基础的。

    • 开闭原则、里氏替换原则和组合/聚合复用原则 可以在提高系统可复用性的同时,提高系统的可扩展性。
    • 开闭原则、迪米特法则、接口隔离原则 可以在提高系统可复用性的同时,提高系统的灵活性。
    • 开闭原则、里氏替换原则、组合/聚合原则、依赖倒置原则 可以在提高系统可复用性的同时, 提高系统的可插入性。

    设计原则

    设计原则.png

    注意,通常所说的SOLID(上方表格缩写的首字母,从上到下)设计原则没有包含本篇介绍的迪米特法则,而只有其他五项。

    开闭原则

    软件,如类、模块和函数方法等,应该对扩展开放,对修改关闭

    其它设计原则都是开闭原则的手段和工具,是附属于开闭原则的

    解读:

    • 用抽象构建框架,用实现扩展细节。不以改动原有类的方式来实现新需求,而是应该以实现事先抽象出来的接口(或具体类继承抽象类)的方式来实现。
    • 这个抽象层预见了所有可能的扩展,在任何情况下都不改变。
    • 应考虑设计中什么可能会发生变化。(注:考虑的不是什么会导致设计改变,而是允许什么发生变化而不让这一变化导致重新设计)

    优点:开闭原则的优点在于可以在不改动原有代码的前提下给程序扩展功能。增加了程序的可扩展性,同时也降低了程序的维护成本。

    单一职责原则

    就是说功能要单一,一个对象应该只包含单一的职责,并且该职责被完整的封装在一个类中。或者说,就一个类而言,应该只有一个引起它变化的原因

    • 一个类(或者模块、方法)承担的职责越多,它被复用的可能性越小。
    • 单一职责原则是实现高内聚、低耦合的指导方针。

    类的职责主要包括俩个方面:

    • 数据职责,通过属性来体现
    • 行为职责,通过方法来体现

    解读:
    类职责的变化往往就是导致类变化的原因。也就是说如果一个类具有多种职责,就会有多种导致这个类变化的原因,从而导致这个类的维护变得困难。往往在软件开发中,随着需求的不断增加,可能会给原来的类添加一些本来不属于它的一些职责,从而违反了单一职责原则。如果我们发现当前类的职责不仅仅有一个,就应该将本来不属于该类真正的职责分离出去。不仅仅是类,函数也要遵循单一职责原则,即一个函数制作一件事情。如果发现一个函数里面有不同的任务,则需要将不同的任务以另一个函数的形式分离出去。

    优点:如果类与方法的职责划分的很清晰,不但可以提高代码的可读性,更实际性地更降低了程序出错的风险,因为清晰的代码会让bug无处藏身,也有利于bug的追踪,也就是降低了程序的维护成本。

    里氏替换原则

    就是说在使用基类的的地方可以任意使用其子类,能保证子类完美替换基类。

    • 里氏替换原则是继承复用的基础
    • 反过来的替换则不成立,即如果一个软件使用的是一个子类,那么它不一定适用于父类。

    解读:

    • 只要父类能出现的地方子类就能出现。反之,父类则未必能胜任。
    • 在继承体系中,子类中可以增加自己特有的方法,也可以实现父类的抽象方法,但是不能重写父类的非抽象方法,否则该继承关系就不是一个正确的继承关系。

    优点:增强程序的健壮性,即使增加了子类,原有的子类还可以继续运行。

    如果违反了里氏替换原则怎么办?

    如果有俩个具有继承关系的类A和B违反了里氏替换原则,就要取消继承关系,可采用以下方案:

    1. 创建一个新的抽象类C,作为俩个具体类的父类,讲A和B的共同行为移动到C中,从而解决A和B行为不完全一致的问题
    2. 将A和B的继承关系改写为组合/聚合关系

    依赖倒置原则

    依赖倒置原则是面向对象设计的核心原则
    抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。

    • 高层模块不应该依赖底层模块,二者都应该依赖其抽象
    • 抽象不应该依赖细节,细节应该依赖抽象

    解读:
    高层模块就是调用端,低层模块就是具体实现类。抽象就是指接口或抽象类。细节就是实现类。
    要达到上述要求,一个具体类应当只实现抽象类或Java接口中声明过的方法,而不应该给出多余的方法
    通俗点来说就是依赖倒置原则的本质就是通过抽象(接口或者抽象类)使得各类或者模块的实现彼此独立,互不影响,实现类之间不发生直接的依赖关系,实现模块间的松耦合。

    优点:可减少类之间的耦合性,提高系统的可维护性,减少并行开发引起的风险,提高代码的可读性。

    耦合关系

    在面向对象的系统里,俩个类之间有零耦合、具体耦合和抽象耦合三种类型的耦合关系。

    • 零耦合:指俩个类之间没有耦合关系
    • 具体耦合:指在俩个具体类之间的耦合,一个类对另一个具体类的直接引用
    • 抽象耦合:指一个具体类和一个抽象类/Java接口之间的耦合,有最大的灵活性

    依赖倒置原则要求客户端依赖于抽象耦合

    接口隔离原则

    准确而恰当地划分角色以及角色所对应的接口,是面向对象设计的一个重要的组成部分。

    在这里有俩种解释:

    1. 客户端不应该依赖哪些它不需要的接口。
    2. 另一种就是,一旦一个接口太大、太笨重,则需要将它分割成一些更细小的接口,使用该接口的客户端仅需要知道与之相关的方法即可。

    接口隔离原则是指使用多个专门的接口比使用单一的总接口要好。接口仅仅提供客户端需要的行为,即所需的方法,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口。
    从客户端的角度看,接口隔离原则是指一个类对另一个的依赖性应当建立在最小的接口上。

    解读:
    使用接口隔离原则拆分接口时,首先必须满足单一职责原则,将一组相关的操作定义在一个接口中,且在满足高内聚的前提下,接口中的方法越少越好。
    总之,应当将多个不同的角色交给不同的接口,而不应当都交给同一个接口。
    不要将看上去差不多的甚至是没有关系的接口合并,这样会形成国与臃肿的大接口,称为接口的污染。

    优点:避免同一个接口里面包含不同类职责的方法,接口责任划分更加明确,符合高内聚低耦合的思想。

    迪米特法则

    核心观念:类间解耦
    迪米特法则又称为最少知识原则,是指一个对象应当对其他对象有尽可能少的了解。

    迪米特法则可以表述为只与你直接的朋友通信;不要跟“陌生人”说话
    在迪米特法则中,对于一个对象,其朋友包括以下几类:

    1. 当前对象本身(this)
    2. 以参数形式传入到当前对象方法中的对象
    3. 当前对象的成员对象
    4. 如果当前对象的成员成员对象是一个集合,那么集合中的元素也都是朋友
    5. 当前对象所创建的对象

    任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”

    简单的说,迪米特法则就是指一个软件实体应当尽可能少地与其他实体发生相互作用。这样的话,当一个模块修改时,就会尽量少地影响其他模块,扩展会相对容易,这是对软件实体之间通信的限制,它要求限制软件实体之间通信的宽度和深度。

    迪米特法则可分为侠义法则和广义法则:

    侠义迪米特法则

    指如果俩个类不是必须要彼此直接通信,那么这俩个类就不应当发生直接的相互作用。
    如果其中的一个类需要调用另一个类(陌生人)的某一个方法,可通过第三者(朋友)转发这个调用。
    但第三者需要额外增加方法。
    优:可降低类之间的耦合
    劣:会在系统中造出大量的小方法,散落在系统的各个角落

    广义迪米特法则

    指对象之间的信息流量、流向以及信息的影响的控制,主要是对信息隐藏的控制。一个系统的规模越大,信息的隐藏就越重要。
    一个设计得好的模块应该就自己的内部数据和与实现有关的细节隐藏起来,并提供给外界的API和自己的实现分隔开。
    这样,模块与模块之间只通过彼此的API相互通信,而不理会模块内部的工作细节。这就是面向对象的封装特性。

    注意

    将迪米特法则运用到系统设计,特别是类的设计时,要注意以下几点:

    1. 在类的划分上,应当使创建的类之间的耦合为弱耦合,有利于复用。一个弱耦合中的类被修改不会对有关系的类造成影响。
    2. 在类的设计上,应尽量将一个类设计成不可变类。
    3. 在类的设计上,应尽量降低一个类的访问权限。
    4. 在类的设计上,应尽量降低成员的访问权限。

    组合/聚合复用原则

    要尽量使用组合/聚合,而相应的减少继承的使用
    首先介绍以下组合与聚合的概念

    • 组合:是一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样。
    • 聚合:表示一种弱的拥有关系或者整体与部分的关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分。

    组合/聚合复用原则会使类的继承层次保持较小的规模,避免成为不可控制的庞然大物。

    组合/聚合复用 与 继承复用
    继承复用

    里氏替换原则是继承复用的基础。

    • 优点:父类的大部分功能可以通过继承关系自动进入子类,所以新的实现比较容易,修改或扩展继承而来的实现也比较容易
    • 缺点:
    1. 从父类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性
    2. 典型的白箱复用,父类的内部细节对子类是透明的,继承将父类的实现细节暴露给子类,继承破坏包装
    3. 子类与父类之间有紧密的依赖关系
    组合/聚合复用

    组合或者聚合是将已有的对象纳入新对象中,使之成为新对象的一部分,因此新对象可以调用已有对象的功能。

    • 优点:
    1. 耦合度相对较低
    2. 可以在运行时动态进行
    3. 黑箱复用,已有对象的内部细节对新对象不可见
    4. 作为复用的手段,几乎可以应用到任何环境中
    • 缺点:使用组合/聚合复用的话,需要管理较多的对象
    Has-A 与 Is-A

    Has-A :表示某一个角色具有某一项责任,代表一个类是另一个类的一个角色,而不是另一个类的特殊种类。组合/聚合复用就是Has-A。
    Is-A :是严格分类学意义上的定义,意思是一个类是另一个类的一种。继承复用是Is-A关系。

    根据里氏替换原则,如果俩个类的关系是Has-A关系而不是Is-A关系,那么这俩个类一定违反里氏替换原则。
    只有俩个类满足里氏替换原则,才能使Is-A关系。

    参考
    • 《软件体系结构与设计》
    • https://www.jianshu.com/p/807bc228dbc2
    展开全文
  • 设计原则—KISS

    万次阅读 2020-12-04 08:56:56
    KISS 原则算是一个万金油类型的设计原则,可以应用在很多场景中。它不仅经常用来指导软件开发,还经常用来指导更加广泛的系统设计、产品设计等,比如,冰箱、建筑、iPhone 手机的设计等等 Keep It Simple and ...

    KISS

    英文翻译

    • Keep It Simple and Stupid
    • Keep It Short and Simple
    • Keep It Simple and Straightforward.

    意义

    • KISS 原则算是一个万金油类型的设计原则,可以应用在很多场景中。它不仅经常用来指导软件开发,还经常用来指导更加广泛的系统设计、产品设计等,比如,冰箱、建筑、iPhone 手机的设计等等
    • 代码的可读性和可维护性是衡量代码质量非常重要的两个标准。而 KISS 原则就是保持代码可读和可维护的重要手段。代码足够简单,也就意味着很容易读懂,bug 比较难隐藏。即便出现 bug,修复起来也比较简单
    • KISS 原则是保持代码可读和可维护的重要手段。KISS 原则中的“简单”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,我们还要考虑逻辑复杂度、实现难度、代码的可读性等。

    落地

    1. 不要使用同事可能不懂的技术来实现代码。比如前面例子中的正则表达式,还有一些编程语言中过于高级的语法等。
    2. 不要重复造轮子,要善于使用已经有的工具类库。经验证明,自己去实现这些类库,出 bug 的概率会更高,维护的成本也比较高。
    3. 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性
    展开全文
  • 架构设计(2)-架构设计原则

    万次阅读 2017-10-17 14:19:01
    如何设计出一个好的架构,不像数据公式或者定律,很难一概而就...一些好的架构设计原则可以确保设计决策在一定程度上能够满足需求。 一、形成架构原则的过程 形成架构原则的过程: 架构原则要SMART ...

     

    架构设计学习思维导图: 架构设计系列主要的ADM(架构开发方法)主要基于TOGAF9或者TOGAF9.1来论述。这是个人学习实践和总结笔记,专注并不断积累和更新,努力精进自己。个人拙见,仅供参考。
    1、 架构概述:了解架构基础知识:架构定义、分类、级别、应用架构演进、架构是否合理、架构误区等。
         《谈谈架构》
    2、 原则模式:了解架构模式和设计原则。
         《架构设计原则》
          《架构模式》
    3、 瀑布模式:根据瀑布开发模式,从前期的架构愿景->到架构需求分析->架构设计
          《架构愿景分析》
          《架构需求分析》
          《如何设计一个架构》
    4、 架构三高:架构战术设计主要关注点:高可用、高性能、高效服务治理或者高并发
         《高可用架构设计》
         《高性能设计》
         《分布式服务治理》
    5、 具体知识点:架构实施知识点,框架、组件、限流、容错等知识。
         《分布式链路跟踪》
         《分布式链路跟踪:Zipkin实践》
         《分布式链路跟踪:skywalking实践》
         《我们自研log2跟踪组件》

     

    前言


    原则定义:行事所作为依据的准则、规范、标准,是指经过长期经验总结所得出的合理化的现象。

    生活中的原则

    我们在生活中,说这人原则性很强,即他做人做事严格按照自己制定的准则规范。原则性很强的人在生活和工作中遇到需要抉择的情境时,根据自己原则毫不犹豫地作出选择。而没有原则的人往往是犹豫不决,优柔寡断。

    架构的原则

         我们在《架构设计(1)-谈谈架构》的架构定义也提到架构的第一条内容:
         系统性思考的合理决策:比如技术选型、解决方案、成本评估、性价比评估等等。那如何评估决策的合理性呢?如何评估架构设计的合理性?比如老板说N+1容灾部署成本太高,架构设计不合理. 那怎么怼老板呢?

    架构原则源于业务目标

      

    架构设计不像数据公式或者定律,很难一概而就。很多时候是设计者(架构师)的各种设想,各种权衡折中而符合系统需求的智慧输出。一些好的架构设计原则可以确保设计决策在一定程度上能够满足需求。

              

     

    一、形成架构原则的过程


    架构原则不是某个架构师拍脑袋决定,可能是架构师提出最初文档,然后经过了团队成员内部反复讨论和共同认可后才得以确定和精炼的。形成架构原则的过程:

      架构原则要SMART 

    具体SMART原则有不同的解释:
    SMART原则1(S=Specific具体、M=Measurable度量、A=Attainable实现、R=Relevant相关、T=Time-bound时限)
    SMART原则2(S=Specific具体、M=Measurable度量、A=Attainable实现、R=Realistic实际、T=Testable 可验证)

     

    二、架构原则分级


    分类和分级说明原则,目的是明确原则的适用范围。

    1、从架构分类说明

    下面部分图来自《京东架构介绍》ppt。

    1)、系统级架构原则:


    2)、业务架构原则

    3)、应用架构原则

    4)、数据架构原则

    5)、代码架构原则

          应用程序开发中,一般要求尽量两做到可维护性和可复用性。我个人理解是常用的7个面向对象设计原则。这些原则并不是孤立存在的,它们相互依赖,相互补充。具体参考《设计模式原则详解》和《设计模式概论》提到相关原则。

     

    6)、技术架构原则

     

     

    2、从服务分级来说明:

    明确每个服务级别的原则要求。《架构设计(8)—高可用架构设计》服务分级治理也提到相关原则

    类别 服务 原则 描述
    一级核心服务 核心产品或者服务 冗余N+1部署、可监控、可回滚等原则 系统引擎部分:一旦出现故障,整个系统瘫痪
    二级重要服务 重要的产品功能 可监控、可回滚等原则 类比汽车轮子:该服务出现问题,该重要功能不可用。
    三级一般服务 一般功能 可以单点部署等原则 类比汽车倒车影像:该部分出现问题,稍微影响用户体验
    四级工具服务 工具类是服务 混合部署、不监控等原则 非业务功能:比如爬虫、管理后台、运维工具

    在此就可以回答老板“N+1容灾部署成本太高,架构设计不合理”的问题。核心服务必须N+1部署,保证系统的高可用。

     

    三、15条普适架构原则


    我们掌握前人总结的经验,让我们站在巨人的肩膀上高山远瞩。《架构真经》这本书简单阐述了架构设计的一些常用的原则。罗列一些常用的原则,下面是15个具有普适价值架构原则 :

    1、N+1设计 :开发的系统在发生故障时,至少有一个冗余的实例
    2、回滚设计 :确保系统可以向后兼容。
    3、禁用设计:可以关闭任何发布功能
    4、监控设计 :在设计阶段就要考虑监控,而不是在部署完成后。
    5、多活数据中心设计
    6、采用成熟的技术
    7、故障隔离 :
    8、水平扩展
    9、非核心则购买
    10、使用商品化硬件
    11、快速迭代
    12、异步设计
    13、无状态设计
    14、前瞻性设计
    15、自动化

    1、N+1设计 :开发的系统在发生故障时,至少有一个冗余的实例

       广泛地应用在从数据中心设计到应用服务的部署:

    • 在发生故障时,系统至少要有一个冗余的实例。
    • 必须确保一个为自己,一个为客户、 一个为失败


    2、回滚设计 :确保系统可以向后兼容。

    • 如果很久才能修复服务,那么就要在一定的时间范围内完成回滚。
    • 灾难性的事故,例如损坏客户数据,往往在部署后好几天才出现。
    • 系统最好按照预先的设计,通过发布或回滚解决问题。

        通过版本化方式实现回滚设计,一旦发生灾难级别的故障可以通过回滚到最近版本来恢复服务。

    3、禁用设计(功能开关、降级开关):可以关闭任何发布功能

         当设计系统,特别是与其他系统或服务通讯的高风险系统时,要确保这些系统能够通过开关来禁用。这将为修复服务提供额外的时间,同时确保系统不因为错误引起诡异需求而宕机。

     

         降级开关通过配置中心集中化管理,例如(apollo配置中心)通过推送机制把开关推送到各个应用服务。

     

    4、监控设计 :在设计阶段就要考虑监控,而不是在部署完成后。

       通过监控发现系统的可用性问题。

    •  通过监控使系统自我诊断、自我修复成为可能。
    •  通过监控确定系统可预留空间的使用情况。
    •  通过监控掌握系统之间的交互关系,发现瓶颈 

        如果监控做的好,不仅能发现服务的死活,检查日志文件,还能收集系统相关的数据,评估终端用户的响应时间。如果系统和应用在设计和构建时就考虑好监控,那么即使不能自我修复,也至少可以自我诊断。

    5、多活数据中心设计

    • 数据是否全部集中在一个数据中心?
    • 读写是否分离?
    • 是否所有的客户信息都共享同一个数据结构?
    • 服务调用是否允许延时的存在


    6、采用成熟的技术

    •  工程师倾向于学习和实施性感时髦的新技术。因为新技术可以降低成本、减少产品上市时间、提高性能。不幸的是,新技术也往往有较高的故障率。如果把新技术应用在架构的关键部分,可能会对可用性产生显著的影响。
    •  最好争取在多数人采用该技术的时候进入,先把新技术用在对可用性要求不高的功能上,一旦证明它可以可靠地处理日常的交易,再将此技术移植到关键任务领域中去。

    7、故障隔离 :

         1、避免单一业务占用全部资源。避免业务之间的相互影响  2. 机房隔离避免单点故障。
         不共享原则:理想情况是负载均衡、网络前端、应用服务器、数据库,绝不共享任何服务、硬件和软件。
         不跨区原则: 不同隔离区之间无通讯,所有服务调用必须发生在同一个故障隔离区。

    8、水平扩展

        什么是水平可扩展?平台的水平扩展是指随着业务的发展,当需要扩大平台的服务能力时,不必重构软件系统,通过增加新的设备来满足业务增长的需要。 


     X轴扩展:服务器拆分。平台的服务能力可以在不改变服务的情况下,通过添加硬件设备来完成扩容。

     Y轴扩展:数据库拆分。平台的服务能力通过不断地分解和部署服务来完成扩容。

     Z轴扩展:功能拆分。平台的服务能力可以按照客户不断分解和部署来机器 完成容量的扩展。(比如按用户uid来分表分库等)

     

    9、非核心则购买 

    工程师往往有自己研发所有系统的冲动,如果使用云服务器,建议直接使用云服务相关产品,比如日志系统,可以直接使用日志服务。

    •   系统研发要投入资源,系统维护更要长期投入。
    •   影响核心产品到市场的速度。
    •   如果可以形成差异化的竞争优势,那么自己做,否则外购。

    10、使用商品化硬件 

    •      在大多数情况下,便宜的是最好的。
    •      标准、低成本、可互换、易于商品化是商品化硬件的特征。

          如果架构设计得好,就可以通过购买最便宜的服务器轻松地实现水平扩展,前提是所有商品化硬件的总成本要低过高端硬件的总成本。

    11、快速迭代

    • 小构建:小构建的成本较低,可以确保投资可以产生价值。
    • 小发布:发布的失败率与变更数量相关,小发布失败率较低。
    • 快试错:可依市场反馈,快速迭代,加快TTM,优化用户体验。

    快速迭代需要完善的运维工具,比如从cmdb,持续集成工具,监控等等。

    12、异步设计

    同步系统中个别子系统出现故障会对整个系统带来影响。

    • 同步系统中性能最慢的子系统成为整个系统性能的瓶颈。
    •  同步系统中扩展性最差的子系统是整个系统扩展的瓶颈。 
       

    13、无状态设计

    无状态定义:是应用服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的。比如单实例的mysql,zookeeper集群是有状态,而类似单纯tomcat服务是无状态的。

    无状态的系统更利于扩展,更利于做负载均衡。状态是系统的吞吐量、易用性、可用性、性能和可扩展性的大敌,要尽最大可能避免。

    14、前瞻性设计

    • Now :目前正使用系统的架构、设计、能力、性能和扩展性。
    • Now+1: 下一代预研系统的架构、设计、能力、性能和扩展性。
    • Now+2: 下一代规划系统的架构、设计、能力、性能和扩展性


    15、自动化

    设计和构建自动化的过程。如果机器可以做,就不要依赖于人. 人常犯错误,更令人沮丧的是,他们往往会以不同的方式多次犯同样的错误。

    四、应用服务拆分原则


    应用拆分首先明确拆分目的和需求,然后制定拆分原则。

    4.1、拆分的目的

    1. 人员的角度:  多人维护一个工程,从开发开发、测试、部署、上线,效率是极低的。定位问题和修复问题非常困难。
    2. 业务的角度:代码已经严重影响到业务的效率,每个业务有各自的需求,需要给自己应用部署,各自开发需求。
    3. 从架构的角度:应用已经无法满足非功能性需求:无法满足并发需求、安全性、扩展维护很麻烦。需要梳理和抽取核心应用、公共应用,作为独立的服务下沉到核心和公共能力层,逐渐形成稳定的服务中心

         总之,系统拆分是单体程序向分布式系统演变的关键一步,也是很重要的一步,拆分的好坏直接关系到未来系统的扩展性、可维护性和可伸缩性等,拆分工作不难理解,但是如何正确拆分、有什么样的方法和原则能帮助我们拆分得到一个我们理想中的系统:高可用、可扩展、可维护、可伸缩的分布式系统。

    4.2、拆分需求

    1. 组织结构变化:从最初的一个团队逐渐成长并拆分为几个团队,团队按照业务线不同进行划分,为了减少各个业务系统和代码间的关联和耦合,几个团队不再可能共同向一个代码库中提交代码,必须对原有系统进行拆分,以减少团队间的干扰。
    2. 安全需求:这里所指的安全不是系统级别的安全,而是指代码或成果的安全,尤其是对于很多具有核心算法的系统,为了代码不被泄露,需要对相关系统进行模块化拆分,隔离核心功能,保护知识产权。
    3. 替换性:有些产品为了提供差异化的服务,需要产品具有可定制功能,根据用户的选择自由组合为一个完整的系统,比如一些模块,免费用户使用的功能与收费用户使用的功能肯定是不一样的,这就需要这些模块具有替换性,判断是免费用户还是收费用户使用不同的模块组装,这也需要对系统进行模块化拆分。
    4. 交付速度:单体程序最大的问题在于系统错综复杂,牵一发而动全身,也许一个小的改动就造成很多功能没办法正常工作,极大的降低了软件的交付速度,因为每次改动都需要大量的回归测试确保每个模块都能正确工作,因为我们不清楚改动会影响到什么,所以需要做大量重复工作,增加了测试成本。这时候就需要对系统进行拆分,理清各个功能间的关系并解耦。
    5. 技术需求:
    6. 1)、 扩展困难:单体程序由于技术栈固定,尤其的是比较庞大的系统,不能很方便的进行技术升级,或者说对引入新技术或框架等处于封闭状态;每种语言都有自己的特点,单体程序没有办法享受到其它语言带来的便利;对应到团队中,团队技术相对比较单一。
    7.  2)、 减少重复造轮子:相比于基于业务的垂直拆分,基于技术的横向拆分也很重要,使用数据访问层可以很好的隐藏对数据库的直接访问、减少数据库连接数、增加数据使用效率等;横向拆分可以极大的提高各个层级模块的重用性,减少重复造轮子。
    8. 6)、业务需求:由于业务上的某些特殊要求,比如对某个功能或模块的高可用性、高性能、可伸缩性等的要求,虽然也可以将单体整体部署到分布式环境中实现高可用、高性能等,但是从系统维护的角度来考虑,每次改动都要重新部署所有节点,显然会增加很多潜在的风险和不确定定性因素,所以有时候不得不选择将那些有特殊要求的功能从系统中抽取出来,独立部署和扩展。

     

    4.3.拆分原则

    4.3.1、业务原则

    1. 单一职责:1)、满足单一职责原则对于一个微服务而言,有限定的业务边界,可以帮助我们满足服务开发和交付的敏捷性;即每一个组件或者是模块应该只有一个职责或者是功能,功能要内聚。2)、高内聚、低耦合:拆分主要的参考因素就是最小化交互,高内聚、低耦合。错误的拆分边界,可能会导致功能之间的高耦合性和复杂性。3)、最小知识原则:一个组件或者是模块不应该知道其他组件或者模块的内部实现细节。
    2. 服务粒度适中:以业务模型拆分、有适当的边界。 1)、粗粒度优行原则:由服务提供方提供粗粒度的业务服务,封装数据及数据处理逻辑,屏蔽数据及业务规则,降低耦合度,提供更多业务价值;2)、适当边界:关注微服务的功能范围,一个服务的大小应该等于满足某个特定业务能力所需要的大小;
    3. 业务分层原则: 从整体规划上把业务分层,形成单向依赖,避免微服务之间的网状依赖关系;
    4. 可重用性拆分原则:将通用部分和专用部分分解为不同的应用。1) 若粗粒度服务不能满足重用需求,则拆分粗粒度服务,以增加重用;2)非唯一依赖:至少被2个以上其它微服务依赖的功能模块,才有必要独立成一个微服务。
    5. 稳定性原则:将稳定部分和易变部分分离。将动态部分和静态部分分解为不同的元素;将机制和策略分离为不同的元素;将应用和服务分离。

     

    4.3.2、技术原则

    1. 低耦合:可独立部署
    2. 轻量级的通信机制
    3. 性能要求拆分原则:若粗粒度服务性能达不到性能需求,则适当拆分服务,以满足性能需求;
    4. 安全性拆分原则:若粗粒度服务所包含的所有处理不在同一个安全级别上,为满足安全性需求拆分服务形成细粒度服务;

     

    4.3.3 其他治理原则

    1. 演进式拆分
    2. 考虑团队人员结构
    3. 避免环形依赖和双向依赖

    对于微服务组件拆分粒度应该是尽可能的拆小,但也不应该过分追求细粒度,要考虑适中不能过大或过小。按照单一职责原则和康威定律,在业务域、团队还有技术上平衡粒度。拆分后的代码应该是易控制,易维护的,业务职责也是明确单一的。

     

    五、技术选型: 指导原则


    架构设计并没有像编程语言那样的语法约束,更多的时候是面多多种可能时的“选择”,例如:

    • 选先进的技术还是团队熟悉的技术?先进的出问题怎么办?熟悉的后续技术演化困难怎么办?
    • 用Angular还是React,一个很强大一个更灵活
    • MySQL还是MongoDB?
    • 淘宝的电商架构咳哟简单的照搬么?
    • 等等

    但存在共性原则:合适原则、简单原则、演化原则

    1、合适原则:合适优于业界领先

    优秀人才的技术情节导致各种以先进技术主导的创业失败,原因有:

    1. 将军难打无兵之仗(人数)
    2. 罗马不是一天建成的(积累)
    3. 冰山下面才是关键(业务)

    所以真正的优秀架构都是在企业当前人力、条件、业务等各种约束下设计出来的。BAT的架构师到小公司没有了大公司的资源、平台、积累和业务,只照搬大公司的做法和技术即会失败!

    我们架构设计核心目标是解决问题,提高效率。所以合适于业务场景需求的架构设计才是首要选择,而不是以追求实施性感时髦的新技术。

    2、简单原则:简单优于复杂。

    软件架构设计目的就是为了解决系统的复杂度。系统组成结构和相关之间关系越复杂,出问题的可能性就越大。扩展的难度也就越大,很有可能牵一发而动全身。

    软件领域复杂度体现两个方面:

    1)、结构的复杂性

    • 组成复杂系统的组件数量更多
    • 同时这些组件之间的关系也更加复杂
    • 组件增多整体出现鼓掌的概率增加,可用性下降
    • 某个组件改动会影响关联的所有组件
    • 定位复杂系统的问题比简单系统更加困难

    2)、逻辑的复杂性

    • 单组件承担功能过多,导致逻辑复杂度升高
    • 后续的功能修改会影响很大
    • 使用了复杂的算法难以实现修改和问题解决

    简单变复杂的事情可能人人都会,但化繁为简会很难。正所谓大道至简,如果简单和复杂的都能满足需求,最好选择简单的方案!

    3、演化原则:演化优于一步到位. 架构设计没有完美银弹. 勿过度设计.  

    业务需求是不断变化的,所以架构设计需要不断随着业务变化而变化的,这样才能去适应业务需求。软件架构同建筑架构相似,但建筑不可变,软件可变, 例如:Windows的演化、Android的发展。

    软件架构类似于大自然“设计”的一个生物,通过演化适应环境,逐步变得强大。

    1. 首先满足当前需要,解决当前最核心问题. 
    2. 预测并并发未来可能存在的问题,  不断迭代保留,不断完善, 
    3. 业务变化时,架构扩展、重构、甚至重写。

    不要贪大求全,分析清楚自身业务特点,快速落地,不断完善演化。当然如果一开始系统就有很好的基础设计, 未来可能更容易到达满意的目标.  

     

    所有问题的前提要搞清楚我们今天面临的业务量有多大,增长走势是什么样,而且解决高并发的过程,一定是一个循序渐进逐步的过程。例如在初创公司的野蛮生长阶段,业务场景和需求边界很难把握,有时候根本不需要架构师,产品需要快速迭代和变现,需求频繁更新,这个时候需要的是快速实现。这时候考虑如何做好架构设计, 如何微服务化, 可能会影响业务的发展. 

    网上的一张图很经典,总结的非常好:

     

    整个系统进化分为三个阶段:

    x轴,水平扩展阶段,通过负载均衡服务器不断的横向扩充应用服务器,水平扩展最重要的问题是需要注意不用服务器之间的如何保持session和会话同步,不能让用户在不通服务器之间切换时有感知应用扩展后自然遇到的问题就是DB的瓶颈:连接数,iops等。

    z轴,就是对数据库的拆分,难度上了一个台阶,Sharding的基本思想就要把一个数据库如何进行切分,可以分为水平切分和垂直切分,水平切分相对简单,一主多从,多主都可以,根据业务的需要,多主切分设计时需要注意主键的关系,解决多写在进行数据同步时候的冲突问题,垂直拆分更加复杂,一般都会涉及到架构逻辑的改造,需要引入中间件,来进行数据源的管理,垂直拆分时把关系紧密(比如同一模块)的表切分出来放在一个库上,或者通过hash进行拆分,从而将原有数据库切分成类似矩阵一样可以无限扩充的队列。

    y轴扩展,最后就是功能分解了,也就是我们讲的微服务切分。微服务拆分将巨型应用按照功能模块分解为一组组不同的服务,淘宝的系统当年也经历了这样的过程,通过五彩石项目从单一的war包拆分成了今天的大家看到买家,卖家中心,交易等系统。

     

    六、优秀程序设计的18大原则


    良好的编程原则与良好的设计工程原则密切相关。本文总结的这些设计原则,帮助开发者更有效率的编写代码,并帮助成为一名优秀的程序员。作者Diggins是加拿大一位有25年编程经验的资深技术人员,曾效力于Microsoft和Autodesk,并创办过两家赢利的互联网公司。

    1.避免重复原则(DRY - Don’t repeat yourself)

    编程的最基本原则是避免重复。在程序代码中总会有很多结构体,如循环、函数、类等等。一旦你重复某个语句或概念,就会很容易形成一个抽象体。

    2.抽象原则(Abstraction Principle )

    与DRY原则相关。要记住,程序代码中每一个重要的功能,只能出现在源代码的一个位置。

    3.简单原则(Keep It Simple and Stupid )

    简单是软件设计的目标,简单的代码占用时间少,漏洞少,并且易于修改。

    4.避免创建你不要的代码 Avoid Creating a YAGNI (You aren’t going to need it)

    除非你需要它,否则别创建新功能。

    5.尽可能做可运行的最简单的事(Do the simplest thing that could possibly work)

    尽可能做可运行的最简单的事。在编程中,一定要保持简单原则。作为一名程序员不断的反思“如何在工作中做到简化呢?”这将有助于在设计中保持简单的路径。

    6.别让我思考(Don’t make me think )

    这是Steve Krug一本书的标题,同时也和编程有关。所编写的代码一定要易于读易于理解,这样别人才会欣赏,也能够给你提出合理化的建议。相反,若是繁杂难解的程序,其他人总是会避而远之的。

    7.开闭原则(Open/Closed Principle)

    你所编写的软件实体(类、模块、函数等)最好是开源的,这样别人可以拓展开发。不过,对于你的代码,得限定别人不得修改。换句话说,别人可以基于你的代码进行拓展编写,但却不能修改你的代码。

    8.代码维护(Write Code for the Maintainer)

    一个优秀的代码,应当使本人或是他人在将来都能够对它继续编写或维护。代码维护时,或许本人会比较容易,但对他人却比较麻烦。因此你写的代码要尽可能保证他人能够容易维护。用书中原话说“如果一个维护者不再继续维护你的代码,很可能他就有想杀了你的冲动。”

    9.最小惊讶原则(Principle of least astonishment)

    最小惊讶原则通常是在用户界面方面引用,但同样适用于编写的代码。代码应该尽可能减少让读者惊喜。也就是说,你编写的代码只需按照项目的要求来编写。其他华丽的功能就不必了,以免弄巧成拙。

    10.单一责任原则(Single Responsibility Principle)

    某个代码的功能,应该保证只有单一的明确的执行任务。

    11.低耦合原则(Minimize Coupling)

    代码的任何一个部分应该减少对其他区域代码的依赖关系。尽量不要使用共享参数。低耦合往往是完美结构系统和优秀设计的标志。

    12.最大限度凝聚原则(Maximize Cohesion)

    相似的功能代码应尽量放在一个部分。

    13.隐藏实现细节(Hide Implementation Details)

    隐藏实现细节原则,当其他功能部分发生变化时,能够尽可能降低对其他组件的影响。

    14.迪米特法则又叫作最少知识原则(Law of Demeter)

    该代码只和与其有直接关系的部分连接。(比如:该部分继承的类,包含的对象,参数传递的对象等)。

    15.避免过早优化(Avoid Premature Optimization)

    除非你的代码运行的比你想像中的要慢,否则别去优化。假如你真的想优化,就必须先想好如何用数据证明,它的速度变快了。

    “过早的优化是一切罪恶的根源”——Donald Knuth

    16.代码重用原则(Code Reuse is Good)

    重用代码能提高代码的可读性,缩短开发时间。

    17.关注点分离(Separation of Concerns)

    不同领域的功能,应该由不同的代码和最小重迭的模块组成。

    18.拥抱改变(Embrace Change)

    这是Kent Beck一本书的标题,同时也被认为是极限编程和敏捷方法的宗旨。

    许多其他原则都是基于这个概念的,即你应该积极面对变化。事实上,一些较老的编程原则如最小化耦合原则都是为了使代码能够容易变化。无论你是否是个极限编程者,基于这个原则去编写代码会让你的工作变得更有意义。
     

     

    七、架构设计非侵入性原则


           架构的侵入性:所谓侵入性就是指的这个架构设计出来的部件对系统的影响范围,比如框架的侵入性就很高,因为在一个工程中引入一个框架,你的整个设计都必须围绕这个框架来进行,一旦使用了,框架的可替代性几乎为0,这样子就是搞侵入性。组件的侵入性就比较低,比如ibaties,他可以在任何java框架下使用,甚至可以和其他ORM组件共存,你仅仅需要引入,配置,然后就可以使用了,你也可以用其他的ORM替换他,所以......这个体验应该是很愉快的。
          所以话说回来说到如果我们在设计一个通用架构的时候就应该注意到这个一个非常重要的地方,除非我们只是自己拿来用用,否则我们不应该假设我们的设计的用户已经具备怎么怎么样的环境或者是需要做什么特殊的设计才能够使用。
            这里打个比方,假如说我们在设计一个通用权限管理什么什么的时候我们就要想好,这是一个组件,还是框架,还是一个现成系统(复用通过改改代码实现,其实个人觉得这种设计很低级,虽然有的这样子的东西功能确实丰富)。确定了目标之后我们才好开始下一步,比如确定是一个框架的话可能发挥要自由一些,因为不需要高度的内聚,不过可能因为框架要设计的方方面面太多了,所以老是觉得个人的力量不足以搞这种东西出来。如果是组件的话就需要高度的内聚来实现非侵入式,比如引入DLL的时候还需要让所有页面继承自某个基类页就不算是一个good idear。
           虽然话说得好听,不过我在自己做设计的时候还是常常因为功力不够造成一些侵入的现象,但是高内聚低耦合都是我们不断追求的目标,所以所有做设计的同学们一起努力吧

    参考总结《大型网站技术架构》《架构真经》

    展开全文
  • 面向对象设计的七大设计原则详解

    万次阅读 多人点赞 2018-10-03 12:32:21
    面向对象的七大设计原则 文章目录面向对象的七大设计原则简述七大原则之间的关系一、开闭原则(The Open-Closed Principle ,OCP)概念理解系统设计需要遵循开闭原则的原因开闭原则的实现方法一个符合开闭原则的...

    面向对象的七大设计原则


    简述

    类的设计原则有七个,包括:开闭原则里氏代换原则迪米特原则(最少知道原则)单一职责原则接口分隔原则依赖倒置原则组合/聚合复用原则

    七大原则之间的关系


    七大原则之间并不是相互孤立的,彼此间存在着一定关联,一个可以是另一个原则的加强或是基础。违反其中的某一个,可能同时违反了其余的原则。

    开闭原则是面向对象的可复用设计的基石。其他设计原则是实现开闭原则的手段和工具。

    一般地,可以把这七个原则分成了以下两个部分:

    设计目标:开闭原则、里氏代换原则、迪米特原则
    设计方法:单一职责原则、接口分隔原则、依赖倒置原则、组合/聚合复用原则

    一、开闭原则(The Open-Closed Principle ,OCP)


    软件实体(模块,类,方法等)应该对扩展开放,对修改关闭。

    概念理解


    开闭原则是指在进行面向对象设计中,设计类或其他程序单位时,应该遵循:

    • 对扩展开放(open)
    • 对修改关闭(closed) 的设计原则。

    开闭原则是判断面向对象设计是否正确的最基本的原理之一。

    根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。

    • 扩展开放:某模块的功能是可扩展的,则该模块是扩展开放的。软件系统的功能上的可扩展性要求模块是扩展开放的。
    • 修改关闭:某模块被其他模块调用,如果该模块的源代码不允许修改,则该模块修改关闭的。软件系统的功能上的稳定性,持续性要求模块是修改关闭的。

    通过下边的例子理解什么是扩展开放和修改关闭:

    在这里插入图片描述

    左边的设计是直接依赖实际的类,不是对扩展开放的。

    右边的设计是良好的设计:

    • Client对于Server提供的接口是封闭的;
    • Client对于Server的新的接口实现方法的扩展是开放的。

    系统设计需要遵循开闭原则的原因


    1. 稳定性。开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。
    2. 扩展性。开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。
      遵循开闭原则的系统设计,可以让软件系统可复用,并且易于维护。

    开闭原则的实现方法


    为了满足开闭原则的对修改关闭原则以及扩展开放原则,应该对软件系统中的不变的部分加以抽象,在面向对象的设计中,

    • 可以把这些不变的部分加以抽象成不变的接口,这些不变的接口可以应对未来的扩展;
    • 接口的最小功能设计原则。根据这个原则,原有的接口要么可以应对未来的扩展;不足的部分可以通过定义新的接口来实现;
    • 模块之间的调用通过抽象接口进行,这样即使实现层发生变化,也无需修改调用方的代码。

    接口可以被复用,但接口的实现却不一定能被复用。
    接口是稳定的,关闭的,但接口的实现是可变的,开放的。
    可以通过对接口的不同实现以及类的继承行为等为系统增加新的或改变系统原来的功能,实现软件系统的柔性扩展。

    好处:提高系统的可复用性和可维护性。

    简单地说,软件系统是否有良好的接口(抽象)设计是判断软件系统是否满足开闭原则的一种重要的判断基准。现在多把开闭原则等同于面向接口的软件设计。

    一个符合开闭原则的设计


    需求:创建一系列多边形。
    首先,下面是不满足开闭原则的设计方法:

    Shape.h

    enumShapeType{ isCircle, isSquare};
    typedef struct Shape {
    	enumShapeType type
    } shape;
    

    Circle.h

    typedef struct Circle {
    	enumShapeType type;
    	double radius;
    	Point center;
    } circle;
    void drawCircle( circle* );
    

    Square.h

    typedef struct Square {
    	enumShapeType type;
    	double side;
    	Point topleft;
    } square;
    void drawSquare( square* );
    

    drawShapes.cpp

    #include "Shape.h"
    #include "Circle.h"
    #include "Square.h"
    void drawShapes( shape* list[], intn ) {
    	int i;
    	for( int i=0; i<n; i++ ) {
    		shape* s= list[i];
    		switch( s->type ) {
    		case isSquare:
    			drawSquare( (square*)s );
    			break;
    		case isCircle:
    			drawCircle( (circle*)s );
    			break;
    		}
    	}
    }
    

    该设计不是对扩展开放的,当增加一个新的图形时:

    • Shape不是扩展的,需要修改源码来增加枚举类型
    • drawShapes不是封闭的,当其被其他模块调用时,如果要增加一个新的图形需要修改switch/case

    此外,该设计逻辑复杂,总的来说是一个僵化的、脆弱的、具有很高的牢固性的设计。

    用开闭原则重构该设计如下图:

    在这里插入图片描述

    此时,在该设计中,新增一个图形只需要实现Shape接口,满足对扩展开放;也不需要修改drawShapes()方法,对修改关闭。

    开闭原则的相对性


    软件系统的构建是一个需要不断重构的过程,在这个过程中,模块的功能抽象,模块与模块间的关系,都不会从一开始就非常清晰明了,所以构建100%满足开闭原则的软件系统是相当困难的,这就是开闭原则的相对性。

    但在设计过程中,通过对模块功能的抽象(接口定义),模块之间的关系的抽象(通过接口调用),抽象与实现的分离(面向接口的程序设计)等,可以尽量接近满足开闭原则。

    二、 里氏替换原则(Liskov Substitution Principle ,LSP)


    所有引用基类的地方必须能透明地使用其派生类的对象。

    概念理解


    也就是说,只有满足以下2个条件的OO设计才可被认为是满足了LSP原则:

    • 不应该在代码中出现if/else之类对派生类类型进行判断的条件。

    • 派生类应当可以替换基类并出现在基类能够出现的任何地方,或者说如果我们把代码中使用基类的地方用它的派生类所代替,代码还能正常工作。

    以下代码就违反了LSP定义。

    if (obj typeof Class1) {
        do something
    } else if (obj typeof Class2) {
        do something else
    }
    

    里氏替换原则(LSP)是使代码符合开闭原则的一个重要保证。

    同时LSP体现了:

    • 类的继承原则:如果一个派生类的对象可能会在基类出现的地方出现运行错误,则该派生类不应该从该基类继承,或者说,应该重新设计它们之间的关系。

    • 动作正确性保证:从另一个侧面上保证了符合LSP设计原则的类的扩展不会给已有的系统引入新的错误。
      示例:

    里式替换原则为我们是否应该使用继承提供了判断的依据,不再是简单地根据两者之间是否有相同之处来说使用继承。

    里式替换原则的引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能。

    具体来说:

    • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    • 子类中可以增加自己特有的方法。
    • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松。
    • 当子类的方法实现父类的方法时(重载/重写或实现抽象方法)的后置条件(即方法的输出/返回值)要比父类更严格或相等。

    下面举几个例子帮助更进一步理解LSP:
    例:1:
    在这里插入图片描述

    Rectangle是矩形,Square是正方形,Square继承于Rectangle,这样一看似乎没有问题。

    假如已有的系统中存在以下既有的业务逻辑代码:

    void g(Rectangle r)
    {
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
    }
    

    则对应于扩展类Square,在调用既有业务逻辑时:

        Rectangle square = new Square();
        g(square);
    

    时会抛出一个异常。这显然违反了LSP原则。说明这样的继承关系在这种业务逻辑下不应该使用。

    例2:鲸鱼和鱼,应该属于什么关系?从生物学的角度看,鲸鱼应该属于哺乳动物,而不是鱼类。没错,在程序世界中我们可以得出同样的结论。如果让鲸鱼类去继承鱼类,就完全违背了Liskov替换原则。因为鱼作为基类,很多特性是鲸鱼所不具备的,例如通过腮呼吸,以及卵生繁殖。那么,二者是否具有共性呢? 有,那就是它们都可以在水中"游泳",从程序设计的角度来说,它们都共同实现了一个支持"游泳"行为的接口。

    例:3:运动员和自行车例子,每个运动员都有一辆自行车,如果按照下面设计,很显然违反了LSP原则。

    class Bike {
    public:
           void Move( );
           void Stop( );
           void Repair( );
    protected:
           int    ChangeColor(int );
    private:
           int    mColor;
    };
    
    
    class Player : private Bike
    {
    public:
          void  StartRace( );
          void  EndRace( ); 
    protected:
           int    CurStrength ( ); 
    private:
            int   mMaxStrength;
            int   mAge;
    };
    

    里式替换原则的优点


    • 约束继承泛滥,是开闭原则的一种体现。
    • 加强程序的健壮性,同时变更时也可以做到非常好地提高程序的维护性、扩展性。降低需求变更时引入的风险。

    重构违反LSP的设计

    如果两个具体的类A,B之间的关系违反了LSP 的设计,(假设是从B到A的继承关系),那么根据具体的情况可以在下面的两种重构方案中选择一种:

    • 创建一个新的抽象类C,作为两个具体类的基类,将A,B的共同行为移动到C中来解决问题。

    • 从B到A的继承关系改为关联关系。

    对于矩形和正方形例子,可以构造一个抽象的四边形类,把矩形和正方形共同的行为放到这个四边形类里面,让矩形和正方形都是它的派生类,问题就OK了。对于矩形和正方形,取width 和height 是它们共同的行为,但是给width 和height 赋值,两者行为不同,因此,这个抽象的四边形的类只有取值方法,没有赋值方法。

    对于运动员和自行车例子,可以采用关联关系来重构:

    class Player 
    {
    public:
          void  StartRace( );
          void  EndRace( ); 
    protected:
           int    CurStrength ( ); 
    private:
            int   mMaxStrength;
            int   mAge;
    Bike * abike;
    };
    

    在进行设计的时候,我们尽量从抽象类继承,而不是从具体类继承。

    如果从继承等级树来看,所有叶子节点应当是具体类,而所有的树枝节点应当是抽象类或者接口。当然这只是一个一般性的指导原则,使用的时候还要具体情况具体分析。

    在很多情况下,在设计初期我们类之间的关系不是很明确,LSP则给了我们一个判断和设计类之间关系的基准:需不需要继承,以及怎样设计继承关系。

    三、 迪米特原则(最少知道原则)(Law of Demeter ,LoD)


    迪米特原则(Law of Demeter)又叫最少知道原则(Least Knowledge Principle),可以简单说成:talk only to your immediate friends,只与你直接的朋友们通信,不要跟“陌生人”说话。

    概念理解


    对于面向OOD来说,又被解释为下面两种方式:

    1)一个软件实体应当尽可能少地与其他实体发生相互作用。

    2)每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。

    朋友圈的确定
    “朋友”条件:

    1. 当前对象本身(this)
    2. 以参量形式传入到当前对象方法中的对象
    3. 当前对象的实例变量直接引用的对象
    4. 当前对象的实例变量如果是一个聚集,那么聚集中的元素也都是朋友
    5. 当前对象所创建的对象

    任何一个对象,如果满足上面的条件之一,就是当前对象的“朋友”,否则就是“陌生人”。

    迪米特原则的优缺点


    迪米特原则的初衷在于降低类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,相互之间不存在(或很少有)依赖关系。

    迪米特原则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特原则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系,这在一定程度上增加了系统的复杂度

    例如,购房者要购买楼盘A、B、C中的楼,他不必直接到楼盘去买楼,而是可以通过一个售楼处去了解情况,这样就减少了购房者与楼盘之间的耦合,如图所示。

    违反迪米特原则的设计与重构


    下面的代码在方法体内部依赖了其他类,这严重违反迪米特原则

    class Teacher { 
    public: 
     void command(GroupLeader groupLeader) { 
    	   list<Student> listStudents = new list<Student>; 
    	   for (int i = 0; i < 20; i++) { 
    	        listStudents.add(new Student()); 
    	   } 
    	   groupLeader.countStudents(listStudents); 
    } 
    }
    

    方法是类的一个行为,类竟然不知道自己的行为与其他类产生了依赖关系(Teacher类中依赖了Student类,然而Student类并不在Teacher类的朋友圈中,一旦Student类被修改了,Teacher类是根本不知道的),这是不允许的。

    正确的做法是:

    class Teacher { 
    public:
    void command(GroupLeader groupLeader) { 
    	        groupLeader.countStudents(); 
      } 
    }
    
    class GroupLeader { 
    private:
    list<Student> listStudents; 
    public:
    GroupLeader(list<Student> _listStudents) { 
    	this.listStudents = _listStudents; 
    } 
    void countStudents() { 
    	cout<<"女生数量是:" <<listStudents.size() <<endl; 
       } 
    }
    

    使用迪米特原则时要考虑的


    • 朋友间也是有距离的

    一个类公开的public属性或方法越多,修改时涉及的面也就越大,变更引起的风险扩散也就越大。因此,为了保持朋友类间的距离,在设计时需要反复衡量:是否还可以再减少public方法和属性,是否可以修改为private等。

    注意: 迪米特原则要求类“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、protected等访问权限。

    • 是自己的就是自己的

    如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,就放置在本类中。

    四、单一职责原则


    永远不要让一个类存在多个改变的理由。

    换句话说,如果一个类需要改变,改变它的理由永远只有一个。如果存在多个改变它的理由,就需要重新设计该类。

    单一职责原则原则的核心含意是:只能让一个类/接口/方法有且仅有一个职责。

    为什么一个类不能有多于一个以上的职责?


    如果一个类具有一个以上的职责,那么就会有多个不同的原因引起该类变化,而这种变化将影响到该类不同职责的使用者(不同用户):

    • 一方面,如果一个职责使用了外部类库,则使用另外一个职责的用户却也不得不包含这个未被使用的外部类库。
    • 另一方面,某个用户由于某个原因需要修改其中一个职责,另外一个职责的用户也将受到影响,他将不得不重新编译和配置。
      违反了设计的开闭原则,也不是我们所期望的。

    职责的划分


    既然一个类不能有多个职责,那么怎么划分职责呢?

    Robert.C Martin给出了一个著名的定义:所谓一个类的一个职责是指引起该类变化的一个原因。

    如果你能想到一个类存在多个使其改变的原因,那么这个类就存在多个职责。

    SRP违反例:

    class Modem {
    		   void dial(String pno);    //拨号
               void hangup();        //挂断
               void send(char c);    //发送数据
               char recv();        //接收数据
    };
    

    乍一看,这是一个没有任何问题的接口设计。
    但事实上,这个接口包含了2个职责:第一个是连接管理(dial,hangup);另一个是数据通信(send,recv)。
    很多情况下,这2个职责没有任何共通的部分,它们因为不同的理由而改变,被不同部分的程序调用。所以它违反了SRP原则。

    下面的类图将它的2个不同职责分成2个不同的接口,这样至少可以让客户端应用程序使用具有单一职责的接口:
    在这里插入图片描述
    让 ModemImplementation实现这两个接口。我们注意到,ModemImplementation又组合了2个职责,这不是我们希望的,但有时这又是必须的。通常由于某些原因,迫使我们不得不绑定多个职责到一个类中,但我们至少可以通过接口的分割来分离应用程序关心的概念。

    事实上,这个例子一个更好的设计应该是这样的,如图:
    在这里插入图片描述
    例如,考虑下图的设计。

    Retangle类具有两个方法,如图。一个方法把矩形绘制在屏幕上,另一个方法计算矩形的面积。
    在这里插入图片描述
    有两个不同的Application使用Rectangle类,如上图。一个是计算几何面积的,Rectangle类会在几何形状计算方面给予它帮助。另一Application实质上是绘制一个在舞台上显示的矩形。

    这一设计违反了单一职责原则。Rectangle类具有了两个职责,第一个职责是提供一个矩形形状几何数据模型;第二个职责是把矩形显示在屏幕上。

    对于SRP的违反导致了一些严重的问题。首先,我们必须在计算几何应用程序中包含核心显示对象的模块。其次,如果绘制矩形Application发生改变,也可能导致计算矩形面积Application发生改变,导致不必要的重新编译,和不可预测的失败。

    一个较好的设计是把这两个职责分离到下图所示的两个完全不同的类中。这个设计把Rectangle类中进行计算的部分移到GeometryRectangle类中。现在矩形绘制方式的改变不会对计算矩形面积的应用产生影响了。
    在这里插入图片描述

    使用单一职责原则的理由


    单一职责原则从职责(改变理由)的侧面上为我们对类(接口)的抽象的颗粒度建立了判断基准:在为系统设计类(接口)的时候应该保证它们的单一职责性。

    降低了类的复杂度、提高类的可读性,提高系统的可维护性、降低变更引起的风险

    五、 接口分隔原则(Interface Segregation Principle ,ISP)


    不能强迫用户去依赖那些他们不使用的接口。

    概念理解


    换句话说,使用多个专门的接口比使用单一的总接口总要好。

    它包含了2层意思:

    • 接口的设计原则:接口的设计应该遵循最小接口原则,不要把用户不使用的方法塞进同一个接口里。如果一个接口的方法没有被使用到,则说明该接口过胖,应该将其分割成几个功能专一的接口。

    • 接口的依赖(继承)原则:如果一个接口a继承另一个接口b,则接口a相当于继承了接口b的方法,那么继承了接口b后的接口a也应该遵循上述原则:不应该包含用户不使用的方法。 反之,则说明接口a被b给污染了,应该重新设计它们的关系。

    如果用户被迫依赖他们不使用的接口,当接口发生改变时,他们也不得不跟着改变。换而言之,一个用户依赖了未使用但被其他用户使用的接口,当其他用户修改该接口时,依赖该接口的所有用户都将受到影响。这显然违反了开闭原则,也不是我们所期望的。

    总而言之,接口分隔原则指导我们:

    1. 一个类对一个类的依赖应该建立在最小的接口上

    2. 建立单一接口,不要建立庞大臃肿的接口

    3. 尽量细化接口,接口中的方法尽量少

    违反ISP原则的设计与重构


    下面我们举例说明怎么设计接口或类之间的关系,使其不违反ISP原则。

    假如有一个Door,有lock,unlock功能,另外,可以在Door上安装一个Alarm而使其具有报警功能。用户可以选择一般的Door,也可以选择具有报警功能的Door。

    有以下几种设计方法:

    ISP原则的违反例一:在Door接口里定义所有的方法。
    在这里插入图片描述
    但这样一来,依赖Door接口的CommonDoor却不得不实现未使用的alarm()方法。违反了ISP原则。

    ISP原则的违反例二:在Alarm接口定义alarm方法,在Door接口定义lock,unlock方法,Door接口继承Alarm接口。
    在这里插入图片描述
    跟方法一一样,依赖Door接口的CommonDoor却不得不实现未使用的alarm()方法。违反了ISP原则。

    遵循ISP原则的例一:通过多重继承实现

    在这里插入图片描述
    在Alarm接口定义alarm方法,在Door接口定义lock,unlock方法。接口之间无继承关系。CommonDoor实现Door接口,AlarmDoor有2种实现方案:

    1)同时实现Door和Alarm接口。

    2)继承CommonDoor,并实现Alarm接口。

    第2)种方案更具有实用性。

    这样的设计遵循了ISP设计原则。

    遵循ISP原则的例二:通过关联实现
    在这里插入图片描述
    在这种方法里,AlarmDoor实现了Alarm接口,同时把功能lock和unlock委让给CommonDoor对象完成。

    这种设计遵循了ISP设计原则。

    接口分隔原则的优点和适度原则


    • 接口分隔原则从对接口的使用上为我们对接口抽象的颗粒度建立了判断基准:在为系统设计接口的时候,使用多个专门的接口代替单一的胖接口。

    • 符合高内聚低耦合的设计思想,从而使得类具有很好的可读性、可扩展性和可维护性。

    • 注意适度原则,接口分隔要适度,避免产生大量的细小接口。

    单一职责原则和接口分隔原则的区别


    单一职责强调的是接口、类、方法的职责是单一的,强调职责,方法可以多,针对程序中实现的细节;

    接口分隔原则主要是约束接口,针对抽象、整体框架。

    六、 依赖倒置原则(Dependency Inversion Principle ,DIP)


    A. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
    B. 抽象不应该依赖于细节,细节应该依赖于抽象 C.针对接口编程,不要针对实现编程。

    概念理解


    依赖:在程序设计中,如果一个模块a使用/调用了另一个模块b,我们称模块a依赖模块b。

    高层模块与低层模块:往往在一个应用程序中,我们有一些低层次的类,这些类实现了一些基本的或初级的操作,我们称之为低层模块;另外有一些高层次的类,这些类封装了某些复杂的逻辑,并且依赖于低层次的类,这些类我们称之为高层模块。

    依赖倒置(Dependency Inversion)
    面向对象程序设计相对于面向过程(结构化)程序设计而言,依赖关系被倒置了。因为传统的结构化程序设计中,高层模块总是依赖于低层模块。
    在这里插入图片描述
    问题的提出:
    Robert C. Martin氏在原文中给出了“Bad Design”的定义:

    1. 系统很难改变,因为每个改变都会影响其他很多部分。

    2. 当你对某地方做一修改,系统的看似无关的其他部分都不工作了。

    3. 系统很难被另外一个应用重用,因为很难将要重用的部分从系统中分离开来。

    导致“Bad Design”的很大原因是“高层模块”过分依赖“低层模块”。

    一个良好的设计应该是系统的每一部分都是可替换的。如果“高层模块”过分依赖“低层模块”,一方面一旦“低层模块”需要替换或者修改,“高层模块”将受到影响;另一方面,高层模块很难可以重用。

    问题的解决:

    为了解决上述问题,Robert C. Martin氏提出了OO设计的Dependency Inversion Principle (DIP) 原则。

    DIP给出了一个解决方案:在高层模块与低层模块之间,引入一个抽象接口层。
    在这里插入图片描述

    High Level Classes(高层模块) --> Abstraction Layer(抽象接口层) --> Low Level Classes(低层模块)

    抽象接口是对低层模块的抽象,低层模块继承或实现该抽象接口。

    这样,高层模块不直接依赖低层模块,而是依赖抽象接口层。抽象接口也不依赖低层模块的实现细节,而是低层模块依赖(继承或实现)抽象接口。

    类与类之间都通过抽象接口层来建立关系。

    依赖倒置原则的违反例和重构


    示例:考虑一个控制熔炉调节器的软件。该软件从一个IO通道中读取当前的温度,并通过向另一个IO通道发送命令来指示熔炉的开或者关。

    温度调节器的简单算法:

      const byte THERMONETER=0x86;
      const byte FURNACE=0x87;
      const byte ENGAGE=1;
      const byte DISENGAGE=0;
    
      void Regulate(double minTemp,double maxTemp)
      {
         for(;;)
         {
            while (in(THERMONETER) > minTemp)
               wait(1);
            out(FURNACE,ENGAGE);
            
            while (in(THERMONETER) < maxTemp)
               wait(1);
            out(FURNACE,DISENGAGE);
         }
      }
    

    算法的高层意图是清楚的,但是实现代码中却夹杂着许多低层细节。这段代码根本不能重用于不同的控制硬件。

    由于代码很少,所以这样做不会造成太大的损害。但是,即使是这样,使算法失去重用性也是可惜的。我们更愿意倒置这种依赖关系。
    在这里插入图片描述
    图中显示了 Regulate 函数接受了两个接口参数。Thermometer 接口可以读取,而 Heater 接口可以启动和停止。Regulate 算法需要的就是这些。这就倒置了依赖关系,使得高层的调节策略不再依赖于任何温度计或者熔炉的特定细节。该算法具有很好的可重用性。

    通用的调节器算法:

      void Regulate(Thermometer t, Heater h, double minTemp,
         double maxTemp)
      {
        for(;;)
        {
           while (t.Read() > minTemp)
              wait(1);
           h.Engate();
    
           while (t.Read() < maxTemp)
              wait(1);
           h.Disengage();
        }
      }
    

    怎么使用依赖倒置原则


    1. 依赖于抽象

    • 任何变量都不应该持有一个指向具体类的指针或引用。

    如:

    class class1{
    class2* cls2 = new class2();
    }
    class class2{
    .......
    }
    
    • 任何类都不应该从具体类派生。

    2. 设计接口而非设计实现

    • 使用继承避免对类的直接绑定
      在这里插入图片描述

    • 抽象类/接口: 倾向于较少的变化;抽象是关键点,它易于修改和扩展;不要强制修改那些抽象接口/类

    例外:

    有些类不可能变化,在可以直接使用具体类的情况下,不需要插入抽象层,如:字符串类

    3. 避免传递依赖

    • 避免高层依赖于低层
      在这里插入图片描述
    • 使用继承和抽象类来有效地消除传递依赖
      在这里插入图片描述

    依赖倒置原则的优点


    可以减少类间的耦合性、提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险。

    七、 组合/聚合复用原则(Composite/Aggregate Reuse Principle ,CARP)


    尽量使用组合/聚合,不要使用类继承。

    概念理解


    即在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分,新对象通过向这些对象的委派达到复用已有功能的目的。就是说要尽量的使用合成和聚合,而不是继承关系达到复用的目的。

    组合和聚合都是关联的特殊种类。

    聚合表示整体和部分的关系,表示“拥有”。组合则是一种更强的“拥有”,部分和整体的生命周期一样。

    组合的新的对象完全支配其组成部分,包括它们的创建和湮灭等。一个组合关系的成分对象是不能与另一个组合关系共享的。

    组合是值的聚合(Aggregation by Value),而一般说的聚合是引用的聚合(Aggregation by Reference)。

    在面向对象设计中,有两种基本的办法可以实现复用:第一种是通过组合/聚合,第二种就是通过继承。

    什么时候才应该使用继承


    只有当以下的条件全部被满足时,才应当使用继承关系:

    • 1)派生类是基类的一个特殊种类,而不是基类的一个角色,也就是区分"Has-A"和"Is-A"。只有"Is-A"关系才符合继承关系,"Has-A"关系应当用聚合来描述。

    • 2)永远不会出现需要将派生类换成另外一个类的派生类的情况。如果不能肯定将来是否会变成另外一个派生类的话,就不要使用继承。

    • 3)派生类具有扩展基类的责任,而不是具有置换掉(override)或注销掉(Nullify)基类的责任。如果一个派生类需要大量的置换掉基类的行为,那么这个类就不应该是这个基类的派生类。

    • 4)只有在分类学角度上有意义时,才可以使用继承。

    总的来说:

    如果语义上存在着明确的"Is-A"关系,并且这种关系是稳定的、不变的,则考虑使用继承;如果没有"Is-A"关系,或者这种关系是可变的,使用组合。另外一个就是只有两个类满足里氏替换原则的时候,才可能是"Is-A" 关系。也就是说,如果两个类是"Has-A"关系,但是设计成了继承,那么肯定违反里氏替换原则。

    错误的使用继承而不是组合/聚合的一个常见原因是错误的把"Has-A"当成了"Is-A" 。"Is-A"代表一个类是另外一个类的一种;"Has-A"代表一个类是另外一个类的一个角色,而不是另外一个类的特殊种类。

    看一个例子:

    如果我们把“人”当成一个类,然后把“雇员”,“经理”,“学生”当成是“人”的派生类。这个的错误在于把 “角色” 的等级结构和 “人” 的等级结构混淆了。“经理”,“雇员”,“学生”是一个人的角色,一个人可以同时拥有上述角色。如果按继承来设计,那么如果一个人是雇员的话,就不可能是学生,这显然不合理。
    在这里插入图片描述
    正确的设计是有个抽象类 “角色”,“人”可以拥有多个“角色”(聚合),“雇员”,“经理”,“学生”是“角色”的派生类。
    在这里插入图片描述

    通过组合/聚合复用的优缺点


    优点:

    • 1.新对象存取子对象的唯一方法是通过子对象的接口。
    • 2.这种复用是黑箱复用,因为子对象的内部细节是新对象所看不见的。
    • 3.这种复用更好地支持封装性。
    • 4.这种复用实现上的相互依赖性比较小。
    • 5.每一个新的类可以将焦点集中在一个任务上。
    • 6.这种复用可以在运行时间内动态进行,新对象可以动态的引用与子对象类型相同的对象。
    • 7.作为复用手段可以应用到几乎任何环境中去。

    缺点: 就是系统中会有较多的对象需要管理。

    通过继承来进行复用的优缺点


    优点:

    • 新的实现较为容易,因为基类的大部分功能可以通过继承的关系自动进入派生类。
    • 修改和扩展继承而来的实现较为容易。

    缺点:

    • 继承复用破坏封装性,因为继承将基类的实现细节暴露给派生类。由于基类的内部细节常常是对于派生类透明的,所以这种复用是透明的复用,又称“白箱”复用。

    • 如果基类发生改变,那么派生类的实现也不得不发生改变。

    • 从基类继承而来的实现是静态的,不可能在运行时间内发生改变,没有足够的灵活性。

    展开全文
  • Solid设计原则

    万次阅读 2020-10-25 23:56:15
    开闭原则(总纲,其他原则是其实现) 软件实体应当对扩展开放,对修改关闭 即当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求 1 里氏替换原则 子类...
  • 架构设计模式—6大设计原则

    千次阅读 2019-07-03 22:53:00
    架构设计原则 6大设计原则 Single Responsibility Principle : 单一职责原则 Liskov Substitution Principle : 里氏替换原则 Dependence Inversion Principle :依赖倒置原则 Interface ...
  • 设计模式之设计原则

    千次阅读 2015-03-04 21:25:53
    设计模式之设计原则 1、单一职责原则(Single Responsibility Principle:SRP) 2、里氏替换原则(Liskov Substitution Principle:LSP) 3、依赖倒置原则(Dependence Inversion Principle:DIP) ...
  • 面向对象的设计原则

    千次阅读 2019-04-15 12:42:56
    文章目录一、设计原则概述1.1 面向对象设计原则概述:1.2 单一职责原则1.3 开闭原则1.4 里氏代换原则1.5 依赖倒转原则1.6 接口隔离原则1.7 合成复用原则1.8 迪米特法则 一、设计原则概述 1.1 面向对象设计原则概述...
  • 好了,废话不多说,开始整理Java设计原则! 六大设计原则 Java设计原则 - 单一职责原则 Java设计原则 - 里氏替换原则 Java设计原则 - 依赖倒置原则 Java设计原则 - 接口隔离原则 Java设计原则 - 迪米特法则 Java...
  • Java设计原则 - 里氏替换原则

    千次阅读 2018-03-14 17:46:41
    六大设计原则 Java设计原则 - 单一职责原则 Java设计原则 - 里氏替换原则 Java设计原则 - 依赖倒置原则 Java设计原则 - 接口隔离原则 Java设计原则 - 迪米特法则 Java设计原则 - 开闭原则 定义 如果对每一个...
  • 数据库设计原则

    千次阅读 2019-06-09 09:52:33
    一,数据库设计原则 1. 原始单据与实体之间的关系  可以是一对一、一对多、多对多的关系。在一般情况下,它们是一对一的关系:即一张原始单据对应且只对应一个实体。 在特殊情况下,它们可能是一对多或多对一的...
  • 页面设计原则

    千次阅读 2017-07-25 09:56:12
    页面设计原则 Web作为出版物的一种,同其他出版物如报纸,杂志等在设计上有许多共同之处,也要遵循一些设计的基本原则,不同之处在于每章提到的Web的“非所见即所得”特性和交互性。因此,熟悉一些设计的基本原则...
  • 系统设计原则

    千次阅读 2018-10-03 20:27:35
    业务设计原则 高并发原则 1. 无状态 若应用无状态、方便水平扩展、则要保证配置服务有状态 eg. 不同的机房需要读取不同的数据源、此时可以通过配置中心指定 2. 拆分 拆分不是必须的、可以根据系统流量和人员...
  • 设计模式之面向对象设计原则

    千次阅读 2020-11-14 14:54:30
    最简单的面向对象设计原则,它用于控制类的粒度大小; 单一职责原则是实现高内聚,低耦合的指导方针。它是最简单但又最难运用的原则,需要设计人员发现类的不同职责并将其分离; 如原始类: CustomerDataChart 类...
  • 面向对象设计原则

    万次阅读 2018-09-28 15:36:19
    面向对象的软件设计中,有23种经典的设计模式,是一套前人代码设计经验的总结,如果把设计模式比作武功招式,那么设计原则就好比是内功心法。常用的设计原则有七个,下文将具体介绍。 七种设计原则简单归纳如下 ...
  • 设计原则范式 之 面向对象的5个基本设计原则
  • 设计模式需要遵循基本的软件设计原则。可维护性(Maintainability)和可复用性(Reusability)是衡量软件质量的重要的两个属性: 可维护性:软件能够被理解、改正、适应及扩展的难易程度 可复用性:软件能够被复用...
  • 设计原则—SOLID(OCP)

    万次阅读 2020-12-04 09:05:09
    开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities ...在 23 种经典设计模式中,大部分设计模式都是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。
  • 尼尔森十大交互设计原则

    千次阅读 2019-04-21 12:16:30
    Jakob Nielsen(雅各布·尼尔森)的十大交互设计原则。它们被称为“启发式”,因为它们是广泛的经验法则,而不是特定的可用性指导原则。因此,我们不能把它上升为一种标准,而是应该当做一种经验来学习,然后跟现实...
  • 六大设计原则之开闭原则

    万次阅读 多人点赞 2016-08-28 10:32:38
    开闭原则的定义开闭原则是java世界里最基础的设计原则,它指导我们如何建立一个稳定,灵活的系统。开闭原则定义如下:Software entities like classes,modules and functions should be open for extension but ...
  • OOP设计原则

    千次阅读 2016-04-11 18:25:39
    它的设计原则对于指导开发有实际意义。S.O.L.I.D 是面向对象设计(OOD)和面向对象编程(OOP)中的几个重要编码原则(Programming Priciple)的首字母缩写。面向对象设计的原则SRP The Single Responsibility Principle...
  • 软件设计的七大设计原则

    千次阅读 2019-01-05 00:02:35
    七大设计原则是23种设计模式的基础,体现了软件设计的思想,但并不是所有设计模式都遵循这七大设计原则,有些设计模式只遵循一部分设计原则,是对一些实际情况做的一些取舍。在我们项目中也并不一定完全遵循所有设计...
  • GRASP设计原则(职责分配原则)

    千次阅读 2018-01-04 23:42:57
    GRASP设计原则(职责分配原则) GRASP(General responsibility assignment software Principle)设计原则是设计模式的基础,在GOF的23中设计模式中处处可以体现其中的一个或多个设计原则,所以在掌握设计模式之前...
  • 七大设计原则

    千次阅读 2018-11-17 10:44:10
    七大原则是程序员架构之路上躲...设计原则的出现是为了提高系统的可维护性和可复用性,提高系统的高内聚和低耦合! 每种设计原则的组合和使用都是在业务场景和需求量中进行取舍! =================================...
  • 应用架构设计原则

    千次阅读 2018-06-02 08:51:27
    文 / 阿里P7工程师 严明明目录抽象原则共享原则自治原则冗余原则分布原则自动原则软件系统架构设计原则就是把我们在各种场景下的架构设计进行抽选化提取公共特征形成过一定的方法论,这些方法论是经过严格推敲并具备...
  • 设计模式的总结有助于构建软件开发知识体系,为后期软件的架构设计、重构打下夯实基础,所以开设了Android设计模式专栏,... 本文重在总结设计模式和设计原则,后期会结合具体实例代码来说明常用的设计原则和设计模式。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 86,371
精华内容 34,548
关键字:

设计原则