Code contracts and inheritance (condition for overridden method)

Currently, code contracts do not allow preconditions for members in derived classes where the member already has a precondition set in the base class (in fact, I actually get a warning, not an error). I do not understand the logic of this. I understand that this is due to the Liskov replacement rule, which states that a derived class should always be used in those places where a parent is expected. Of course, “used” means work as expected. This seems convenient for interfaces, since various types of interface implementations do not add state and, therefore, can agree on a contract exactly. However, when you inherit the base class, you do this to add state and special functions, and most often the main method will have additional requirements.Why can't preconditions be combined together as mail conditions and object invariants?

Take a look below:

class Speaker
{
    public bool IsPlugged { get; set; }
    protected virtual void Beep()
    {
        Contract.Requires(IsPlugged);
        Console.WriteLine("Beep");
    }
}

class WirelessSpeaker : Speaker
{
    public bool TransmitterIsOn { get; set; }
    protected override void Beep()
    {
        Contract.Requires(TransmitterIsOn);
        base.Beep();
    }
}

You can argue that this class hierarchy breaks the Liskov rule because the wireless speaker may not beep when passed to methods that expect a Speaker. But don't we use code contracts? to make sure the requirements are met?

+4
source share
3 answers

Code contracts are not concluded in compliance with requirements, but in their communication. Subscribers Speaker.Beepare bound by a contract that takes effect in some cases.

WirelessSpeaker Speaker - , . Speaker , . WirelessSpeaker, Speaker .

:

WirelessSpeaker , Beep. , , , , 100% Speaker s.

, , . , ( ).

, , "" , .

+8

@BryanWatts . , , . - . , , / .

, (LSP). LSP , interface .

, . , , . Speaker WirelessSpeaker , .

public class Speaker
{
    public bool IsPlugged { get; set; }

    public virtual void Beep()
    {
        if (!IsPlugged)
        {
            throw
            new InvalidOperationException("Speaker is not plugged in!");
        }

        Console.WriteLine("Beep.");
    }
}

public class WirelessSpeaker : Speaker
{
    public bool TransmitterIsOn { get; set }

    public override void Beep()
    {
        if (!TransmitterIsOn)
        {
            throw
            new InvalidOperationException("Wireless Speaker transmitter is not on!");
        }

        Console.WriteLine("Beep.");
    }
}

public class IBeepSpeakers
{
    private readonly Speaker _speaker;

    public IBeepSpeakers(Speaker speaker)
    {
        Contract.Requires(speaker != null);
        Contract.Ensures(_speaker != null && _speaker == speaker);
        _speaker = speaker;

        // Since we know we act on speakers, and since we know
        // a speaker needs to be plugged in to beep it, make sure
        // the speaker is plugged in.
        _speaker.IsPlugged = true;
    }

    public void BeepTheSpeaker()
    {
        _speaker.Beep();
    }
}

public static class MySpeakerConsoleApp
{
    public static void Main(string[] args)
    {
        BeepWiredSpeaker();

        try
        {
            BeepWirelessSpeaker_Version1();
        }
        catch (InvalidOperationException e)
        {
            Console.WriteLine($"ERROR: e.Message");
        }

        BeepWirelessSpeaker_Version2();
    }

    // We pass in an actual speaker object.
    // This method works as expected.
    public static BeepWiredSpeaker()
    {
        Speaker s = new Speaker();
        IBeepSpeakers wiredSpeakerBeeper = new IBeepSpeakers(s);
        wiredSpeakerBeeper.BeepTheSpeaker();
    }

    public static BeepWirelessSpeaker_Version1()
    {
        // This is a valid assignment.
        Speaker s = new WirelessSpeaker();

        IBeepSpeakers wirelessSpeakerBeeper = new IBeepSpeakers(s);

        // This call will fail!
        // In WirelessSpeaker, we _OVERRODE_ the Beep method to check
        // that TransmitterIsOn is true. But, IBeepSpeakers doesn't
        // know anything _specifically_ about WirelessSpeaker speakers,
        // so it can't set this property!
        // Therefore, an InvalidOperationException will be  thrown.
        wirelessSpeakerBeeper.BeepTheSpeaker();
    }

