上一篇着重介绍了开闭原则的概念,通过类图我们可以看出,如果不对Client和Server类进行解耦,当Client类需要使用另外一个Server类时,必须对相关代码进行修改.导致不必要的僵化性和脆弱性.下面将通过一个渐进的示例来展示如何运用开闭原则:
1.客户需要有一个在标准GUI上绘制圆应用程序.
Circle类
View Code
class Circle
{
private Guid _id;
public Circle() { _id = Guid.NewGuid(); }
public void Draw()
{
Console.WriteLine("ID: {0} 圆形绘制",_id.ToString("N"));
}
}
GUICommon类
View Code
static class GUICommon
{
private static List _circles = new List();
public static List Circles { get { return _circles; } }
public static void AddCircle(Circle c)
{
_circles.Add(c);
}
public static void AddCircles(List collection)
{
_circles.AddRange(collection);
}
public static void DrawAllSharp()
{
foreach (Circle c in Circles)
{
c.Draw();
}
}
}
我们可以在Main函数中向GUICommon中添加Circle,并且可以调用DrawAllSharp方法绘制Circle.
2.当某一天,客户说他需要程序也能够输出正方形,并且需要圆在正方形之前被绘制出.
至少我们可以想出两种办法来修改我们的程序,要么我们继续按照原来的方式新建一个Square类,在GUICommon中添加它的集合,并且在绘制时按顺序执行.要么我们对
Square类和Circle类进行抽象,来适应不断新增的变化.同时在GUICommon中也只对这个固定的抽象体进行操作,而不是对具体的类型.
抽象出的Sharp类型
View Code
abstract class Sharp
{
protected Guid _id;
public Sharp() { _id = Guid.NewGuid(); }
public abstract void Draw();
}
新的Square类型
View Code
class Square:Sharp
{
public override void Draw()
{
Console.WriteLine("ID: {0} 正方形绘制", _id.ToString("N"));
}
}
Circle类型同样继承自Sharp
View Code
class Circle:Sharp
{
public override void Draw()
{
Console.WriteLine("ID: {0} 圆形绘制",_id.ToString("N"));
}
}
GUICommon类
View Code
static class GUICommon
{
private static List _sharps = new List();
public static List Sharps { get { return _sharps; } }
public static void AddSharp(Sharp s)
{
_sharps.Add(s);
}
public static void AddSharps(List collection)
{
_sharps.AddRange(collection);
}
public static void DrawAllSharp()
{
foreach (Sharp s in Sharps)
{
s.Draw();
}
}
}
有上面的示例可以看出,即使将来增加新的类型.我们只需要增加一个Sharp类的新派生类,这样DrawAllSharp方法就符合开闭原则,无需改变自身代码就可以扩展它的行为.其次,我们所做的工作只是创建新类,并且实现抽象类的所有方法,再也不需要为了找出更改的地方而在应用程序中到处搜索,这个方案不在是脆弱的.
同样,这个方法不在是僵化的.所有模块的代码不需要任何修改.仅仅是创建派生类实例的模块需要改动.除此而外,任何程序中重用DrawAllSharp方法时,不需要附带上两个具体的类型,只是需要创建自己的派生类,这个方案同样不再是顽固的.
但是...还是会存在一个问题,如何使圆在正方形之前输入呢?将具体的类抽象成Sharp类反倒成了一种障碍,虽然这个抽象是贴切的,但是应对这个问题它却不是最优的.很显然,在这个系统中,形状的顺序比形状的类型更具有实际意义.所以,无论模块多么封闭,总会存在一些无法封闭的变化,没有对与所有情况都贴切的类型.
如何解决形状顺序输出的问题呢?是不是在DrawAllSharp方法中对集合中每个形状类型进行判断,控制它们的顺序呢?难道需要这样实现?
View Code
public static void DrawAllSharp()
{
foreach (Sharp s in Sharps)
{
if (s is Circle)
{
s.Draw();
}
}
#region 应对Square类型所做的变化
foreach (Sharp s in Sharps)
{
if (s is Square)
{
s.Draw();
}
}
#endregion
}
3.客户又说他需要系统同样能够输出三角形,并且绘制顺序按照三角形,正方形和圆形.
当再次面对改变时,DrawAllSharp方法依旧只针对具体的类来实现.如何做到在函数中对形状的顺序变化封闭?值得注意的是,封闭是建立在抽象基础之上的.在此处,我们需要建立一个顺序抽象体.让Sharp类的各个派生类都不知道顺序的变化.List集合是可以排序的,我们可以将形状顺序的设置隔离到一个新的辅助类中,在每次按顺序输出之前对集合体进行排序.
View Code
class SharpComparer:IComparer
{
private static Dictionary priorities = new Dictionary();
static SharpComparer()
{
priorities.Add(typeof(Circle), 3);
priorities.Add(typeof(Triangle), 1);
priorities.Add(typeof(Square), 2);
}
private int PriorityFor(Type type)
{
if (priorities.ContainsKey(type))
{
return priorities[type];
}
return 0;
}
#region IComparer 成员
public int Compare(Sharp x, Sharp y)
{
int priority1 = PriorityFor(x.GetType());
int priority2 = PriorityFor(y.GetType());
return priority1.CompareTo(priority2);
}
#endregion
}
DrawAllSharp方法的修改
View Code
public static void DrawAllSharp()
{
_sharps.Sort(new SharpComparer());
foreach (Sharp s in _sharps)
{
s.Draw();
}
}对于上面的解决方案.每次顺序变化时,我们需要修改SharpComparer类中顺序的定义,而不再需要改变其他任何模块的代码.达到对顺序变化的封闭!
最后,在许多方面,OCP都是面向对象设计的核心所在.遵循这个原则可以带来面向对象技术所声称的巨大好处:灵活性,可重用性以及可维护性.然而,并不是说只要使用一种面向对象语言就遵循了这个原则.对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意.正确的做法是,开发人员应该仅仅对程序中出现频繁变化的那些部分做出抽象.拒绝不成熟的抽象和抽象本身一样重要!!! |