Liskov Substitution principle

Let's remember this definition: subtypes must be substitutable for their base types. This means that this should happen without breaking the execution or losing any other kind of functionality.

You'll notice that this idea lies behind the basic principles of inheritance in the OOP programming paradigm.

If you have a method that requires an argument of the Person type (let's put it that way), you can pass an instance of another class (Employee, Provider, and so on) as long as these instances inherit from Person.

This is one of the main advantages of well-designed OOP languages, and the most popular and accepted languages support this characteristic.

Back to the code again

Let's take a look at the support inside our sample, where a new requisite arises. Actually, our demo simply calls the subscribers of Mercedes cars and notifies them that a SpeedLimit event took place.

However, what if we need to know the moment in time in which that circumstance happened and the resulting speed that we tried to obtain? That is, what if we need more information about the event?

In the current state, the SpeedLimit event does not pass any information to the caller beyond the sender (which refers to the origin of such call). But we can use the implementation of the Liskov Substitution principle inherent to the C# language in order to pass a derived class of EventArgs containing the required information, and the context should manage it just as well.

So, the first step is to inherit from EventArgs and create a new class capable of holding the solicited information:

public class SpeedLimitData : EventArgs
{
  public DateTime moment { get; set; }
  public int resultingSpeed { get; set; }
}

And we need to change the event invocation so that it recovers the necessary information before calling the event. In this way, the new version of Accelerate—which is still totally compatible with the previous one—will be as follows:

public virtual bool Accelerate(bool advise)
{
  bool speedExceeded = Speed + SpeedIncr > MaxSpeed;
  Speed = (speedExceeded) ? Speed : Speed + SpeedIncr;
  if (speedExceeded && advise && (SpeedLimit!= null))
  {
    SpeedLimitData data = newSpeedLimitData()
    {
      moment = DateTime.Now,
      resultingSpeed = Speed + SpeedIncr
    };
    SpeedLimit(this, data);
  }
  return speedExceeded;
}

So, when we invoke SpeedLimit, we are sending business logic information to any subscriber, either from the UI or any other. So, we can pass a derived instance of the EventArgs class to the event without provoking any complain in the UI's editor (or the compiler).

The final step is to change the user interface to recover the data passed to it and present it in a modified version of the previous MessageBox call:

private void TheCar_SpeedLimit(object sender, EventArgs e)
{
  var eventData = e as SpeedLimitData;
  MessageBox.Show("Attempt to obtain " + eventData.resultingSpeed +
  " Miles//hr at: " + eventData.moment.ToLongTimeString(), "Warning",
  MessageBoxButtons.OK, MessageBoxIcon.Warning);
}

This time, when we select a Mercedes car and try to surpass the limit, we get a much more informative report in MessageBox:

Back to the code again

Thanks to the Liskov Substitution principle support, we were able to add behavior and information with minimum effort, knowing that the UI receiving the information would perform a simple casting to convert the basic EventArgs declaration into the extended SpeedLimitData event that we really passed to the event handler.

Other implementations of LSP in .NET (Generics)

This is not the only implementation of the LSP principle that we find inside .NET, since different areas of the framework have grown using this conception. For instance, generics are one of the benefits of LSP.

In our sample, we can create a generic version of the event in order to manage extra information very easily. Imagine that besides the private measures taken in the case of Mercedes, all the brands now want to support messaging when the legal speed limit is reached.

This affects any instance of SpeedCar. It's not mandatory (it doesn't force you to stop increasing the speed, but it shows you another warning about this condition).

Since it has an impact on all brands, we can add a new event to the SpeedCar class, only this time, we define it as generic in order to support the extra information:

public eventEventHandler<int> LegalLimitCondition;

Let's assume that the value for Speed Legal Limit is the maximum allowed in some states of the US (80 mi/h). We'll define a new constant, MaxLegal, with this value:

const int MaxLegal = 80;

Now, to reflect this new condition, we should modify our Accelerate methods to include a previous call in case the car exceeds the legal value, indicating the amount exceeded:

public virtual bool Accelerate()
{
  bool speedExceeded = Speed + SpeedIncr > MaxSpeed;
  bool legalExceeded = Speed + SpeedIncr >MaxLegal;
  if (legalExceeded && LegalLimitCondition != null)
  {
    LegalLimitCondition(this, (Speed + SpeedIncr) - MaxLegal);
  }
  Speed = (speedExceeded) ? Speed: Speed + SpeedIncr;
  return speedExceeded;
}
public virtual bool Accelerate(bool advise)
{
  bool speedExceeded = Speed + SpeedIncr > MaxSpeed;
  bool legalExceeded = Speed + SpeedIncr > MaxLegal;
  if (legalExceeded && LegalLimitCondition != null)
  {
    LegalLimitCondition(this, (Speed + SpeedIncr) - MaxLegal);
  }
  if (speedExceeded && advise && (SpeedLimit!= null))
  {
    SpeedLimitData data = newSpeedLimitData()
    {
      moment = DateTime.Now,
      resultingSpeed = Speed + SpeedIncr
    };
    SpeedLimit(this, data);
  }
  Speed = (speedExceeded) ? Speed : Speed + SpeedIncr;
  return speedExceeded;
}

That's all the work you need to do with the SpeedCar class. The rest will be an update to the user interface; so, for any car, when the condition launches, another MessageBox call warns the user about the condition.

In this way, we now register every car for the LegalLimitCondition event and let the IDE generate the associated event handler for us:

theCar.LegalLimitCondition += TheCar_LegalLimitCondition;
private void TheCar_LegalLimitCondition(object sender, int e)
{
  updateUI(e);
}

This time, we pass the argument to a revised version of the UpdateUI method, which now admits an optional argument, indicating the speed excess:

private void updateUI(int speedExcess = 0)
{
  txtSpeed.Text = theCar.Speed.ToString();
  if (speedExcess > 0)
  {
    MessageBox.Show( "Legal limit exceeded by " + speedExcess + " mi/h");
  }
}

And that's it. Now, different event mechanisms inform the user interface about the business logic conditions via notifications with a custom event system.

Note that the sequence in calling events is important and the final assignment of the Speed value is performed at the end of the Accelerate method when all previous conditions have been processed.

Events are flexible enough as to be defined in a way that allows us to pass our own information via classic definitions, or—with the participation of generics—we can simply define a generic event handler that holds information of any kind. All these techniques foster the implementation of good practices, not just the SOLID principles.

Changes in the UI should not affect the SportClass definition; although its usage of the business logic differs, we keep the changes in the class to a minimum.

At runtime, we will now be warned about any excess in velocity over the MaxLegal constant previously established (refer to the screenshot):

Other implementations of LSP in .NET (Generics)

Let's review the other two principles remaining in the SOLID package now: Interface Segregation principle (ISP) and Dependency Inversion principle (DIP).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.206.25