适合这个问题的设计/设计模式?

Jas*_*son 4 .net c# inheritance design-patterns

我以前发过这个,但我觉得它太冗长和无关紧要了.我的问题是也喜欢这样.第二个链接中的一张海报说答案(为什么你不能做下面的代码)是一个设计问题,特别是"继承的坏用法".所以我想再次与StackOverflow的专家一起检查这个问题,看看这是否真的是"糟糕的继承"问题 - 但更重要的是,如何修复设计.

就像海报一样,我也对Factory方法以及如何应用它感到困惑.似乎工厂方法适用于具有与抽象基类完全相同的实现的多个具体类,并且不添加它们自己的属性.但是,正如您将在下面看到的,我的具体类构建在抽象基类之上并添加额外的属性.

我们建立的基类:

public abstract class FlatScreenTV
{
     public string Size { get; set; }
     public string ScreenType { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

扩展类示例:

public class PhillipsFlatScreenTV : FlatScreenTV
{
     // Specific to Phillips TVs. Controls the backlight intensity of the LCD screen.
     public double BackLightIntensity { get; set; }
}

public class SamsungFlatScreenTV : FlatScreenTV
{
     // Specific to Samsung TVs. Controls the time until the TV automatically turns off.
     public int AutoShutdownTime { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

假设有更多品牌的平板电视有更多扩展类.然后,假设我们将它们全部放入通用列表中:

public static void Main()
{
     List<FlatScreenTV> tvList = new List<FlatScreenTV>();

     tvList.Add(new PhillipsFlatScreenTV());
     tvList.Add(new SamsungFlatScreenTV());
     tvList.Add(new SharpFlatScreenTV());
     tvList.Add(new VizioFlatScreenTV());

     FlatScreenTV tv = tvList[9]; // Randomly get one TV out of our huge list
}
Run Code Online (Sandbox Code Playgroud)

问题:

我想访问此变量所属的"原始"品牌电视的特定属性.我知道这个品牌,因为如果我打电话tv.GetType(),它会返回正确的"原始"类型 - 而不是 FlatScreenTV.但是我需要能够tvFlatScreenTV回到其原始类型,以便能够访问每个品牌的平板电视的特定属性.

问题1:如何正确地动态投射 - 没有临时黑客和巨大的if-else链条来粗暴猜测"原始"类型?

浏览类似的设计问题后,大多数答案是:你不能.有人说要查看工厂模式,其他人说要使用接口修改设计,但我不知道如何使用它们来解决这个问题.

问题2:那么,我应该如何设计这些类,以便我可以在上面的上下文中访问原始类型的特定属性?

问题3:这是非常糟糕的继承吗?

Mar*_*ath 5

您的设计违反了" Liskov替代原则 ".换句话说,处理FlatScreenTV列表中的项目的代码不应该知道或关心派生类型是什么.

假设您的代码需要创建自定义远程控制GUI.仅仅知道每个电视的属性的名称和类型以自动生成UI可能就足够了.在这种情况下,您可以执行以下操作以从基类公开自定义属性:

public abstract class FlatScreenTV
{
    public FlatScreenTV()
    {
        CustomProperties = new Dictionary<string,object>();
    }

    public Dictionary<string,object> CustomProperties { get; private set; }
    public string Size { get; set; }
    public string ScreenType { get; set; }
}

public class PhillipsFlatScreenTV : FlatScreenTV
{
    public PhillipsFlatScreenTV()
    {
        BackLightIntensity = 0;
    }

    // Specific to Phillips TVs. Controls the backlight intensity of the LCD screen.
    public double BackLightIntensity 
    { 
        get { return (double)CustomProperties["BackLightIntensity"]; }
        set { CustomProperties["BackLightIntensity"] = value; }
    }
}

public class SamsungFlatScreenTV : FlatScreenTV
{
    public SamsungFlatScreenTV()
    {
        AutoShutdownTime = 0;
    }

    // Specific to Samsung TVs. Controls the time until the TV automatically turns off.
    public int AutoShutdownTime 
    {
        get { return (int)CustomProperties["AutoShutdownTime"]; }
        set { CustomProperties["AutoShutdownTime"] = value; }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您确实需要直接使用派生类型,那么您应该考虑转向基于插件的体系结构.例如,您可能有这样的工厂方法:

IRemoteControlGUI GetRemoteControlGUIFor(FlatScreenTV tv)
Run Code Online (Sandbox Code Playgroud)

这将扫描你的插件并找到知道如何为你传入的特定类型的FlatScreenTV构建UI的那个.这意味着对于你添加的每个新的FlatScreenTV,你还需要创建一个知道如何制作它的遥控器的插件控制GUI.