【问题标题】:Object-Oriented Programming: How to properly design, implement, and name a method which involve object interactions?面向对象编程:如何正确设计、实现和命名涉及对象交互的方法?
【发布时间】:2015-04-05 22:13:05
【问题描述】:

语言无关紧要,这是面向对象的通用问题(以 java/C# 等为例)。采取一个简单的概念。 一个人有一辆汽车。人可以驾驶汽车。汽车通常不会开车或四处游荡,对吗? `` 但是,通常在代码中,我们会看到类似 myCarObject.Drive() 的方法。

现在当一个人被介绍,这个人开车:

======================= First Way =================================
class Car{
    int odometer;void drive(){ odometer++; } 
}
class Person{
    void driveCar(Car c) { c.drive(); }
}
========================================================================

================================ Alternative Way =======================

public Car{ 
    int odometer;   // car doesn't do the driving, it's the person, so no drive()
}
public Person{
    void driveCar(Car c) { c.odometer++; }
}

===========================等其他方式....============== ===============

================================================ ==============================

所以,我的问题很明确:在类似情况下设计/实现/命名方法的最佳方式是什么?

【问题讨论】:

    标签: oop methods language-agnostic


    【解决方案1】:

    让这样的简化示例变得有意义有点困难,但这里有一个尝试:

    Car 类通常包含对象可以利用其拥有的信息自行执行的操作的方法,例如:

    public class Car {
    
      private bool engineOn;
    
      public int Speed { get; private set; }
    
      public void Start() { engineOn = true; Speed = 0; }
      public void Accelerate() { Speed++; }
      public void Break() { if (Speed > 0) Speed--; }
      public void Stop() { Speed = 0; engineOn = false; };
    
    }
    

    Person 类将通过控制汽车​​本身在其环境中不知道的事物来管理汽车。示例:

    public class Person {
    
      public void Drive(Car car, int speedLimit) {
        car.Start();
        while (car.Speed < speedLimit) {
          car.Accelerate();
        }
        while (car.Speed > 0) {
          car.Break();
        }
        car.Stop();
      }
    
    }
    

    当然,在每种情况下如何使用 OO 有许多不同的变体。

    【讨论】:

      【解决方案2】:

      如果您希望以一种非常类似于人类语言语义的方式来表达您的逻辑,您需要在一个逻辑上能够执行它的实体上调用一个动作或函数。

      当行为不能被放置在一个对象上时(从某种意义上说它有状态),你把它放在一个服务或实用程序类中,或者一些类似的构造中。 Authenticate 是一个典型的例子,它在用户或任何其他对象上调用没有多大意义。为此,我们创建了一个AuthenticationProvider(或服务,随你喜欢)。

      在您的 Person 和 Car 场景中,它是一个对象调用另一个对象的行为。因此,person.Drive(car) 最有意义。

      如果一个人拥有一辆汽车(并且汽车总是由一个人拥有),那么person.Drive() 可能是您唯一需要做的事情。 Drive() 方法将可以访问person 的属性,其中之一是它的car

      这里要注意的重要一点是松散耦合的概念。在更复杂的场景中,您不想在模型中进行各种交叉引用。但是通过使用接口和抽象,您经常会发现自己将方法放在从现实世界的角度来看并不真正属于的对象上。诀窍是要意识到并利用一种语言的特性来同时实现松散耦合和现实语义。

      请记住,在实际应用程序中,您会将引导代码隐藏在其他地方,下面是 C# 中的示例:

      我们首先为可传输的事物 (ITransporter) 和可传输的事物 (ITransportable) 定义接口:

      public interface ITransportable
      {
          void Transport(Transportation offset);
      }
      
      public interface ITransporter
      {
          void StartTransportation(ITransportable transportable);
          void StopTransportation(ITransportable transportable);
      }
      

      注意Transportation 帮助类,它包含在以一定速度传输一段时间后重新计算ITransportable 的当前位置所需的信息等等。一个简单的例子:

      public class Transportation
      {
          public double Velocity { get; set; }
          public TimeSpan Duration { get; set; }
      }
      

      然后我们继续为这些创建我们的实现。您可能已经猜到了,Person 将派生自 ITransportableCar 派生自 ITransporter

      public class Person : ITransportable
      {
          public Tuple<double, double> Location { get; set; }
          private ITransporter _transporter;
      
          void ITransportable.Transport(Transportation offset)
          {
              // Set new location based on the offset passed in by the car
          }
      
          public void Drive<TCar>(TCar car) where TCar : ITransporter
          {
              car.StartTransportation(this);
              _transporter = car;
          }
      
          public void StopDriving()
          {
              if (_transporter != null)
              {
                  _transporter.StopTransportation(this);
              }
          }
      }
      

      密切注意我在那里所做的事情。我在Person 类上提供了一个显式接口实现。这意味着 Transport 只能在此人实际被引用为 ITransportable 时被调用 - 如果您将其引用为 Person,则只有 DriveStopDriving 方法可见。

      现在是汽车:

      public class Car : ITransporter
      {
          public double MaxVelocity { get; set; }
          public double Acceleration { get; set; }
          public string FuelType { get; set; }
      
          private Dictionary<ITransportable, DateTime> _transportations = new Dictionary<ITransportable, DateTime>();
      
          void ITransporter.StartTransportation(ITransportable transportable)
          {
              _transportations.Add(transportable, DateTime.UtcNow);
          }
      
          void ITransporter.StopTransportation(ITransportable transportable)
          {
              if (_transportations.ContainsKey(transportable))
              {
                  DateTime startTime = _transportations[transportable];
                  TimeSpan duration = DateTime.UtcNow - startTime;
                  var offset = new Transportation
                  {
                      Duration = duration,
                      Velocity = Math.Max((Acceleration*duration.Seconds), MaxVelocity)/2
                  };
      
                  transportable.Transport(offset);
                  _transportations.Remove(transportable);
              }
          }
      }
      

      按照我们之前设置的指南,Car 上也不会有任何(可见的)方法。除非您将其明确引用为 ITransporter,这正是 Person 的 DriveStopDriving 方法内部发生的情况。

      所以这里的汽车只是汽车。它有一些属性,就像一辆真正的汽车,你可以根据这些属性确定一个人驾驶它一定时间后的位置偏移。汽车不能“驾驶”、“启动”或类似的东西。人对汽车这样做——汽车不会对自己这样做。

      为了使其更真实,您必须添加各种额外的元数据,这些元数据会影响汽车在特定路线上特定时间段内的平均速度。事实是,无论如何,您可能最终都不会建模这样的东西。我坚持使用您的模型只是为了说明如果您使用的对象很难保留自然语言语义。

      客户如何使用这些类的示例:

      Person person = new Person();
      Car car = new Car();
      
      // car.Transport(); will not compile unless we explicitly
      // cast it to an ITransporter first.
      
      // The only thing we can do to set things in motion (no pun intended)
      // is invoke person.Drive(car);
      
      person.Drive(car);
      
      // some time passes..
      
      person.StopDriving();
      
      // currentLocation should now be updated because the Car
      // passed a Transportation object to the Person with information
      // about how quickly it moved and for how long.
      var currentLocation = person.Location;
      

      正如我之前已经避开的那样,这绝不是这个特定场景的一个很好的实现。但是,它应该说明如何解决您的问题的概念:将“传输”逻辑保留在“传输器”内部,而无需通过公共方法公开该逻辑。这为您的客户端代码提供了自然语言语义,同时保持了适当的关注点分离。

      有时您只需要利用您拥有的工具发挥创造力。

      【讨论】:

        【解决方案3】:

        在第二种情况下,就像您在说驾驶汽车的任务在于增加里程表一样。这显然不是驱动程序的事,并且违反了封装。里程表应该是一个实现细节。

        在第一种情况下,汽车可能不会自行行驶,但它会前进,因此您可以使用另一个动词。但是 car.advance() 可能不是人驾驶汽车的方式……即使是通过语音命令,命令的解码也可能会产生一系列更基本的命令。

        我非常喜欢 Guffa 的回答,它试图解决驾驶汽车的含义。但当然,你可能有其他背景......

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-10-29
          • 2011-04-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多