关于方法及其实例变量的使用,如何在内部构建类?

时间:2010-01-02 18:11:29

标签: design-patterns oop

我在决定如何在内部创建类时遇到问题。最初我会有一个外部类处理所有变量管理:

String destination = car.setDestination("San Fransisco");
int totalGas = car.getAmountOfGas();
int requiredGas = car.gasRequiredForDestination(destination);
boolean enough = car.hasEnoughGas(totalGas, destination);
if (enough) 
   car.travelToDestination()

但我觉得另一个班级应该为汽车类的数据做所有的工作似乎很奇怪,因为汽车应该能够完成大部分的工作。因此,为了解决这个问题,我认为......“嗯,让我把所有这些都放在班级里似乎应该去的地方”。我想通过这样做我可以避免在方法之间来回传递这么多数据。所以我得到了这个:

Class Car {
  String location = "home";
  String destination;
  int totalGas = 0;
  int requiredGas = 0;
  boolean enoughGas = false;

  public Car (String destination, int totalGas) {
    this.destination = destination;
    this.totalGas = totalGas;
  }
  public boolean travelToDestination() {
     getGasRequiredForDestination();
     hasEnoughGas();
     if (enoughGas == true)
        location = destination;
  }

所以我在这里遇到的问题是,现在是的,我不必传递数据,事情看起来很干净,但现在我依赖每个函数将值返回给实例变量。这本身并不可怕,但这对我来说似乎是非常错误的。与此同时,我认为自己“好吧我将所有这些数据从一个地方传递到另一个地方是没有意义的,因为我似乎应该操纵实例变量。另一方面,我的程序最终有6行一排:

myMethod {
  doThis()
  doThat()
  checkThis()
  checkTheOtherThing()
}

我从来没有真正看到在现实生活中以这种方式完成的事情,所以我试图基本上计算A)如果这是错误的B)如果是这样的话我们何时应该将信息粘贴在实例变量中而不是传递给它。面向对象允许我们这样做,但我不知道这是不是一件好事。 C)是否有任何OO原则涉及以这种方式做或不做?也许我违反了我不知道的事情?

我已经对OO进行了很长时间的编程,但我倾向于有这样的问题,所以我希望能够解决这个问题。如果有任何书籍建议涉及OO的棘手方面,我也会对此感兴趣。

编辑:我应该说,这是一个很好的例子,所以在现实世界中有些东西我可能不会这样做。但我需要某种例子,因为我的代码太复杂了。

7 个答案:

答案 0 :(得分:4)

尝试更抽象地推理:尽管你的类的一个实例正在建模一个真实世界的实体(一件好事,当你可以方便地做到这一点)时,实例变量应该是你需要代表的所有该事物的状态 - 不是诸如计算的临时结果之类的人工制品,它们与任何现实世界的“状态”都不对应。

所以,例如,考虑你的班级:

