Question

I consider interfaces not only as a set of members, but also as a "contract" which force realisation to hold restrictions specified in interface documentation. For example:

interface IDevice
{
    bool IsActive { get; }

    int Address { get; }

    /// <summary>
    /// Raised when (IsActive == false)
    /// and device was activated
    /// </summary>
    event Action Activated;

    /// <summary>
    /// Raised when (IsActive == true)
    /// and device was deactivated
    /// </summary>
    event Action Deactivated;

    /// <summary>
    /// Raised when (IsActive == false)
    /// and Address was changed
    /// </summary>
    event Action<int> AddressChanged;
}

Also I have users which are not interested in activation/deactivation process but want to know when Address will change, so, lead by ISP, I create a new interface:

interface IAddressee
{
    int Address { get; }

    /// <summary>
    /// Raised when Address was changed
    /// </summary>
    event Action<int> AddressChanged;
}

And now IDevice looks like:

interface IDevice : IAddressee
{
    bool IsActive { get; }

    /// <summary>
    /// Raised when (IsActive == false)
    /// and device was activated
    /// </summary>
    event Action Activated;

    /// <summary>
    /// Raised when (IsActive == true)
    /// and device was deactivated
    /// </summary>
    event Action Deactivated;
}

As you see, IDevice's contract has loosed one condition: AddressChanged event should be raised only when device is not active (IsActive == false).

I cannot document it in IAddressee interface since it is not depend on IDevice and non-device implementations can exist.

Is this situation is normal? What would you do to force IDevice realisation to correct behaviour?

I am new to the concept of contracts, so please dispel my illusions and doubts

Was it helpful?

Solution

In such cases abstract conditions will do. They express something that may depend on information not available in the top-level class. The condition is later implemented in a way suitable for a specific descendant. In your example

interface IAddressee
{
    int Address { get; }

   /// <summary>
   /// Can Address be changed?
   /// </summary>
   bool IsAddessChangeable { get; };

    /// <summary>
    /// Raised when (IsAddessChangeable == true)
    /// and Address was changed
    /// </summary>
    event Action<int> AddressChanged;
}

In a class that implements IDevice, the query IsAddessChangeable will return IsActive == false, in other classes - the value depending on the required semantics.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top