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