代码改变世界

OOP的几个原则-----OCP:开闭原则(下)

2012-03-05 23:44  宅的一米  阅读(365)  评论(0编辑  收藏  举报

上一篇着重介绍了开闭原则的概念,通过类图我们可以看出,如果不对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<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类型

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<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方法中对集合中每个形状类型进行判断,控制它们的顺序呢?难道需要这样实现?

 

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<Sharp>
    {
        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方法的修改

View Code
public static void DrawAllSharp()
        {
            _sharps.Sort(new SharpComparer());
            foreach (Sharp s in _sharps)
            {
                s.Draw();
            }
        }

对于上面的解决方案.每次顺序变化时,我们需要修改SharpComparer类中顺序的定义,而不再需要改变其他任何模块的代码.达到对顺序变化的封闭!

 

最后,在许多方面,OCP都是面向对象设计的核心所在.遵循这个原则可以带来面向对象技术所声称的巨大好处:灵活性,可重用性以及可维护性.然而,并不是说只要使用一种面向对象语言就遵循了这个原则.对于应用程序中的每个部分都肆意地进行抽象同样不是一个好主意.正确的做法是,开发人员应该仅仅对程序中出现频繁变化的那些部分做出抽象.拒绝不成熟的抽象和抽象本身一样重要!!!