2009-06-25 106 views
17

这是非常基本的东西,但在这里。我发现我无法同意我自己是否我的方式拆分成较小的类使事情更容易维护或更少维护。我熟悉设计模式,虽然没有详细说明,并且还有面向对象设计的概念。抛开所有奇特的规则和准则,我期待着通过一个非常简单的示例场景来思考我缺少的东西。基本上是这样的:“......这样的设计会让它变得更加困难”等等......所有我不期望的事情,因为本质上缺乏经验。如何将代码拆分为组件......大类?小班?

假设您需要编写一个基本的“文件读取器/文件写入器”风格类来处理某种类型的文件。我们称之为文件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 

行,所以这可以产生3基本类:

public class YadaKungFooFile 
public class YadaKungFooFileSection 
public class YadaKungFooFileSectionValue 

后两种类实质上唯一的数据结构与一个ToString()重写到吐出使用几个通用列表存储的值的字符串列表。这足以实现YadaKungFooFile保存功能。

随着时间的推移,YadaYadaFile开始增长。一些重载保存为不同的格式,包括XML等,文件开始推向800线左右。 现在真正的问题:我们想添加一个功能来验证YadaKungFoo文件的内容。首先想到的是显然要补充的:

var yada = new YadaKungFooFile("C:\Path"); 
var res = yada .Validate() 

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

var yada = new YadaKungFooFile("C:\Path"); 
var validator = new YadaKungFooFileValidator(yada); 
var result = validator.Validate(); 

现在这个样本显然是非常简单,琐碎和微不足道。不管是哪一种可能是两个方面不会做出太大的差别,但我不喜欢的是:

  1. YadaKungFooFileValidator类和YadaKungFooFile类似乎非常强烈地受到这种设计加上。看起来一个班的变化,可能会引发另一班的变化。
  2. 我知道诸如“验证器”,“控制器”,“经理”等词组表示与其他对象的状态相关的类别而不是其“自己的业务”,因此违反了分离关注原则和消息发送。

总而言之,我想我觉得我没有经验去了解设计糟糕的原因,在什么情况下它并不重要,什么样的担忧会带来更多的重量:更小的班级或更有凝聚力的班级。他们似乎是矛盾的要求,但也许我错了。也许验证器类应该是一个复合对象?

本质上,我要求对上述设计可能带来的好处/问题提出意见。什么可以做不同? (基本的FileValidator类,FileValidator接口,等等。你把它命名)。想象随着时间的推移,YadaKungFooFile功能会不断增长。

回答

15

鲍勃·马丁写了一系列的关于一流的设计,文章,他指的是作为固体原则:

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

的原则是:

  1. 单一职责原则:一上课应该有一个,并且只有一个理由要改变。
  2. 开放闭合原理:您应该可以扩展类的行为,而不需要修改它。
  3. Liskov替换原则:派生类必须是可替代的基类。
  4. 接口隔离原理:生成细粒度的接口,这些接口是客户端特定的。
  5. 依赖倒置原理:依赖于抽象,而不是依赖于结核。

所以那些灯,让我们来看看一些语句:

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

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

同样,对于所有这三个责任拥有一个验证器,对这个单独的类承担过多的责任。

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

2

我会解决这个

的YadaKungFooFileValidator类和 的YadaKungFooFile类似乎 通过这样的设计非常强耦合。 这似乎是一个班级的变化, 可能会触发其他变化。

是的,所以鉴于此,弄清楚如何尽可能无缝地做到这一点。在最好的情况下,设计YadaKungFooFile类的方式是验证器可以自行完成。

首先,语法本身应该很容易。我不希望语​​法验证发生变化,因此您可能只需要硬编码即可。

就可接受的属性而言,您可以从File类中暴露出验证器将检查的枚举器。如果解析的值不在给定节的任何枚举器中,则抛出异常。

等等等等......

14

我不认为一个类的大小是一个问题。关心的是更多的内聚和耦合。你想要设计松散耦合和内聚的对象。也就是说,他们应该关注一个明确定义的事情。所以如果事情碰巧非常复杂,那么这个班级将会增长。

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

+4

“我不认为一个班级的大小是一个问题。”理论上我很想同意。在实践中,我从来没有见过我喜欢的超过2000行的课程。 – PeterAllenWebb 2009-06-25 21:43:21

0

我在这种情况下处理事情的个人方式是拥有一个YadaKungFooFile类,它将由YadaKungFooFileSection列表组成,该列表将成为YadaKungFooFileValues列表。

在验证器的情况下,您应该让每个类都调用它下面的类的validate方法,并且层次结构中的最低类实现验证方法的实际内容。

0

如果验证不依赖于整个文件,而是仅对单个节的值起作用,则可以通过指定一组验证器来操作节的值,从而分解YadaKungFooFile类的验证。

如果验证确实取决于整个文件,验证自然会与文件类相关联,您应该考虑将它放在一个类中。

0

班级规模问题不像方法规模那么大。班级是组织的一种手段,虽然有好的和坏的方法来组织你的代码,但它并不直接与班级规模挂钩。

方法大小不同,因为方法正在做实际的工作。方法应该有非常具体的工作,这将不可避免地限制其规模。

我发现确定方法大小的最好方法是通过编写单元测试。如果您正在编写具有足够代码覆盖率的单元测试,那么当方法太大时就会变得明显。

单元测试方法需要很简单,否则你最终需要测试来测试你的单元测试,以确定它们工作正常。如果你的单元测试方法变得复杂,那很可能是因为一种方法试图做得太多,应该将其分解为更小的方法,并承担明确的责任。

4

YadaKungFooFile应该不知道如何从磁盘读取自身。它应该只知道如何遍历自己,披露它的孩子等。它还应该提供添加/删除儿童等的方法。

应该有YadaKungFooReader接口,YadaKungFooFile会在他的Load方法中使用它将用来加载自身从磁盘。再加上像AbstractKungFooReader,PlainTextYadaKungFooReader,XMLYadaKungFooWriter这样的几个实现,它们知道如何读取和对应的格式。

写作相同。

最后,应该有一个YadaKungFooValidatingReader,它将读取一个IYadaKungFooReader读取器,用它读取和验证输入。然后,只要您希望它在从磁盘读取时进行验证,就会将验证的读取器传递给YadaKungFooFile.Load。

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