局域网内个人网站建设,网店推广渠道,公众号官方,网络营销推广是做什么的4 设计原则 通常来说#xff0c;要想构建一个好的软件系统#xff0c;应该从写整洁的代码开始做起。这就是SOLID设计原则所要解决的问题。 SOLID原则的主要作用就是告诉我们如何将数据和函数组织成为类#xff0c;以及如何将这些类链接起来成为程序。请注意#xff0c;这里…4 设计原则 通常来说要想构建一个好的软件系统应该从写整洁的代码开始做起。这就是SOLID设计原则所要解决的问题。 SOLID原则的主要作用就是告诉我们如何将数据和函数组织成为类以及如何将这些类链接起来成为程序。请注意这里的“类”并不意味着SOLID原则只适用于面向对象编程这里的类仅仅代表了一种数据和函数的分组每个软件都会有自己的分类系统不管它们各自是不是将其称为“类”事实上都是SOLID原则的适用领域。 一般情况下我们为软件构建中层结构的主要目标如下 (1) 使软件可容忍被改动 (2) 使软件更容易被理解 (3) 构建可在多个软件系统中复用的组件 我们这里之所以会使用“中层”这个词是因为这些设计原则主要适用于那些进行模块级编程的程序员。SOLID原则应该直接紧贴于具体的代码逻辑之上这些原则是用来帮助我们定义软件架构中的组件和模块的。 SOLID原则是指 (1) 单一职责原则Single Responsibility PrincipleSRP每个类都应该只有一个引起它变化的原因换句话说一个类应该只有一个职责或功能当需求改变时会影响这个类的修改只应与该职责相关 (2) 开闭原则Open-Closed PrincipleOCP类模块应该是对扩展开放的open for extension对修改关闭的closed for modification也就是说已有的代码在不改动的情况下可以增加新的行为通过继承和多态来实现新功能的添加而不是修改已有的代码 (3) 里氏替换原则Liskov Substitution PrincipleLSP子类型必须能够替换掉它们的基类型这意味着任何使用基类型的地方子类型都可以无缝替代并且不会导致程序的行为出现错误或异常 (4) 接口隔离原则Interface Segregation PrincipleISP客户端不应该被迫依赖它们不需要的方法接口应该小而具体每个接口代表一种独立的责任或服务这样可以使系统更松散耦合且更易复用 (5) 依赖倒置原则Dependency Inversion PrincipleDIP高层模块不应该依赖低层模块二者都应当依赖其抽象抽象不应依赖细节细节应当依赖抽象这个原则强调了依赖于抽象而非具体实现的重要性通过依赖注入等方式实现有助于提供代码的可扩展性和可测试性
4.1 单一职责原则Single Responsibility Principle SRP很容易被误解为“每个模块都应该只做一件事”而实际上这只是SRP的一部分实际上SRP应该被描述成“任何一个软件模块都应该只对某一类行为负责”。这里的软件模块是指一组紧密相关的函数和数据结构。在这里“相关”这个词实际上就隐含了SRP这一原则。代码与数据就是靠着与某一类行为者的相关性被组合在一起的。 我们来看一些反面案例。
4.1.1 重复的假象 某个工资管理程序中的Employee类有三个函数calculatePay()、reportHours()和save() 如上所述这个类的三个函数分别对应的是三类非常不同的行为者违反了SRP设计原则 (1) calculatePay()函数是由财务部门制定的他们负责向CFO汇报 (2) reportHours()函数是由人力资源部门制定并使用的他们负责向COO汇报 (3) save()函数是由DBA制定的他们负责向CTO汇报 这三个函数被放在同一个源代码文件即同一个Employee类中程序员这样做实际上就等于使三类行为者的行为耦合在了一起这有可能会导致CFO团队的命令影响到COO团队所依赖的功能。例如calculatePay()函数和reportHours()函数使用同样的逻辑来计算正常工作时数程序员为了避免重复编码通常会将该算法单独实现为一个名为regularHours()的函数 接下来假设CFO团队需要修改正常工作时数的计算方法而COO带领的HR团队不需要这个修改因为他们对数据的用法是不同的这时候负责这项修改的程序员会注意到calculatePay()函数调用了regularHours()函数但可能不会注意到该函数会同时被reportHours()调用。于是该程序员就这样按照要求进行了修改同时CFO团队的成员验证了新算法正常工作这项修改最终被成功部署上线了。但是COO团队显然完全不知道这些事情的发生HR仍然在使用reportHours()产生的报表随后就会发现他们的数据出错了 这类问题发生的根源就是因为我们将不同行为者所依赖的代码强凑到了一起对此SRP强调这类代码一定要被分开。
4.1.2 代码合并 一个拥有很多函数的源代码文件必然会经历很多次代码合并该文件中的这些函数分别服务不同行为者的情况就更常见了。 例如CTO团队的DBA决定要对Employee数据库表结构进行简单修改与此同时COO团队的HR需要修改工作时数报表的格式。这样一来就很可能出现两个来自不同团队的程序员分别对Employee类进行修改的情况。不出意外的话他们各自的修改一定会互相冲突这就必须要进行代码合并。 在这个例子中这次代码合并不仅有可能让CTO和COO要求的功能出错甚至连CFO原本正常的功能也可能受到影响。 事实上这样的案例还有很多它们的一个共同点是多人为了不同的目的修改了同一份源代码这很容易造成问题的产生。而避免这种问题产生的方法就是将服务不同行为者的代码进行切分。
4.1.3 解决方案 我们有很多不同的方法可以用来解决上面的问题每一种方法都需要将相关的函数划分成不同的类。其中最简单直接的办法是将数据与函数分享设计三个类共同使用一个不包括函数、十分简单的EmployeeData类每个类只包含与之相关的函数代码互相不可见这样就不存在互相依赖的情况了。 这种解决方案的坏处在于程序员现在需要在程序里处理三个类。 另一种解决办法是使用Facade设计模式 这样一来EmployeeFacade类所需要的代码量就很少了它仅仅包含了初始化和调用三个具体实现类的函数。 当然也有些程序员更倾向于把最重要的业务逻辑与数据放在一起那么我们可以选择将最重要的函数保留在Employee类中同时用这个类来调用其他没那么重要的函数 总而言之每一个类都分别容纳了一组作用于相同作用域的函数而在该作用域之外它们各自的私有函数是互相不可见的。 单一职责原则主要讨论的是函数和类之间的关系——但是它在两个讨论层面上会以不同的形式出现在组件层面我们可以将其称为共同闭包原则Common Closure Principle在软件架构层面它则是用于奠定架构边界的变更轴心Axis of Change。
4.2 开闭原则Open-Closed Principle 开闭原则OCP是Bertrand Meyer在1988年提出的该设计原则认为设计良好的计算机软件应该易于扩展同时抗拒修改。换句话说一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。 OCP是我们进行系统架构设计的主导原则其主要目标是让系统易于扩展同时限制其每次被修改所影响的范围。实现方式是通过将系统划分为一系列组件并且将这些组件间的依赖关系按层次结构进行组织使得高阶组件不会因低阶组件被修改而受到影响。
4.3 里氏替换原则Liskov Substitution Principle 里氏替换原则LSP由Barbara Liskov在1987年的“数据抽象和层级结构”研讨会上首次提出并在后续论文《行为型模式的继承》中进一步明确。这一原则在软件工程领域具有深远影响对于保证代码的可扩展性和维护性至关重要。 里氏替换原则的核心思想可以总结为 (1) 子类兼容父类在一个程序设计中如果一个对象能够被其基类的实例所替代而不会引起程序的任何错误或异常则称该子类符合里氏替换原则即子类应当保持与父类相同的接口约定和行为特征 (2) 扩展而非修改子类可以在不破坏原有功能的基础上扩展功能但不能覆盖或削弱父类原有的功能这意味着子类可以添加新的方法或者增强已有的功能但不应该重写或更改父类的方法以导致原有契约失效 (3) 不变式条件保护子类必须尊重并维持父类定义的不变式条件即对状态的约束且不得引入新的约束条件 (4) 开放封闭原则的支持通过遵循LSP子类能够在不修改原有代码的基础上进行扩展这是实现开闭原则的关键手段之一 违反LSP会导致多态性失效、代码难以理解和维护以及系统稳定性降低等问题。因此在设计类层次结构时开发者需要确保子类严格遵循父类的设计契约避免出现不符合预期的行为变化。
4.4 接口隔离原则Interface Segregation Principle 接口隔离原则ISP由罗伯特.C.马丁Robert C.Martin在《敏捷软件开发原则、模式与实践》一书中提出该原则强调了接口设计应具有高度的内聚性避免创建臃肿的大接口。接口隔离原则的主要内容和指导原则包括 (1) 客户端特定需求每个接口应该针对一组相关功能进行设计而不是提供一个包含所有可能操作的庞大接口这意味着接口的设计应当基于客户端的需求来划分职责客户端只需要依赖它们实际使用的接口部分 (2) 单一职责在接口层面的体现类似于单一职责原则ISP要求接口也只承担一种抽象职责如果一个接口中包含了多个不相关的职责则应当将其拆分为多个更小、更专注的接口 (3) 降低耦合度通过细粒度接口的使用可以减少类之间的耦合度一个类不需要实现它不使用的接口方法这样当接口发生变化时对依赖此接口的类的影响将被最小化 (4) 可扩展性和灵活性增强遵循接口隔离原则可以使系统更加灵活易于扩展新功能可以通过新增接口或修改现有接口而不影响其他已有的模块 (5) 接口用户满意度理想的接口设计应当使接口使用者仅需了解他们所关心的方法不必关注无关细节从而提高用户的满意度和系统的清晰度 具体到实践中接口隔离原则鼓励开发者为不同的服务或功能创建独立且专门的接口并确保每个接口都足够小且具有明确的目的使得任何实现这些接口的类都能够准确无误地完成所需的任务而不会因为不必要的方法负担而导致设计复杂性增加。
4.5 依赖反转原则Dependency Inversion Principle 依赖反转原则DIP主要想告诉我们的是如果想要设计一个灵活的系统在源代码层次的依赖关系中就应该多引用抽象类型而非具体实现。在应用DIP时我们不必考虑稳定的操作系统或者平台设施因为这些接口很少会变动。我们主要应关注的是软件系统内部那些会经常变动的volatile具体实现模块这些模块是不停开发的也就会经常出现变更。 我们每次修改抽象接口时一定也会去修改对应的具体实现反之当我们修改具体实现时却很少需要去修改相应的抽象接口因此我们认为接口比实现更稳定。也就是说如果想要在软件架构设计上追求稳定就必须多使用稳定的抽象接口少依赖多变的具体实现。 依赖反转原则可以归结为以下几条具体的编码守则 (1) 应在代码中多使用抽象接口尽量避免使用那些多变的具体实现类这条守则适用于所有的编程语言无论静态类型语言还是动态类型语言同时对象的创建过程也应该受到严格的限制对此我们通常选择用抽象工厂这个设计模式 (2) 不要在具体实现类上创建衍生类 (3) 不要覆盖override包含具体实现的函数调用包含具体实现的函数通常就意味着引入了源代码级别的依赖即使覆盖了这些函数我们也无法消除这其中的依赖——这些函数继承了那些依赖关系在这里控制依赖关系的唯一办法就是创建一个抽象函数然后再为该函数提供多种具体实现 (4) 应避免在代码中写入与任何具体实现相关的名字或者是其他容易变动的事物的名字