设计开放扩展但封闭修改的模型

问题描述 投票:0回答:2

假设我有一些如下所示的模型代码,在中等规模的现有系统中有相当多的引用:

public class House
{
    public House(IEnumerable<Window> windows)
    {
        Windows = windows;
    }

    public IEnumerable<Window> Windows { get; private set; }


}

public class Window
{
    public Window(string brand, int height, int width)
    {
        Brand = brand;
        Height = height;
        Width = width;
    }

    public string Brand { get; private set; }
    public int Height { get; private set; }
    public int Width { get; private set; }
}

新的需求出现了。 系统的部分需要知道特定的窗户是否是耐阳光的,但是其他部分并不关心并且可能不知道窗户是否是耐阳光的。

我们可以扩展我们的窗户类别来表示窗户具有防晒功能的可能性

public class WindowMaybeSolarResistant : Window
{
    public WindowMaybeSolarResistant(string brand, int height, int width, bool isSolarResistant) : base(brand, height, width)
    {
        IsSolarResistant = isSolarResistant;
    }

    public bool IsSolarResistant { get; private set; }
}

我的问题是如何处理House? 该系统与房子有窗户的想法相当结合。

我们可以向窗口添加另一个构造函数

public class Window : IWindow
{
    public Window(string brand, int height, int width)
    {
        Brand = brand;
        Height = height;
        Width = width;
        IsSolarResistant = false;  //What if this is really true for the window though?  Could someone use the wrong constructor?
    }

    public Window(string brand, int height, int width, bool isSolarResistant)
    {
        Brand = brand;
        Height = height;
        Width = width;
        IsSolarResistant = isSolarResistant;
    }

    public string Brand { get; private set; }
    public int Height { get; private set; }
    public int Width { get; private set; }
    public bool IsSolarResistant { get; private set; }
}

房子可以用仿制药代替吗? 这保证了窗口具有某些属性,但留有扩展空间。

public interface IWindow
{ 
    public string Brand { get;  }
    public int Height { get;  }
    public int Width { get;  }
}


public class House<TWindow> where TWindow : IWindow
{
    public House(IEnumerable<TWindow> windows)
    {
        Windows = windows;
    }

    public IEnumerable<TWindow> Windows { get; private set; }

}

你扩建了House,但这似乎会失控?

public class HouseCouldHaveSolarWindows : House
{
    public HouseCouldHaveSolarWindows(IEnumerable<WindowMaybeSolarResistant> windows) : base(windows)
    {
    }

    public new IEnumerable<WindowMaybeSolarResistant> Windows { get; private set; }
}

我希望我上面的困惑是有道理的。 当系统的某些部分需要额外的属性时,我试图找出扩展与另一个类耦合的类的最佳方法。

我正在尝试找到一种方法来遵循模型的开放/封闭原则,以便我的模型对扩展(新属性)开放并对修改关闭。

c# oop solid-principles
2个回答
2
投票

就我个人而言,我会采用另一种方法 - 向窗口类添加一些“功能”集合。沿着这些思路的东西(未经测试,只是为了提供一个想法):

public interface IWindowFeature;

public class SolarResistantWindowFeature : IWindowFeature
{
    // some SolarResistantFeature props 
}

public class Window
{
    private readonly Dictionary<Type, object> Features = new();
    
    // ...

    public void SetFeature<T>(T feature) where T : class, IWindowFeature 
      => Features[typeof(T)] = feature;

    public T? GetFeature<T>() where T : class, IWindowFeature
      => Features.TryGetValue(typeof(T), out var v)
          ? v as T 
          : null;
}

然后在需要时设置/获取功能:

window.SetFeature(new SolarResistantWindowFeature());
var hasSolarResistance = window.GetFeature<SolarResistantWindowFeature>() is not null;

这将允许根据需要拥有尽可能多的功能(并在不修改现有类型的情况下扩展它们的数量),在需要时查询它们,并且您不需要为所有相关的功能组合创建单独的类。


0
投票

有一个常见的误解,认为像

Window
这样的类是面向对象的。事实并非如此。通常,面向对象的设计意味着具有行为的数据,而
Window
类没有行为。

开放/封闭原则(OCP)的部分描述是类是

开放扩展。这意味着模块的“行为”可以扩展。随着应用程序需求的变化,我们可以使用满足这些变化的新行为来扩展模块。换句话说,我们能够改变模块做什么 -

APPP

,罗伯特·C·马丁,Prentice Hall,2006 年,第 1 章9、我的重点

请注意,OCP 的描述重点是对象的行为方式,而不是它们的形状。 APPP 示例通过一个涉及在画布上绘制形状的示例进一步说明了这一点,其中解决方案是类可以重写的虚拟
Draw

方法。

只有当您知道应用程序代码(此处例如

House

)想要通过

Window
do 做什么时,您才能遵循或实施 OCP。我们在OP中看不到这一点,所以很难提出解决方案。

综上所述,最简单的方法可能是将
IsSolarResistant

添加到

Window
public class Window
{
    public Window(string brand, int height, int width)
        this(brand, height, width, false)
    {
    }

    public Window(string brand, int height, int width, bool isSolarResistant)
    {
        Brand = brand;
        Height = height;
        Width = width;
        IsSolarResistant = isSolarResistant;
    }

    public string Brand { get; private set; }
    public int Height { get; private set; }
    public int Width { get; private set; }
    public bool IsSolarResistant { get; private set; }
}

这至少是一个非破坏性的更改,但是您现在需要去修改 
House

才能使用新属性,因此您违反了 OCP。然而,据我所知,您需要对所有建议的解决方案执行此操作,包括

Guru Stron 的答案

© www.soinside.com 2019 - 2024. All rights reserved.