21

对于这个问题的主观性,我深表歉意,但我有点卡住了,我希望得到以前必须处理过这个问题的任何人的一些指导和建议:

我有(现在变成了)一个用 C# 2.0 编写的非常大的 RESTful API 项目,而且我的一些类变得很可怕。我的主要 API 类就是一个例子——有几十个成员和方法(可能接近数百个)。正如您可以想象的那样,它正在成为一场小噩梦,不仅要维护此代码,甚至只是导航代码都已成为一件苦差事。

我对 SOLID 原则相当陌生,而且我是设计模式的忠实粉丝(但我仍处于可以实现它们的阶段,但还不足以知道何时使用它们——在不那么明显的情况下) .

我需要将我的班级缩小规模,但我不知道如何最好地做到这一点。我的 StackOverflow 同事能否建议他们采用现有代码单体并将它们缩小到大小的方法?

4

5 回答 5

29

单一职责原则——一个类应该只有一个改变的理由。如果你有一个单一的类,那么它可能有不止一个改变的理由。简单地定义你改变的一个原因,并且尽可能的细化。我建议从“大”开始。将三分之一的代码重构到另一个类中。一旦你有了它,然后重新开始你的新课程。直接从一个班级升到 20 个班级太令人生畏了。

开放/封闭原则- 一个类应该对扩展开放,但对更改关闭。在合理的情况下,将您的成员和方法标记为虚拟或抽象。每个项目本质上都应该相对较小,并为您提供一些基本功能或行为定义。但是,如果您稍后需要更改功能,您将能够添加代码,而不是更改代码以引入新的/不同的功能。

Liskov 替换原则- 一个类应该可以替换它的基类。在我看来,这里的关键是正确地继承。如果你有一个巨大的 case 语句,或者两页 if 语句检查对象的派生类型,那么你违反了这个原则,需要重新考虑你的方法。

接口隔离原则——在我看来,这个原则非常类似于单一职责原则。它仅适用于高级(或成熟)类/接口。在大型类中使用此原则的一种方法是让您的类实现一个接口。接下来,将使用您的类的所有类型更改为接口的类型。这会破坏你的代码。但是,它将准确地指出你是如何消费你的课程的。如果您有三个实例,每个实例都使用自己的方法和属性子集,那么您现在知道需要三个不同的接口。每个接口都代表一组功能,以及改变的一个理由。

依赖倒置原则——父/子寓言让我明白了这一点。想想一个父类。它定义了行为,但不关心肮脏的细节。这是可靠的。然而,一个子类是关于细节的,不能依赖它,因为它经常变化。你总是想依赖父母,负责的班级,而不是相反。如果您有一个依赖于子类的父类,那么当您更改某些内容时会出现意外行为。在我看来,这与 SOA 的思维方式相同。服务合同定义了输入、输出和行为,没有任何细节。

当然,我的观点和理解可能不完整或错误。我建议向那些掌握了这些原则的人学习,比如鲍勃叔叔。对我来说,他的书《敏捷原则、模式和 C# 实践》是一个很好的起点。另一个很好的资源是Hanselminutes 上的 Bob 叔叔

当然,正如Joel 和 Jeff 所指出的,这些是原则,而不是规则。它们将成为帮助指导你的工具,而不是土地的法律。

编辑:

我刚刚发现这些看起来非常有趣的SOLID 截屏视频。每一个大约是 10-15 分钟。

于 2009-04-23T23:52:21.797 回答
4

Martin Fowler有一本经典著作——重构:改进现有代码的设计。

在那里,他提供了一组设计技术和决策示例,以使您现有的代码库更易于管理和维护(这也是 SOLID 原则的全部内容)。尽管重构中有一些标准例程,但它是一个非常自定义的过程,一个解决方案不能应用于所有项目。

单元测试是这个过程成功的支柱之一。您确实需要用足够的代码覆盖率覆盖现有的代码库,这样您就可以确保在更改它时不会破坏内容。实际上,使用具有模拟支持的现代单元测试框架将鼓励您进行更好的设计。

有像 ReSharper(我最喜欢的)和 CodeRush 这样的工具来帮助进行繁琐的代码更改。但这些通常是微不足道的机械东西,做出设计决策是一个复杂得多的过程,并且没有那么多工具支持。使用类图和 UML 会有所帮助。实际上,这就是我要开始的。试着理解已经存在的东西并为其带来一些结构。然后,您可以从那里就不同组件之间的分解和关系做出决定,并相应地更改您的代码。

