user3330840
user3330840

Reputation: 7369

Object-Oriented Programming: How to properly design, implement, and name a method which involve object interactions?

Language doesn't matter, it is generic object-oriented question(take java/C# etc). Take a simple concept. A Person has a Car. The Person can drive the Car. Car doesn't usually drive or wander around, right? `` But, usually in codes, we see methods like myCarObject.Drive().

Now when a Person is introduced, and the Person drives the car:

======================= 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++; }
}

========================== and other ways....============================

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

So, my question is clear: what is the best way to design/implement/name methods in similar cases?

Upvotes: 0

Views: 380

Answers (3)

aka.nice
aka.nice

Reputation: 9382

In second case, it's like you're saying that the task of driving a car consist in incrementing the odometer. It's clearly not the driver's business, and a violation of encapsulation. The odometer should probably be an implementation detail.

In first case, the car maybe does not drive itself, but it advances, so you could use another verb. But car.advance() is maybe not how a Person drives cars... Even if it was thru vocal commands, the decoding of the command would probably result in a sequence of more basic commands.

I very much like the answer of Guffa which tries to address what driving a car could mean. But of course, you may have another context...

Upvotes: 0

Fred Kleuver
Fred Kleuver

Reputation: 8047

If you wish to express your logic in a way that closely resembles human language semantics, you'll want to invoke an action or function on an entity which is logically capable of carrying it out.

When behavior cannot be placed on an object (in the sense that it has state), you put it in a Service or Utility class, or some similar construct. Authenticate is a classic example of something that doesn't make much sense to invoke on a user, or on any other object. For this purpose, we create an AuthenticationProvider (or service, whichever you prefer).

In your scenario of a Person and a Car, it's one object invoking behavior on another. person.Drive(car) would therefore make the most sense.

If a Person owns a Car (and a Car is always owned by a Person), then person.Drive() might be the only thing you need to do. The Drive() method will have access to the properties of person, one of which is its car.

An important thing to note here is the concept of loose coupling. In more complex scenario's, you don't want to all sorts of cross-references within your model. But by using interfaces and abstractions you'll often find yourself putting methods on objects where they don't really belong from a real-world perspective. The trick is to be aware of, and utilize a language's features for achieving loose coupling and realistic semantics simultaneously.

Keeping in mind that in a real application you'll have the bootstrapping code tucked away elsewhere, here is an example of how that might look like in C#:

We start off by defining interfaces for the things that can transport (ITransporter), and the things that can be transported (ITransportable):

public interface ITransportable
{
    void Transport(Transportation offset);
}

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

Note the Transportation helper class which contains the information necessary to re-calculate the current location of an ITransportable after it has been transported for a certain period of time with a certain velocity and whatnot. A simple example:

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

We then proceed to create our implementations for these. As you might have guessed, Person will derive from ITransportable and Car derives from 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);
        }
    }
}

Pay close attention to what I did there. I provided an explicit interface implementation on the Person class. What this means is that Transport can only be invoked when the person is actually referenced as an ITransportable - if you reference it as a Person, only the Drive and StopDriving methods are visible.

Now the Car:

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);
        }
    }
}

Following the guidelines we set earlier, a Car will not have any (visible) methods on it, either. Unless you explicitly reference it as an ITransporter, which is exactly what happens inside of the Person's Drive and StopDriving methods.

So a Car here is just a Car. It has some properties, just like a real car, based on which you can determine a location offset after a person drove it for a certain amount of time. A Car cannot "Drive", "Start", or anything like that. A Person does that to a Car - a Car does not do that to itself.

To make it more realistic you would have to add all sorts of additional metadata that affect a Car's average velocity over a certain period of time on a certain route. Truth is, you probably won't end up modeling something like this anyway. I stuck with your model just to illustrate how you could retain natural language semantics if you were working with objects that make it challenging to do so.

An example of how these classes may be used by a client:

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;

As I already eluded before, this is by no means a good implementation of this particular scenario. It should, however, illustrate the concept of how to solve your problem: to keep the logic of "transportation" inside of the "transporter", without the need to expose that logic through public methods. This gives you natural language semantics in your client code while retaining proper separation of concerns.

Sometimes you just need to be creative with the tools you have.

Upvotes: 1

Guffa
Guffa

Reputation: 700322

It's a bit difficult to make simplified examples like that make any sense, but here is an attemt:

A Car class would generally contain methods for the things that the object can do by itself with the information that it has, for example:

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; };

}

A Person class would would manage a car by controlling the things that the car itself is not aware of in its environment. Example:

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();
  }

}

There are of course many different variations of how you can use OO in each situation.

Upvotes: 2

Related Questions