zoukankan      html  css  js  c++  java
  • 敏捷软件开发:原则、模式与实践——第9章 OCP:开放-封闭原则

    第9章 OCP:开放-封闭原则

      软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。


    9.1 OCP概述

      遵循开放-封闭原则设计出的模块具有两个主要特征:

      (1)对于扩展是开放的(open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。

      (2)对于修改是封闭的(closed for modification)。对模块进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。

      在C#或者其他任何OOPL(面向对象程序设计语言)中,可以创建出固定却能够描述一组任意个可能行为的抽象体。这个抽象体就是抽象基类。而这一组任意个可能的行为则表现为可能的派生类。

      模块可能对抽象体进行操作。由于模块依赖于一个固定的抽象体,所以它对于更改可以是封闭的。同时,通过从这个抽象体派生,可以扩展此模块的行为。

      
    9.2 Shape应用程序

    9.2.1 违反OCP

      查看如下代码:

    //--shape.h---------------------------------------
        enum ShapeType
        {
            circle,
            square
        };
    
        struct Shape
        {
            ShapeType itsType;
        };
    
    //--circle.h---------------------------------------
        struct Circle
        {
            ShapeType itsType;
            double itsRadius;
            Point itsCenter;
        };
    
        void DrawCircle(struct Circle*);
    
    //--square.h---------------------------------------
        struct Square
        {
            ShapeType itsType;
            double itsSide;
            Point itsTopLeft;
        };
    
        void DrawSquare(struct Square*);
    
    //--drawAllShapes.cc-------------------------------
        typedef struct Shape *private ShapePointer ;
        void DrawAllShapes(ShapePointer list[]private ,private int n )
        {
            int i;
            for (i = 0; i < n; i++)
            {
              struct Shape* s = list[i];
                switch (s->itsType)
                {
                    case square:
                        DrawSquare((struct Square* )s);
                        break;
                    case circle:
                        DrawCircle((struct Circle* )s);
                        break;
                }
            }
        }

      DrawAllShapes函数不符合OCP,因为它对于新的形状类型的添加不是封闭的。如果希望这个函数能够绘制包含三角形的列表,就必须变更这个函数。事实上,每增加一种新的形状类型,都必须要更改这个函数。

      
    9.2.2 遵循OCP  

      查看如下Square/Circle问题的OOD解决方案

        public interface Shape
        {
            void Draw();
        }
        public class Square : Shape
        {
            public void Draw()
            {
                //draw a square
            }
        }
        public class Circle : Shape
        {
            public void Draw()
            {
                //draw a circle
            }
        }
        public void DrawAllShapes(IList shapes)
        {
            foreach (Shape shape in shapes)
                shape.Draw();
        }


    9.2.3 预测变化和“贴切的”结构

      一般而言,无论模块是多么的“封闭”,都会存在一些无法对之封闭的变化。没有对于所有的情况都贴切的模型。

      既然不能完全封闭,那么就必须有策略的对待这个问题。也就是说,设计人员必须对于他设计的模块应该对哪种变化封装做出选择。他必须先猜测出最有可能发生变化的类,然后构造抽象来隔离那些变化。

      这需要设计人员具有一些从经验中获得的预测能力。有经验的设计人员希望自己对用户和应用领域很了解,能够以此来判断各种变化的可能性。然后,它可以让设计对于最有可能发生的变化遵循OCP原则。

      这一点不容易做到。并且在大多数情况下,他们都会猜测错误。

      遵循OCP的代价也是昂贵的。创建适当的抽象是要花费开发时间和精力的。同时,那些抽象也增加了软件设计的复杂性。

      最终,我们会一直等到变化发生时才采取行动!


    9.2.4 放置吊钩

      在上世纪,我们会在我们认为可能发生变化的地方“放置吊钩”(hook)。我们觉得这样会使软件灵活一些。

      然而,我们放置的吊钩常常是错误的。更糟的是,即使不使用这些吊钩,也必须要去支持和维护它们,从而就有了不必要的复杂性的臭味。通常,我们更愿意一直等到却是需要那些抽象时再把它放置进去。


    9.2.5 使用抽象获得显式封闭

      封闭是建立在抽象的基础上的。因此,为了让DrawAllShapes对于绘制顺序的变化是封闭的。我们需要一种“顺序抽象体”。这个抽象体定义了一个抽象接口,通过这个接口可以表示任何可能的排序策略。

      一个排序策略意味着,给定两个对象,可以推导出先绘制哪一个。C#提供了这样的抽象。IComparable是一个接口,它只提供一个方法:CompareTo。这个方法以一个对象作为输入参数,当接受消息的对象小于、等于、大于参数数对象时,该方法分别返回-1,0,1 。

    如果希望Circle先于Square绘制,查看如下代码:

        public interface Shape : IComparable
        {
            void Draw();
        }
    
        public class Square : Shape
        {
            public void Draw()
            {
                //draw a square
            }
    
            public int CompareTo(object obj)
            {
                if (obj is Circle)
                {
                    return 1;
                }
                else
                {
                    return 0;
                }
            }
        }
        public class Circle : Shape
        {
            public void Draw()
            {
                //draw a circle
            }
    
            public int CompareTo(object obj)
            {
                if (obj is Square)
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }
        }
        public void DrawAllShapes(ArrayList shapes)
        {
            shapes.Sort();
            foreach (Shape shape in shapes)
                shape.Draw();
        }

    对于这样的代码:

            public int CompareTo(object obj)
            {
                if (obj is Square)
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }

    显然不符合OCP。每次创建一个新的Shape类的派生类时,所有的CompareTo()函数都需要改动。


    9.2.6 使用“数据驱动”的方法获取封闭性  
      如果我们不要使Shape类的各个派生类之间互不知晓,可以使用表格驱动的方法。表格驱动的形状排序机制:

    /// <summary>
    /// This comparer will search the priorities
    /// hashtable for a shape's type. The priorities
    /// table defines the odering of shapes. Shapes
    /// that are not found precede shapes that are found.
    /// </summary>
    public class ShapeComparer : IComparer
    {
        private static Hashtable priorities = new Hashtable();
        static ShapeComparer()
        {
            priorities.Add(typeof(Circle), 1);
            priorities.Add(typeof(Square), 2);
        }
        private int PriorityFor(Type type)
        {
            if(priorities.Contains(type))
                return (int)priorities[type];
            else
                return 0;
        }
        public int Compare(object o1, object o2)
        {
            int priority1 = PriorityFor(o1.GetType());
            int priority2 = PriorityFor(o2.GetType());
            return priority1.CompareTo(priority2);
        }
    }

    修改DrawAllShapes方法:

    public void DrawAllShapes(ArrayList shapes)
    {
        shapes.Sort(new ShapeComparer());
        foreach(Shape shape in shapes)
            shape.Draw();
    }


    9.3 结论

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

    摘自:《敏捷软件开发:原则、模式与实践(C#版)》Robert C.Martin    Micah Martin 著

    转载请注明出处:

    作者:JesseLZJ
    出处:http://jesselzj.cnblogs.com

  • 相关阅读:
    cstring string 比较之二(学习笔记)
    转 大话设计模式学习笔记(思维导图) 更新中
    转 十三种设计模式的思维导图
    (转)关于栈、堆、静态存储区最大可分配大小的探讨 海量之一
    如何学习网络协议(学习笔记)
    境界篇:linux 驱动开发的境界(学习笔记)
    b.关于vc编程境界的讨论
    关于编程境界的小结
    Java异常(一) Java异常简介及其架构
    多线程简单阐述
  • 原文地址:https://www.cnblogs.com/jesselzj/p/4764111.html
Copyright © 2011-2022 走看看