Class Car {
  String location = "home";
  String destination;
  int totalGas = 0;
  int requiredGas = 0;
  boolean enoughGas = false;

并根据测试批评它“这个实例变量实际上是状态的一部分吗?”。

按照这个标准,locationtotalGas似乎很好 - 真实世界的汽车确实有一个位置,并且有一些气体,作为其真实世界状态的一部分。其他人更可疑。 destination如果您在旅途中或旅途中的各个地点代表汽车会很好 - 在任何给定时间都会有一个现在的地点,以及汽车行驶的目的地。但是根据你的代码判断你不是在做什么:如果气体充足,destination会立即成为location,所以你正在使用简化的现实模型,其中汽车只表示为在特定的地方,而不是在它们之间的路线上(顺便说一句 完全正确:任何抽象都是,不可避免地,有用的是,简化现实,如果为了你的应用目的你可以抽象出“地方之间的旅行”状态,一定要去吧。这同样适用于关于所需和足够气体的变量 - 而不是物体状态的自然部分。

为相应的方法创建局部变量,参数和返回值,即将旅行方法更改为:

  public void travelToDestination(String destination) {
     int requiredGas = getGasRequiredForDestination(destination);
     bool enoughGas = hasEnoughGas(requiredGas);
     if (enoughGas) {
        totalGas -= requiredGas;
        location = destination;
     }
  }

因此,计算所需值的某些(恰好是那些属于对象状态的值)在实例变量中,其他(计算的中间结果实际上不是对象状态的一部分) )是局部变量,参数,返回值。

这种混合方法比你原来的方法更有效,更有希望(所有那些“getter”方法调用,eep! - )或者另一个极端(其中一切和它的表兄是一个实例变量,仅仅是计算方便,与良好的建模方法完全不同。)

由于实例变量和局部变量因此在大多数计算中混合,许多编程样式要求它们具有可区分的名称(某些语言,如Python和Ruby使其成为必需的 - 例如,实例变量location将是拼写为@locationself.location - 但我正在讨论的语言样式不会强制解决问题,但仍允许您使用尾随下划线命名该实例变量location_,或者带有m_location前缀的m_等。

答案 1 :(得分:1)

这个问题可以由比我更聪明的人详细解释。但这是我的看法:

我倾向于尝试将类定义为一些数据,以及需要对它们执行的一组操作(当然,在类继承层次结构之后)。所以你在Car类上封装操作的方法是正确的,因为在这种情况下你只需要做

Car.travelToDestination()

,没关系。

myMethod {
  doThis()
  doThat()
  checkThis()
  checkTheOtherThing()
}

没有错,因为上面的所有方法都在进行一个逻辑操作(我强烈建议让方法只进行一次逻辑操作)并正确使用它们。

关于传递的类数据,如果你可以在一个类中内部封装数据和操作,它通常被认为是一个好的设计,这似乎是你想要在你的例子中做的。

关于图书推荐,我发现自己Code Complete 2有一个很好的关于类设计的章节(我认为第6章称为工作类)应该解决这样的疑问。我发现自己指的是相当多的东西。无论哪种方式,我认为所有程序员都应该阅读这本书。

答案 2 :(得分:1)

我认为mabe你的问题是应该有“一个应该去的课程”的想法。我不会假装理解您所谈论的域名,但是与该问题相关的一些明显的类似乎是:

  • 地图
  • 旅行计划员
  • 位置
  • 航点

我通常不会询问汽车是否可以从一个地方到另一个地方 - 我可能会问它能够在多大程度上继续当前的油箱负荷。我不希望它知道如何获得SF或里程数,我从地图中获得了一些使用航点。所有这一切都应该由旅行计划员协调,最终得出答案。

所有这些类都有自己的专用成员数据,并将使用特定的成员函数进行交互。其中一些函数将其他类的实例作为参数。

答案 3 :(得分:1)

实际上,您不应该使用实例变量将数据传递给方法。为什么不在一个方法中使用函数结果?

public boolean travelToDestination(string destination) {
   int requiredGas = getGasRequiredForDestination(destination);
   boolean enoughGas = hasEnoughGas(requiredGas);
   if (enoughGas) {
      location = destination;
      totalgas -= requiredgas;
   }
}

这使您的代码清楚地了解getGasRequiredForDestination()和hasEnoughGas()如何工作,而不必寻找副作用。这被称为最不惊讶的原则。

实例变量应该用于存储对象的状态控制流离开类方法后,在这种情况下,位置和从travelToDestination()返回的控制后的totalgas。如果控件应该提前返回,例如当汽车通过多个步骤前往目的地时,您需要在现场变量中存储其他信息。

答案 4 :(得分:1)

我就是这样做的:

Class Car {
    String location = "home";
    int totalGas = 0;

    public Car (int totalGas) {
        this.totalGas = totalGas;
    }

    public boolean travelToDestination(String destination) {
        int requiredGas = getGasRequiredForDestination(destination);
        if(totalGas >= requiredGas){
            location = destination;
            totalGas -= requiredGas;
            return true;
        } else {
            return false;
        }
    }
}

想法是尽可能少的状态,而“state”我的意思是指在方法范围之外的变量(例如成员变量,全局变量等)。我不确定这个原则是否有特定的名称,但我在阅读时肯定会在多个地方遇到类似的原则。我还可以证明从个人经历中减少状态的好处。

每个额外的成员变量都会降低可维护性并增加引入错误的机会。如果它们不能是局部变量,则只创建成员变量。

答案 5 :(得分:0)

我会拉出一个包含原点和目的地的Trip对象,并可以计算距离。我不认为汽车的责任是理解和操作旅行的细节。汽车的责任是了解它的里程数以及行驶距离和行驶距离。

你可以从这里找到两个方向。一个是负责执行汽车之旅,例如:

Trip trip = new Trip(origin, destination);
Car car = new Car();
car.fillTank();

if (car.canTakeTrip(trip))
    car.takeTrip(trip);

但这让我感到困惑。它将Car与Trip结合在一起并导致两次高级别的汽车呼叫,这两次呼叫均在Trip上运行。这表明你可能想要在车里有一个旅行会员并打电话给car.setTrip(旅行)...但现在我们知道我们走错了路。这种方式导致疯狂。汽车现在不仅与Trip相关联,而且还拥有一个与Car无关的物体。你不会问“这辆车有什么旅行?”你会问“汽车在哪里?”。

汽车应该做简单的汽车用品。加满气体。在这里开车。开车去那儿。它不应该知道“旅行”这样的事情。这表明我们颠倒了责任,并承担了旅行本身的工作。也许是这样的:

trip.setCar(car);
trip.travel();
System.out.println(trip.result); // might be "Not enough gas" or "Arrived! Wish you were here!"

在对trip.travel()的调用中,我们可能会看到:

public void travel()
{
    if (this.car.canTravel(this.distance))
    {
         car.travel(this.destination);
    }
    else
    {
         this.result = "Not enough gas";
    }
}

但是我们仍然将Trip to Car耦合在一起,所以我们可能会想要一个隐藏实现的Transport接口。对我来说,旅行拥有一辆运输车比拥有一辆旅行车更有意义。传输接口可能是:

interface Transport
{
    public boolean canTravel(int distanceInMiles);
    public void travel(String destination); // seems like we need more than a String here, but you get the idea
}

现在Car会像

一样实施
class Car implements Transport
{
    // implement canTravel and travel
}

最后Trip可能会做类似的事情:

class Trip
{
    public void travel(Transport transport)
    {
        if (transport.canTravel(this.distance))
        {
             transport.travel(this.destination);
        }
        else
        {
             this.result = "Not enough gas";
        }

    }
}

Car car = new Car();
car.fillUp();
Trip trip = new Trip(); // maybe set Transport on constructor?
trip.travel(car);

或类似的东西。我通常会尝试将对象放到对这些对象来说最自然的最简单的东西上。如果他们开始获得似乎没有自然流入少数(preferably one)职责的知识,那么我会寻找我需要定义的缺失的类,并开始尝试反转职责。

答案 6 :(得分:0)

一个对象应该有三个方面;状态,行为和身份。对象的关键指标是高内部凝聚力(和低外部耦合)。

让你的方法对共享的内部状态起作用绝对没有错。

我知道你说你的例子是为了说明一点,但'Car'看起来更像是'Journey'或'Route'