Liskov替换原则 - 通用接口

时间:2018-04-25 17:58:21

标签: c# oop interface design-principles

我一直试图找出Liskov替换原则和接口隔离原理,我对以下示例有点困惑。

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

我们有两个子课程,Car& MotorcycleCar继承自Vehicle并实现IVehicle接口。 Motorcycle继承自Vehicle并实现IVehicle,但Motorcycle还有一个额外的属性,该属性也会添加到IMotorcycle中,该接口在{Motorcycle中实现。 1}} class。

让我通过在代码中写下来澄清它:

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属性没有强制转换,

有没有办法更好地实现这一点而不是有一个共同的interace或者我错过了什么?

2 个答案:

答案 0 :(得分:3)

如果您想跟踪LSP,如果您有一个接受IVehicle参数的方法,那么无论您是用汽车还是摩托车来调用它都无关紧要。如果你需要以任何方式投射或检查它是否是摩托车,你没有正确设计你的界面。

答案 1 :(得分:0)

这个例子背后的想法突出了一些东西。 IMotorcycle不是IVehicle,在此示例中没有意义,因为假设没有类不会继承IMotorcycle和{{IVehicle中的函数1}}。因此,将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。它只是所有车辆应该具有的对象,并且所有车辆都可以与之交互,并且其他类型特定代码可以与之交互以处理特定于该类型的信息。它不是一个很好的例子,它只是被用来作为一般看看你如何可能改进你的界面,因为如果你打算使用{{1,需要做更多工作来强调功能通过你的系统。 Getter和Setters并不是最好的想法。