假设我有一些如下所示的模型代码,在中等规模的现有系统中有相当多的引用:
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; }
}
我希望我上面的困惑是有道理的。 当系统的某些部分需要额外的属性时,我试图找出扩展与另一个类耦合的类的最佳方法。
我正在尝试找到一种方法来遵循模型的开放/封闭原则,以便我的模型对扩展(新属性)开放并对修改关闭。
就我个人而言,我会采用另一种方法 - 向窗口类添加一些“功能”集合。沿着这些思路的东西(未经测试,只是为了提供一个想法):
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;
这将允许根据需要拥有尽可能多的功能(并在不修改现有类型的情况下扩展它们的数量),在需要时查询它们,并且您不需要为所有相关的功能组合创建单独的类。
有一个常见的误解,认为像
Window
这样的类是面向对象的。事实并非如此。通常,面向对象的设计意味着具有行为的数据,而Window
类没有行为。
开放/封闭原则(OCP)的部分描述是类是
开放扩展。这意味着模块的“行为”可以扩展。随着应用程序需求的变化,我们可以使用满足这些变化的新行为来扩展模块。换句话说,我们能够改变模块做什么。 -
APPP请注意,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 的答案。