单一职责模式对班级应该有多具体?


14

例如,假设您有一个控制台游戏程序,该程序具有往返于控制台的各种输入/输出方法。难道是聪明的,让他们在一个单一inputOutput类或把它们分解到更具体的类,如startMenuIOinGameIOplayerIOgameBoardIO,等使得每个班大约有1-5的方法呢?

同时,如果最好将它们分解,将它们放置在IO命名空间中是否明智,从而使它们更冗长,例如:IO.inGame等?



相关:我从未见过将输入和输出结合起来的充分理由。当我故意将它们分开时,我的代码变得更加整洁。
Mooing Duck

1
无论如何,您在LowerCamelCase中使用哪种语言命名课程?
user253751 '16

Answers:


7

更新(回顾)

由于我已经写了一个相当冗长的答案,所以这可以归结为:

  • 命名空间很好,只要有意义就使用它们
  • 使用inGameIOplayerIO类可能会违反SRP。这可能意味着您将处理IO的方式与应用程序逻辑结合在一起。
  • 有几个通用的IO类,它们由处理程序类使用(或有时是共享的)。然后,这些处理程序类会将原始输入转换为应用程序逻辑可以理解的格式。
  • 输出也是如此:这可以通过相当通用的类完成,但是通过处理程序/映射器对象传递游戏状态,该处理程序/映射器对象将内部游戏状态转换为通用IO类可以处理的内容。

我认为您在错误地看待这个问题。您正在根据应用程序组件的功能来分离IO,而-就我而言-根据IO的来源和“类型”拥有单独的IO类更有意义。

首先要有一些基KeyboardIO类/泛型类MouseIO,然后根据需要的时间和位置,来创建不同的子类来处理所述IO。
例如,您可能希望以与游戏内控件不同的方式来处理文本输入。您会发现自己想根据每个用例以不同的方式映射某些键,但是映射不是IO本身的一部分,而是您处理IO的方式。

坚持使用SRP,我有几个类可以用于键盘IO。根据情况,我可能希望与这些类进行不同的交互,但是它们的唯一工作就是告诉我用户在做什么。

然后,我将这些对象注入到处理程序对象中,该处理程序对象会将原始IO映射到我的应用程序逻辑可以使用的对象上(例如:用户按下“ w”,处理程序将其映射到上MOVE_FORWARD)。

这些处理程序依次用于使角色移动并相应地绘制屏幕。过于简化,但要点是这种结构:

[ IO.Keyboard.InGame ] // generic, if SoC and SRP are strongly adhered to, changing this component should be fairly easy to do
   ||
   ==> [ Controls.Keyboard.InGameMapper ]

[ Game.Engine ] <- Controls.Keyboard.InGameMapper
                <- IO.Screen
                <- ... all sorts of stuff here
    InGameMapper.move() //returns MOVE_FORWARD or something
      ||
      ==> 1. Game.updateStuff();//do all the things you need to do to move the character in the given direction
          2. Game.Screen.SetState(GameState); //translate the game state (inverse handler)
          3. IO.Screen.draw();//generate actual output

现在,我们有了一个负责原始形式的键盘IO的类。另一个将这些数据转换成游戏引擎可以实际理解的东西的类,然后使用该数据更新所涉及的所有组件的状态,最后,一个单独的类将处理输出到屏幕的内容。

每个类都有一个工作:处理键盘输入是由一个不知道/不关心/必须知道其处理意味着什么的类完成的。它所做的只是知道如何获取输入(缓冲的,未缓冲的...)。

处理程序将此信息转换为内部的表示形式,以供应用程序的其余部分使用此信息。

游戏引擎获取转换后的数据,并使用它来通知所有相关组件正在发生的事情。这些组件中的每一个都只做一件事,无论是碰撞检查还是角色动画更改,都没关系,这取决于每个对象。

然后,这些对象将其状态中继回去,并将此数据传递给Game.Screen,从本质上讲,这是一个反向IO处理程序。它将内部表示映射到IO.Screen组件可用于生成实际输出的内容上。


因为它是一个控制台应用程序,所以没有鼠标,并且打印消息或开发板与输入紧密联系。在您的示例中,IOgame命名空间或带有子类的类吗?
shinzou

@kuhaku:它们是名称空间。我要说的要点是,如果您选择基于所处应用程序的哪个部分来创建子类,则可以有效地将基本IO功能与应用程序逻辑紧密地结合在一起。您将得到负责应用程序功能中 IO的类。在我看来,这听起来像是违反了SRP。至于名称vs命名空间类:95%的时间我倾向于使用命名空间
Elias Van Ootegem '16

我已经更新了答案,总结了我的答案
Elias Van Ootegem '16

是的,这实际上是我遇到的另一个问题(将IO与逻辑耦合),因此您实际上建议将输入与输出分开吗?
shinzou

@kuhaku:这实际上取决于您在做什么,以及输入/输出内容的复杂程度。如果处理程序(翻译游戏状态VS原始输入/输出)相差太多,那么你可能会想输入和输出类分开,如果没有,一个IO类是细
埃利亚斯·范·Ootegem

23

单一责任原则很难理解。我发现有用的是像思考句子一样思考它。您不要试图将很多想法塞进一个句子中。每个句子应该清楚地陈述一个想法,并推迟细节。例如,如果您想定义一辆汽车,您会说:

一种由内燃机驱动的通常具有四个车轮的公路车辆。

然后,您将分别定义诸如“车辆”,“道路”,“车轮”等的内容。您不会尝试说:

一种用于在两个地方之间的道路,道路或道路上运输人员的车辆,该车辆已被铺设或以其他方式改进以允许行驶,该行驶具有四个圆形物体,这些物体在固定在车辆下方的车轴上旋转,并由产生动力的发动机提供动力通过与汽油,机油或其他燃料一起燃烧产生动力。

同样,您应该尝试使您的类,方法等尽可能简单地陈述中心概念,并将细节推迟到其他方法和类。就像写句子一样,对于句子的大小也没有硬性规定。


因此,就像用几个词而不是多个词来定义汽车,然后定义小的特定但相似的类就好了吗?
shinzou

2
@kuhaku:小就是好。具体是好的。只要保持DRY类似,就可以了。您正在尝试使设计易于更改。保持小巧,具体的内容有助于您知道在哪里进行更改。保持DRY有助于避免不得不更改很多地方。
沃恩卡托

6
您的第二句话看起来像“该死,老师说这需要四页...'产生动力'是!!”
corsiKa '16

2

我会说最好的方法是将它们放在单独的类中。小班学习还不错,实际上大多数时候他们都是一个好主意。

关于您的特定情况,我认为将它们分隔开可以帮助您更改任何这些特定处理程序的逻辑,而又不影响其他处理程序,并且,如果有必要的话,添加新的输入/输出方法会更容易。


0

单一责任负责人指出,班级只应有一个变更理由。如果您的班级有多种变更原因,则可以将其拆分为其他班级,并利用合成来消除此问题。

要回答您的问题,我必须问您一个问题:您的班级只有一个改变的理由吗?如果没有,那么不要害怕继续添加更多的专业类,直到每个类只有一个改变的理由。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.