代码之家  ›  专栏  ›  技术社区  ›  Stein Åsmul

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

  •  17
  • Stein Åsmul  · 技术社区  · 15 年前

    这是 非常基本的东西 ,但这里是。我发现我永远无法认同自己 把大类分成小类 使事物更易于维护或更少地可维护。我熟悉 设计模式 尽管不是很详细,也有 面向对象设计 . 抛开所有花哨的规则和指导方针,我想用一个非常简单的例子来说明我所缺少的东西。基本上是沿着“这个设计会使它更困难”等路线。因为缺乏经验,我没有预料到的所有事情。

    假设您需要编写一个基本的“文件阅读器/文件编写器”样式类来处理某种类型的文件。我们把文件称为 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. 这个 雅达康福菲利达多 类与 雅达坤 类似乎与此设计紧密耦合。这似乎是一个类中的更改,可能会触发另一个类中的更改。
    2. 我知道诸如“验证器”、“控制器”、“管理器”等短语…表示一个与其他对象的状态相关的类,而不是它的“自己的业务”,因此违反了关注点原则和消息发送的分离。

    总之,我想我觉得我没有经验去理解为什么一个设计是坏的,在什么情况下它并不真正重要,什么关注承载更多的重量:小班或更有凝聚力的班。它们似乎是矛盾的要求,但也许我错了。也许验证程序类应该是复合对象?

    本质上,我要求对上述设计可能带来的好处/问题发表评论。有什么可以改变的?(基本filevalidator类、filevalidator接口等)。你叫它吧)。想想YadakungFoofile的功能随着时间的推移而不断增长。

    7 回复  |  直到 15 年前
        1
  •  15
  •   Tim Lesher    15 年前

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

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

    原则是:

    1. 单一责任原则: 一个类应该有一个,而且只有一个,改变的原因。
    2. 开闭原理: 您应该能够在不修改类行为的情况下扩展它。
    3. Liskov替代原则: 派生类必须可替换为其基类。
    4. 界面分离原理: 创建特定于客户端的细粒度接口。
    5. 依赖倒置原理: 依靠抽象,而不是具体。

    因此,根据这些,让我们看看其中的一些陈述:

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

    这是第一个大的危险标志:yadayadafile开始有两个职责:1)维护节/键/值数据结构,2)知道如何读写一个类似ini的文件。所以有第一个问题。添加更多的文件格式会使问题复杂化:当1)数据结构更改,或2)文件格式更改,或3)添加新的文件格式时,yadayadafile会发生更改。

    同样,对于所有三个职责都有一个单一的验证器,这会给单个类带来太多的责任。

    一个大类就是一种“代码味道”:它本身并不坏,但它常常是由一些真正坏的东西造成的——在本例中,是一个试图成为太多东西的类。

        2
  •  14
  •   Vincent Ramdhanie    15 年前

    我不认为班级的大小是一个问题。关注的是更多的内聚和耦合。您希望设计松散耦合和内聚的对象。也就是说,他们应该关注一件定义明确的事情。因此,如果事情非常复杂,那么这个类就会增长。

    您可以使用各种设计模式来帮助管理复杂性。例如,您可以做的一件事是创建一个验证器接口,让yadakunfile类依赖于接口而不是验证器。这样,只要接口不变,就可以在不更改yadakungfufile类的情况下更改validator类。

        3
  •  4
  •   Andriy Volkov    15 年前

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

    应该有iyadakungfooreader接口,Yadakungfoofile将使用他的加载方法从磁盘加载自己。还有一些实现,比如abstractKungFooReader、plaintextYadakungFooReader、xmlyAdakungFoowriter,它们知道如何读取以及相应的格式。

    写作也是如此。

    最后,应该有yadakungfoovalidatingreader,它将带一个iyadakungfoooreader阅读器进来,使用它来读取并在读取时验证输入。然后您将验证读卡器传递给yadakungfoofile.load,只要您希望它在从磁盘读取时进行验证。

    或者,您可以使读卡器处于活动状态,而不是被动类。然后,您将它用作一个工厂,使用后一种正常访问方法创建您的yadakungfoofile,而不是传递给yadakungfoofile。在这种情况下,您的读卡器还应实现YadakungFoofile接口,以便您可以链接普通读卡器->验证读卡器->YadakungFoofile。有道理吗?

        4
  •  2
  •   Spencer Ruport    15 年前

    我来解决这个问题

    Yadakungfoofilevalidator类和 Yadakungfoofile类似乎是 与这种设计紧密结合。 一个班好像有变化,会吗? 可能引发另一个方面的变化。

    是的,因此根据这一点,找出如何使之尽可能无缝。在最好的情况下,设计YadakungFoofile类的方式应该使验证器能够自己接受它。

    首先,语法本身应该非常简单。我不希望语法验证发生变化,所以您可以安全地硬编码。

    对于可接受的属性,您可以公开来自验证程序将检查的文件类的枚举器。如果解析的值不在给定节的任何枚举器中,则引发异常。

    等等……

        5
  •  0
  •   Thomas    15 年前

    在这种情况下,我个人处理事情的方式是让一个类YadakungFoofile由一个YadakungFoofilections列表组成,它是一个YadakungFoofileValues列表。

    对于验证器,您将让每个类调用它下面的类的validate方法,层次结构中最低的类实现验证方法的实际部分。

        6
  •  0
  •   Hans Malherbe    15 年前

    如果验证不依赖于整个文件,而是只对单个节值有效,则可以通过指定对节值操作的一组验证程序来分解YadakungFoofile类的验证。

    如果验证确实依赖于整个文件,那么验证自然地耦合到文件类,您应该考虑将其放入一个类中。

        7
  •  0
  •   Brad C    15 年前

    类大小并不像方法大小那么重要。类是一种组织方式,虽然有好的和坏的方法来组织代码,但它并不直接与类的大小联系在一起。

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

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

    单元测试方法必须简单,否则您将最终需要测试来测试单元测试,以了解它们是否正常工作。如果您的单元测试方法变得越来越复杂,很可能是因为一个方法试图做的太多,应该被分解成职责明确的更小的方法。