    public static BeepWirelessSpeaker_Version2()
    {
        Speaker s = new WirelessSpeaker();
        // I'm using a cast, to show here that IBeepSpeakers is really
        // operating on a Speaker object. But, this is one way we can
        // make IBeepSpeakers work, even though it thinks it dealing
        // only with Speaker objects.
        //
        // Since we set TransmitterIsOn to true, the overridden
        // Beep method will now execute correctly.
        //
        // But, it should be clear that IBeepSpeakers cannot act on both
        // Speakers and WirelessSpeakers in _exactly_ the same way and
        // have confidence that an exception will not be thrown.
        ((WirelessSpeaker)s).TransmitterIsOn = true;

        IBeepSpeakers wirelessSpeakerBeeper = new IBeepSpeaker(s);

        // Beep the speaker. This will work because TransmitterIsOn is true.
        wirelessSpeakerBeeper.BeepTheSpeaker();
}

(LSP). Agile Principles, Patterns and Practices # . 142-143

LSP , OOD IS-A , .... [W], , . , , , , . , . , [class].

TransmitterIsOn == true Beep WirelessSpeaker, , Speaker. WirelessSpeaker s IsPlugged TransmitterIsOn true, Beep , ( Speaker), a Speaker in TransmitterIsOn.

, SOLID, (ISP):

, .

a WirelessSpeaker . (, , .) WirelessSpeaker , IsPlugged, , Speaker, ! , , . , , , .

, LSP, ISP , , , Open/Closed Principle (OCP):

(, , ..) , .

, , Code Contracts . , ( ) , -, , .

, , : , . , , ; . , WirelessSpeaker?

, WirelessSpeaker Speaker. , WirelessSpeaker IsPlugged. , Speaker, , WirelessSpeaker , IsPlugged, true. " !" : " Beep, WirelessSpeaker ". , . ! WirelessSpeaker Beep, ! (, .) "", WirelessSpeaker; , , , .

, ? , , , . ​​ ?

  • - .
    • , , . , .
    • (, ), - .
    • , , . .
  • .
    • , , .
    • , , , ( , " " ), .

, - . , :

// NOTE: I would prefer to simply call this Speaker, and call
// Speaker 'WiredSpeaker' instead--but to leave your concrete class
// names as they were in your original code, I've chosen to call this
// SpeakerBase.
public abstract class SpeakerBase
{
    protected SpeakerBase() { }

    public void Beep()
    {
        if (CanBeep())
        {
            Console.WriteLine("Beep.");
        }
    }

    public abstract bool CanBeep();
}

! , . , CanBeep() true. , , , . , , SpeakerBase, , CanBeep() true. LSP! , a SpeakerBase , Speaker, WirelessSpeaker, : , .

, , - SpeakerBase:

public class Speaker : SpeakerBase
{
    public bool IsPlugged { get; set; }

    public override bool CanBeep() => IsPlugged;
}

public class WirelessSpeaker : SpeakerBase
{
    public bool IsTransmiterOn { get; set; }

    public override bool CanBeep() => IsTransmitterOn;
}

, Speaker, , . WirelessSpeaker, , . , WirelessSpeaker , "". .

, (DIP):

  • . .
  • . .

, Speaker WirelessSpeaker, SpeakerBase. , , , SpeakerBase, , , , . , IBeepSpeakers , , , , IBeepSpeakers . , IBeepSpeakers. ( , SpeakerBase Beep(), SpeakerBase , , . )

public class IBeepSpeakers
{
    private readonly SpeakerBase _speaker;
    private readonly Action<SpeakerBase> _enableBeeping;

    public IBeepSpeakers(SpeakerBase speaker, Action<SpeakerBase> enableBeeping)
    {
        Contract.Requires(speaker != null);
        Contract.Requires(enableBeeping != null);
        Contract.Ensures(
            _speaker != null && 
            _speaker == speaker);
        Contract.Ensures(
            _enableBeeping != null && 
            _enableBeeping == enableBeeping);

        _speaker = speaker;
        _enableBeeping = enableBeeping;
    }

    public void BeepTheSpeaker()
    {
        if (!_speaker.CanBeep())
        {
           _enableBeeping(_speaker);
        }
        _speaker.Beep();
    }
}

public static class MySpeakerConsoleApp
{
    public static void Main(string[] args)
    {
        BeepWiredSpeaker();

        // No more try...catch needed. This can't possibly fail!
        BeepWirelessSpeaker();
    }

    public static BeepWiredSpeaker()
    {
        Speaker s = new Speaker();
        IBeepSpeakers wiredSpeakerBeeper =
            new IBeepSpeakers(s, s => ((Speaker)s).IsPlugged = true);
        wiredSpeakerBeeper.BeepTheSpeaker();
    }

    public static BeepWirelessSpeaker()
    {
        WirelessSpeaker w = new WirelessSpeaker();
        IBeepSpeakers wirelessSpeakerBeeper =
            new IBeepSpeakers(w, s => ((WiredSpeaker)s).IsTransmitterOn = true);
        wirelessSpeakerBeeper.BeepTheSpeaker();
    }
}

, , , . , , .

+2

, , , CanBeep , WirelessSpeaker TransmitterIsOn. , , , .

, , , . , ? , . , , : , , , ? , , .

+1

All Articles