如何将代码拆分为组件......大类?小班?

Ste*_*mul 17 .net c#

这是非常基本的东西,但是这里有.我发现我无法同意自己,将大型课程分成小型课程的方式是否使得课程更易于维护或更难以维护.我熟悉设计模式,虽然不是很详细,但也熟悉面向对象设计的概念.抛开所有花哨的规则和指导方针,我希望通过一个非常简单的示例场景来挑选我所缺少的内容.基本上是这样的:"......这种设计会让它变得更加困难"......等等......由于缺乏经验,所以我没有预料到的所有事情.

假设您需要编写基本的"文件读取器/文件编写器"样式类来处理某种类型的文件.我们将该文件称为YadaKungFoo文件.YadaKungFoo文件的内容基本上类似于INI文件,但有细微差别.

有部分和价值观:

[Sections]
Kung,Foo
Panda, Kongo

[AnotherSection]
Yada,Yada,Yada
Subtle,Difference,Here,Yes

[Dependencies]
PreProcess,SomeStuffToPreDo
PreProcess,MoreStuff
PostProcess,AfterEight
PostProcess,TheEndIsNear
PostProcess,TheEnd
Run Code Online (Sandbox Code Playgroud)

好的,所以这可以产生3个基本类:

public class YadaKungFooFile
public class YadaKungFooFileSection
public class YadaKungFooFileSectionValue
Run Code Online (Sandbox Code Playgroud)

后两个类本质上只是具有ToString()重写的数据结构,以吐出使用几个通用列表存储的值的字符串列表.这足以实现YadaKungFooFile保存功能.

所以随着时间的推移,YadaYadaFile开始增长.几种重载以不同的格式保存,包括XML等,文件开始推向800行左右.现在真正的问题是:我们想要添加一个功能来验证YadaKungFoo文件的内容.想到的第一件事显然是添加:

var yada = new YadaKungFooFile("C:\Path");
var res = yada .Validate()
Run Code Online (Sandbox Code Playgroud)

我们已经完成了(我们甚至可以从构造函数中调用该方法).麻烦的是验证非常复杂,并且使得类非常大,所以我们决定创建一个这样的新类:

var yada = new YadaKungFooFile("C:\Path"); 
var validator = new YadaKungFooFileValidator(yada); 
var result = validator.Validate();
Run Code Online (Sandbox Code Playgroud)

现在这个样本显然非常简单,琐碎且微不足道.上述两种方式中的任何一种都可能不会产生太大的差异,但我不喜欢的是:

  1. YadaKungFooFileValidator类和YadaKungFooFile类似乎非常强烈地受到这种设计加上.这似乎是一个类的变化,可能会引发另一个类的变化.
  2. 我知道诸如"Validator","Controller","Manager"等短语表示一个关注其他对象状态而不是"自己的业务"的类,因此违反了关注原则的分离和消息发送.

总而言之,我想我觉得我没有经验可以理解设计为什么不好的所有方面,在什么情况下它并不重要,哪些关注更重要:更小的类或更具凝聚力的类.他们似乎是矛盾的要求,但也许我错了.也许验证器类应该是一个复合对象?

基本上我要求就上述设计可能带来的好处/问题提出意见.有什么不同的做法?(基础FileValidator类,FileValidator接口等等.你的名字).想想YadaKungFooFile功能随着时间的推移不断增长.

Tim*_*her 15

鲍勃·马丁写了一系列关于课堂设计的文章,他称之为SOLID原则:

http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

原则是:

  1. 单一责任原则: 一个班级应该只有一个改变的理由.
  2. 开放原则:您应该能够扩展类行为,而无需对其进行修改.
  3. Liskov替换原则:派生类必须可替代其基类.
  4. 接口隔离原则:创建客户端特定的细粒度接口.
  5. 依赖性倒置原则:取决于抽象,而不是结构.

所以鉴于这些,让我们看看一些陈述:

所以随着时间的推移,YadaYadaFile开始增长.几种重载以不同的格式保存,包括XML等,文件开始推向800行左右

这是第一个大红旗:YadaYadaFile开始时有两个职责:1)维护一个部分/键/值数据结构,以及2)知道如何读取和写入类似INI的文件.所以这是第一个问题.添加更多文件格式会增加问题:YadaYadaFile在1)数据结构发生变化时更改,或2)文件格式更改,或3)添加新文件格式.

同样,对所有这三个职责都设置一个验证器会对该单个班级承担太多责任.

一个大类是一个"代码味道":它本身并不坏,但它通常是由一些非常糟糕的东西产生的 - 在这种情况下,一个试图成为太多东西的类.


Vin*_*nie 14

我不认为课程的大小是一个问题.关注的是更多的凝聚力和耦合.您想要设计松散耦合和内聚的对象.也就是说,他们应该关注一个定义明确的事情.所以,如果事情发生得非常复杂,那么课程就会增长.

您可以使用各种设计模式来帮助管理复杂性.例如,您可以做的一件事是创建一个Validator接口,并让YadaKunfuFile类依赖于接口而不是Validator.这样,只要接口没有更改,您就可以更改Validator类而无需更改YadaKungfuFile类.

  • "我不认为班级的规模是一个问题." 从理论上讲,我很想表示赞同.在实践中,我从未见过一个我喜欢的超过2000行的课程. (4认同)

zvo*_*kov 5

YadaKungFooFile 不应该知道如何从磁盘读取自身。它应该只知道如何遍历自身,公开其子项等。它还应该提供添加/删除子项等的方法。

应该有 IYadaKungFooReader 接口,YadaKungFooFile 将在其 Load 方法中采用该接口,并使用该接口从磁盘加载自身。再加上一些知道如何读取和相应格式的实现,例如 AbstractKungFooReader、PlainTextYadaKungFooReader、XMLYadaKungFooWriter。

写作也一样。

最后,应该有 YadaKungFooValidatingReader,它将接收 IYadaKungFooReader 阅读器,用它来读取并在读取时验证输入。然后,每当您希望验证读取器从磁盘读取时进行验证时,您都可以将验证读取器传递给 YadaKungFooFile.Load。

或者,你可以让读者主动,而不是被动上课。然后,您可以将其用作工厂,使用后面的正常访问方法创建 YadaKungFooFile,而不是将其传递给 YadaKungFooFile。在这种情况下,您的阅读器还应该实现 YadaKungFooFile 接口,以便您可以链接普通阅读器 -> 验证阅读器 -> YadaKungFooFile。说得通?