设计模式(六)之里氏替换原则
里氏替换原则,为继承定义规范。
里氏替换原则有如下特点:
代码共享,减少创建类的工作量
提高代码的重用性
提高代码的可扩展性
提高产品代码的开放性
继承侵入性 只要继承,必须拥有父类的内容
降低代码的灵活性,子类必须拥有父类的属性和方法
增强耦合性。
里氏替换原则:
有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
举个例子:
我这里有一个长方形类,一个正方形类,正方形是一个特殊的长方形。那么正方形类继承自长方形类:
代码如下所示:
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
/// <summary>
/// 高层模块:调用长方形及正方形类
/// </summary>
class Program
{
static void Main(string[] args)
{
Rectangle rectangle = new Rectangle();
rectangle.SetHeight(10);
rectangle.SetWidth(20);
Console.WriteLine(rectangle.GetHeight());
Console.WriteLine(rectangle.GetWidth());
Console.WriteLine("------------ 我是分割线 -------------");
Square square = new Square();
square.SetHeight(10);
square.SetWidth(20);
Console.WriteLine(square.GetHeight());
Console.WriteLine(square.GetWidth());
Console.ReadKey();
}
}
}
长方形类:Rectangle.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
/// <summary>
/// 长方形类
/// </summary>
public class Rectangle
{
public double height;
public double width;
public void SetHeight(double height)
{
this.height = height;
}
public double GetHeight()
{
return height;
}
public void SetWidth(double width)
{
this.width = width;
}
public double GetWidth()
{
return width;
}
}
}
正方形类:Square.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
/// <summary>
/// 正方形类
/// </summary>
public class Square: Rectangle
{
public void SetHeight(double height)
{
this.height = height;
this.width = height;
}
public double GetHeight()
{
return height;
}
public void SetWidth(double width)
{
this.height = width;
this.width = width;
}
public double GetWidth()
{
return width;
}
/// <summary>
/// 求面积
/// </summary>
public double GetArea()
{
return width * height;
}
}
}
输出结果如下图所示:

输出的结果明显是不对的,我调用父类与子类的统一方法输出的结果应该是一致的。所以上面的栗子不符合里氏替换原则。
里氏替换原则:
只要有父类出现的地方,都可以用子类来替代,而且不会出现任何错误和异常。但是反过来则不行,有子类出现的地方,不能用其父类替代。
包含以下四种约束:
1:子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
有时候父类有多个子类,但在这些子类中有一个特例。要想满足里氏替换原则,又想满足这个子类的功能时,有的伙伴可能会修改父类的方法。但是,修改了父类的方法又会对其他的子类造成影响,产生更多的错误。这是怎么办呢?我们可以为这个特例创建一个新的父类,这个新的父类拥有原父类的部分功能,又有不同的功能。这样既满足了里氏替换原则,又满足了这个特例的需求。
2:子类中可以增加自己特有的方法。
3:当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。(子类的参数范围要比父类的参数范围大)
4:当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
我上面的那段程序很显然是不符合里氏替换原则的,那么上边我们那段程序该怎么修改呢,我们创建一个接口,接口中包含获取宽度及高度的两个方法,长方形类和正方形类分别实现这个接口,那么其二者现在就为同级,父类可以的地方,他们二者都可以。下边是我修改之后的代码:
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
/// <summary>
/// 高层模块:调用长方形及正方形类
/// </summary>
class Program
{
static void Main(string[] args)
{
Rectangle rectangle = new Rectangle();
rectangle.SetHeight(10);
rectangle.SetWidth(20);
Console.WriteLine(rectangle.GetHeight());
Console.WriteLine(rectangle.GetWidth());
Console.WriteLine("------------ 我是分割线 -------------");
Square square = new Square();
square.SetHeight(10);
square.SetWidth(20);
Console.WriteLine(square.GetHeight());
Console.WriteLine(square.GetWidth());
Console.ReadKey();
}
}
}
Rectangle.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
/// <summary>
/// 长方形类
/// </summary>
public class Rectangle: Interface1
{
public double height;
public double width;
public void SetHeight(double height)
{
this.height = height;
}
public double GetHeight()
{
return height;
}
public void SetWidth(double width)
{
this.width = width;
}
public double GetWidth()
{
return width;
}
}
}
Square.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
/// <summary>
/// 正方形类
/// </summary>
public class Square: Interface1
{
public double height;
public double width;
public void SetHeight(double height)
{
this.height = height;
this.width = height;
}
public double GetHeight()
{
return height;
}
public void SetWidth(double width)
{
this.height = width;
this.width = width;
}
public double GetWidth()
{
return width;
}
/// <summary>
/// 求面积
/// </summary>
public double GetArea()
{
return width * height;
}
}
}
父类接口:Interface1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Displace
{
interface Interface1
{
double GetHeight();
double GetWidth();
}
}
这个程序基本上就实现了上边所说的四个约束,例子有点烂,但是基本上融汇了里氏替换原则的思想。
说到底,里氏替换原则就是对继承的约束~
欢迎访问个人博客
https://guanchao.site