【问题标题】:Liskov Substitution Principle - Common InterfaceLiskov 替换原则 - 通用接口
【发布时间】:2018-04-25 17:58:21
【问题描述】:

我一直在试图弄清楚 Liskov 替换原则和接口隔离原则,我对下面的例子有点困惑。

假设我们有一个名为Vehicle 的基类,它有几个属性和一个在IVehicle 类中实现的接口IVehicle

我们有两个子类,CarMotorcycleCar 继承自 Vehicle 并实现 IVehicle 接口。 Motorcycle 继承自 Vehicle 并实现了 IVehicle,但 Motorcycle 有一个额外的属性,该属性也在 IMotorcycle 类中实现的新接口中添加。Motorcycle 类。

让我用代码写下来澄清一下:

public interface IVehicle
{
    string Brand { get; set; }
    string Model { get; set; }
    int HorsePower { get; set; }
}

public interface IMotorcycle
{
    DateTime DateCreated { get; set; }
}

public abstract class Vehicle : IVehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public int HorsePower { get; set; }
}

public class Car : Vehicle, IVehicle
{ }

public class Motorcycle : Vehicle, IVehicle, IMotorcycle
{
    public DateTime DateCreated { get; set; }
}

public class Start
{
    public IVehicle DoSomeStuff()
    {
        //Does some stuff
        //Based on logic we either return
        //a new Car or Motorcycle
        //but if I return a motorcycle how would I be able to 
        //access the DateCreated attribute since I'm returning IVehicle
        //I guess I have to cast it but is it a good practice to do that
        //or am I setting up everything incorrect?

        return new Motorcycle();
    }
}

我的问题:如果我们有一个类说Start,它有一个返回IVehiclepublic IVehicle DoSomeStuff())的方法。根据逻辑,我们将返回一个新的CarMotorcycle。如果我们返回一个新的Car,我们将能够访问所有属性,因为它只实现了IVehicle 接口,但是假设我们返回一个新的Motorcycle,如何在不强制转换的情况下访问.DateCreated 属性它,

有没有办法更好地实现这一点,而不是有一个共同的交互,还是我错过了什么?

【问题讨论】:

  • “如何在不强制转换的情况下访问 DateCreated 属性”——你必须强制转换它。你可以说if (rtnObj is IMotorcycle) { Console.WriteLine((rtnObj as IMotorcycle).DateCreated); }(C#7 中有一个更简单的习语)。这就是接口的用途:使用is 或其他等效项来确定给定对象是否支持给定接口,如果支持,则进行转换。
  • 一般的想法应该是调用者只关心车辆,而不是车辆的具体类型。如果他们需要了解更多信息,则可以选择使用多种方法来返回更具体的类型。或某种类型的通用方法。或者移动任何需要知道类内部类型的逻辑。
  • 你的例子很糟糕。首先,因为你使用了一个人为的例子DateCreated,所以不清楚为什么这个属性不能适用于所有IVehicles。因为如果确实如此,那么您可以简单地将属性移动到IVehicles。问题解决了。相反,如果您使用了NumberOfWheels,那么显然该属性属于IVehicle,并且可以在这两种情况下实现。也消除了问题。
  • 然而,如果你想强制一些“不适合”的东西,那么你也不应该期望那些适合的东西和不适合的东西有多态性。您实际上是在寻求将“胖接口”强制转换为与 ISP 相反的多态行为的方法。

标签: c# oop interface design-principles


【解决方案1】:

如果你想遵循 LSP,如果你有一个接受 IVehicle 参数的方法,你用汽车或摩托车调用它应该没有关系。如果您需要以任何方式投射或检查它是否是摩托车,那么您的界面设计不正确。

【讨论】:

    【解决方案2】:

    这个例子背后的想法有几件事很突出。 IMotorcycle 不是 IVehicle,这在此示例中没有意义,因为假设没有任何类不会继承 IMotorcycleIVehicle 的函数。因此,将IMotorcycle 更改为从IVehicle 继承会使事情变得更合乎逻辑。接下来,您需要了解 LSP 和 ISP 在与其他 SOLID 原则一起工作时才能工作。该示例不满足单一责任和开闭原则。

    Start 在添加逻辑后不应修改。因此,如果您添加了一个实现IVehicle 和其他一些类/接口(不继承自IVehicle)的新类,那么必须更改逻辑,这意味着它不会被修改。 Start 类中应该有覆盖或重载的方法,以便您可以使用方法转发来处理不同的类型。

    但也存在单一职责不适用于班级的问题。如果您想处理与车辆的一般交互,则应该传入扩展 IVehicle 的对象,但是如果您正在创建/构建 IVehicle 的实例,则应该应用工厂/构建器模式。无论哪种方式,Start 类都应该创建、管理或与 IVehicle 对象交互,而不是创建、管理对象并与对象交互。无论在 Start 类上调用的函数都应该实现 SOLID,这意味着它只需要一个 IMotorcycleIVehicle 对象(IMotorcycle 继承自 IVehicle。)这样函数就具有单一功能,确实添加更多类后不需要更新。

    还有更多内容,但我觉得我已经提供了很多信息。您应该考虑的主要事情是您需要让 SOLID 原则协同工作,而不是孤立地工作。如果您想在从返回更高级别接口的方法中获取某个对象的类型特定函数后使用它,那么您需要使更高级别接口具有在实现时将调用特定类型函数的函数。在你的情况下,你可以有这样的东西:

    public interface IVehicle
    {
        string Brand { get; set; }
        string Model { get; set; }
        int HorsePower { get; set; }
        void setInformation;
        InformObject getInformation;
    
    }
    
    public interface IMotorcycle : IVehicle
    {
        DateTime DateCreated { get; set; }
    }
    
    public abstract class Vehicle : IVehicle
    {
        public string Brand { get; set; }
        public string Model { get; set; }
        public int HorsePower { get; set; }
        public void setInformation(string brand, string model, int hPower){
           Brand = brand;
           Model = model;
           HorsePower = hPower;
        }
        public InfoObject getInformation(){
           return new InfoObject(Brand, Model, HorsePower);
        }
    
    }
    
    public class Car : Vehicle, IVehicle
    { }
    
    public class Motorcycle : Vehicle, IVehicle, IMotorcycle
    {
        public DateTime DateCreated { get; set; }
    }
    
    public class InfoObject 
    {
        public InfoObject(string brand, string model, int hPower){
           Brand = brand;
           Model = model;
           HorsePower = hPower;
        }
    
        public InfoObject(string brand, string model, int hPower, DateTime timeCreated){
           Brand = brand;
           Model = model;
           HorsePower = hPower;
           DateCreated = timeCreated;
        }
    
        public string Brand { get; set; }
        public string Model { get; set; }
        public int HorsePower { get; set; }
        DateTime DateCreated { get; set; }
    }
    

    然后,您会将信息作为一个对象,它需要有限的信息量。这不应该反映层次结构IVehicle。它只是所有车辆都应该具有的对象,并且所有车辆都可以与之交互,并且其他类型特定代码可以与之交互以处理特定于该类型的信息。这不是一个很好的例子,它只是用作一般性的看看如何改进你的界面,因为如果你打算在你的系统中使用IVehicle,需要做更多的工作来突出功能。 Getter 和 Setter 并不是为其创建接口的最佳方法。

    【讨论】:

      猜你喜欢
      • 2019-10-29
      • 2017-10-18
      • 2010-12-03
      • 2012-03-16
      • 2016-08-20
      • 1970-01-01
      • 1970-01-01
      • 2014-06-05
      相关资源
      最近更新 更多