希望这有助于重构愉快!

于 2009-04-24T00:01:55.643 回答
3

这将是一个耗时的过程。您需要阅读代码并找出不符合 SOLID 原则的部分并重构为新类。使用像 Resharper ( http://www.jetbrains.com )这样的 VS 插件将有助于重构过程。

理想情况下,您将对自动化单元测试有很好的覆盖,这样您就可以确保您的更改不会给代码带来问题。

更多信息

在主 API 类中,您需要识别相互关联的方法并创建一个更具体地表示该方法执行的操作的类。

例如

假设我有一个 Address 类,其中包含单独的变量,其中包含街道号、名称等。这个类负责插入、更新、删除等。如果我还需要以特定方式为邮政地址格式化地址,我可以一个名为 GetFormattedPostalAddress() 的方法,它返回格式化的地址。

或者,我可以将此方法重构为一个名为 AddressFormatter 的类,该类在其构造函数中接受一个 Address 并具有一个名为 PostalAddress 的 Get 属性,该属性返回格式化的地址。

这个想法是将不同的职责分成不同的类。

于 2009-04-23T23:33:29.597 回答
2

当遇到这种类型的事情时,我所做的(我很容易承认我以前没有使用过 SOLID 原则,但从我对它们的了解很少,它们听起来不错)是查看现有的代码库连接性的观点。本质上,通过查看系统,您应该能够找到一些内部高度耦合(许多频繁交互)但外部松散耦合(很少不频繁交互)的功能子集。通常,任何大型代码库中都有一些这样的部分;他们是切除的候选人。从本质上讲,一旦您确定了您的候选人,您就必须列举他们与整个系统外部耦合的点。这应该让您对所涉及的相互依赖程度有一个很好的了解。通常涉及相当多的相互依赖。评估子集及其连接点以进行重构;经常(但并非总是)最终会出现一些可以增加解耦的清晰结构重构。着眼于这些重构,使用现有的耦合来定义允许子系统与系统的其余部分一起工作所需的最小接口。在这些接口中寻找共性(通常,您会发现比您预期的更多!)。最后,实施您确定的这些更改。使用现有的耦合来定义允许子系统与系统的其余部分一起工作所需的最小接口。在这些接口中寻找共性(通常,您会发现比您预期的更多!)。最后,实施您确定的这些更改。使用现有的耦合来定义允许子系统与系统的其余部分一起工作所需的最小接口。在这些接口中寻找共性(通常,您会发现比您预期的更多!)。最后,实施您确定的这些更改。

这个过程听起来很糟糕,但在实践中,它实际上非常简单。请注意,这不是通往完全完美设计系统的路线图(为此,您需要从头开始),但它肯定会降低整个系统的复杂性并提高代码的可理解性。

于 2009-04-23T23:48:48.343 回答
1

OOD - 面向对象设计

SOLID - 一流的设计

单一职责原则- SRP - 由鲍勃叔叔介绍。方法、类、模块只负责做单一的事情(一个单一的任务)

开放/封闭原则- OCP - 由 Bertrand Meyer 介绍。方法、类、模块对扩展开放,对修改关闭。使用继承、抽象、多态、扩展、包装的力量。[Java 示例][Swift 示例]

[Liskov 替换原则] - LSP - 由 Barbara Liskov 和 Jeannette Wing 介绍。子类型可以替换超类型而没有副作用

接口隔离原则- ISP - Bob 大叔介绍的。你的界面应该尽可能小

[依赖倒置原则(DIP)] - DIP - Bob大叔介绍的。内部类、层不应该依赖于外部类、层。例如,当您有aggregation[About]依赖项时,您应该使用一些抽象/接口。[DIP vs DI vs IoC]

关于包/模块(.jar、.aar、.framework)的 6 条原则:

什么放在一个包里

  • 发布重用等效性
  • 共同的关闭
  • 常见的重用

包之间的耦合

  • 非循环依赖
  • 稳定的依赖
  • 稳定的抽象

【面向协议编程(POP)】

于 2021-02-07T11:59:10.423 回答