3

I have two camera devices that are able to find a specific hardware illuminated point in an image, as well as measuring the physical distance to the surface (and some more stuff I chose to leave out, as it doesn't affect this problem right now), and the application uses the point and distance information independently of each other. Aha - same functionality with different implementations? I should use an interface! I started to define some, but as you'll see I'm probably making some mistakes in my design.

interface IPointFinder {
    // The point where the spot is 
    Point FindPoint(Image image);
}

interface IDistanceMeasurement {
    // Distance in mm
    int MeasureDistance();
}

interface IDevice {
    string DeviceName { get; }
    IPointFinder { get; }
    IDistanceMeasurement { get; }
    event CameraImage;
}

The reason for the device owning the interface ISpotFinder is that the spot will be illuminated on the surface in different ways depending on the hardware (trying to keep things that will change for the same reason together).

There are two implementations of the IDevice interface. One has an external hardware device for measuring the distance and calculates where the spot is from the distance value (hence ignoring the image parameter), and the other one calculates the distance value from the spot found by SpotFinder. I thought, "Well, I'll just make this dependency invisible for the application, and hide it in the implementation of IDevice (for example by letting the distance measurement observe the point finder)", but that was not really optimal as:

  1. If the distance is dependent on that the point has been found, then it requires an image to have been taken, and processed. Calling this before that method would not return any valuable information.
  2. If the point is dependent on that the distance has been measured, then it requires that the client code already called DistanceMeasurement.MeasureDistance(), otherwise it would not be able to get the point.

The "sequence of execution"-dependency (lack of better term) (edit: I learned from the comments that this is called Temporal Coupling) is hence reversed depending on the implementation, which makes the interface rather useless, as implementation changes would change the way the interface is used.

I'm suspecting my view on the problem is too narrow, and think I could rework this. I would appreciate some feedback. What I thought of so far:

  1. Make the two interfaces into one MeasureDistanceAndPoint which would return both in one go. This feels appropriate as they are clearly related, but a bit weird since the values are used independently in the application, as mentioned.
  2. Define arguments for IDistanceMeasurement so that it would require a point as input. Then the IPointFinder could trigger the external distance measurement silently and calculate the point when external distance measurement is used.
  3. Rethink this altogether, which is probably the way to go, but I spent two full days on it and am getting a bit crazy.
Mattias
  • 173
  • 4
  • 1
    have you considered using [template method](https://softwareengineering.stackexchange.com/a/271458/31260)? – gnat Oct 05 '20 at 15:33
  • 2
    "since the values are used independently in the application" - this is, perhaps, not a concern; this is something that happens on the application boundary, where you don't have to be aware of the details of what the application does with these values internally. Just have something pass each value to the appropriate part of the application. That said, I would still consider option 3. Try to keep it simple, and minimal; if you introduce an interface and it behaviorally doesn't fit, maybe the two component's don't actually "do the same thing". That's OK; sometimes you have to try it to see it. – Filip Milovanović Oct 05 '20 at 20:41
  • 1
    What's the story you are trying to tell here? When abstracting logic look at the problem from the perspective of the solution you want to find. When you read your code, does it make sense? What's difficult to gather from the question is the "so what". In other words, Is the point to get a distance measurement? – Berin Loritsch Oct 06 '20 at 01:06
  • 1
    If you have a temporal dependency you can enforce it in one of three ways. 1) Getting the implementation is a result of having called the prior dependency. 2) The implementation performs the bootstrapping transparently as necessary. 3) The interface exposes the fact that the system can presently be unaware, the implementation can choose the real value, or signal the fact it isn't ready. – Kain0_0 Oct 06 '20 at 02:06
  • Thank you for your comments, I will consider each of them. Additional information is that this is not the main functionality of the device. The main thing is configuring and supplying images for processing, which are triggered by an electrical component that is equal for both systems. The interface works well for all parts but the distance/point thing I described. I suppose that fact tricked me into a specific way of thinking. – Mattias Oct 06 '20 at 06:54
  • I'm starting to believe I mixed up my levels of abstraction, and this is possibly what Filip tells me above. As the devices have the same or similar capabilities at some low level (for example both can tell the electrical component to trigger images, adjust the same properties etc), maybe I need one level in between that actually controls the behaviour of the different devices, which then can be used by the application regardless of which hardware is used. Maybe the outcome of that is that the lower level interface is in fact not useful. – Mattias Oct 06 '20 at 13:17

1 Answers1

1

Constructors are your friend. They don't use inheritance so you can have extra dependencies inserted into one class without affecting another.

To simplify your case lets have the two devices as:

DeviceA
    GetDistance(spot)
    GetSpot()

DeviceB
    GetDistance()
    GetSpot(distance)

To make a single interface we need to remove the parameters from the methods

IDevice
    GetDistance()
    GetSpot()

We can then add a constructor with the required dependency

DeviceA :IDevice
    Constructor(spot)
    GetDistance()
    GetSpot()

DeviceB :IDevice
    Constructor(distance)
    GetDistance()
    GetSpot()

Now you have compile time checking of your dependency order, and the order is different for each device.

It also helps you to decide what the object should be called, a Device that you can only use once per spot or distance measure doesn't sound like a device, perhaps its called Finder or something.

Also, having both methods on a single class is now problematic. You could add some static methods but a neater method would be to separate them (also helping with the naming

DistanceMeasurerA :IDistanceMeasurer
    Constructor(spot)
    GetDistance()

DistanceMeasurerB :IDistanceMeasurer
    Constructor()
    GetDistance()

Now we can have your IDevice composite class with internal spot and distance finders which will need to be created

DeviceA(DistanceMeasurerAFactory ,SpotFinderA)
    GetDistance()
    {
       spot = this.spotFinder.findSpot();
       dist = DistanceMeasurerAFactory.Create(spot).GetDistance();
       return dist;
    }

The classes enforce the order of the two calls at compile time, everything has shared interfaces and your naming and class division is solved!

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • Thank you for the elaborate answer. While I agree with the concepts you describe, I think I left out some crucial information. For example, the GetSpot() in the first DeviceA needs an image as parameter. As I thought of this as a hardware abstraction, the image handling is taken care of by the class that uses IDevice. As it then would have to send the image back to the IDevice I'm starting to believe that I'm mixing levels of abstraction. Maybe the class that uses IDevice should be an abstraction of its own, and use different implementations for different Spot/Distance-behaviours. – Mattias Oct 07 '20 at 09:22
  • 1
    ive tried to keep the answer fairly generic. the idea is you can stick the dependency, whatever it is, in a constructor. This keeps the method call parameters generic – Ewan Oct 07 '20 at 09:50