单一职责与开放封闭原则

单一职责原则(SRP)

image

    为什么要把两个职责分离到单独的类呢?因为每个职责都是变化的一个轴线,如果一个类承担的职责过多,就等于把这些职责耦合在一起了,一个职责的变化可能会削弱这个类满足其它职责的能力,这种耦合会导致脆弱的设计,如果发生变化,设计就会遭到意想不到的破坏。

image

    上面类图中显示 Rectangle 类有两个方法,一个方法把矩形绘制在屏幕上,另一个方法计算矩形的面积。有两个不同的应用程序使用 Rectangle 类,一个是计算几何程序,另一个是图形界面程序,它们分别主要使用 Rectangle 类总两个不同的方法。

    这个设计就侵犯了 SRP 原则,Rectangle 类具备两个职责,一个职责是提供矩形几何计算的数学模型,另一个职责是在 GUI 上渲染矩形。

    对 SRP 原则的侵犯会导致了一些难以解决的问题。首先,我们必须在计算几何应用中包含对 GUI 代码的引用,如果这是一个 C++ 程序,就必须把 GUI 代码链接进来,这导致应用程序无谓的消耗了链接时间、编译时间、内存空间和存储空间等。

    再者,如果因为某些原因对 GraphicalApplication 的一个更改导致 Rectangle 类也相应做了更改,这将强制我们对 ComputationalGeometryApplication 进行重新构建、测试和部署等。如果我们忘了做这些事情,那么应用程序可能以无法预测的方式而失败。

    一个较好的设计是将这两个职责完全地隔离到不同的类当中,如下图所示。将 Rectangle 中关于几何计算的职责移到了 GeometricRectangle 类中,而 Rectangle 类中仅保留矩形渲染职责。

image

    在 SRP 中,我们将职责定义为“变化的原因”,如果你能想出多于一种动机来更改一个类,则这个类就包含多于一个职责。SRP 是所有原则中最简单的之一,也是最难运用正确的之一,因为我们习惯于将多个职责一起来考虑,职责的耦合有时很难被发现。

    借用《UNIX编程艺术》的一句话:Do one thing, and do it well.

开放-封闭原则(OCP)

image

    遵循开放-封闭原则的模块有两个特性:1)对扩展是开放的;2)对修改是封闭的

    乍看起来这两个特性好像是冲突的,因为通常扩展模块行为的常规方式就是修改该模块,那么如何使这两个相反的特性共存呢?关键就是抽象。

    我们有如下程序,其功能是在标准 GUI 窗口上绘制圆形(Circle)和方形(Square),很明显DrawAllShapes不满足 OCP,使用 C 语言的过程化技术是无法满足开放-封闭原则的。

enum ShapeType {circle, square};

struct Shape 
{
    ShapeType itsType;
};

struct Square 
{
    ShapeType itsType;
    double itsSide; 
    Point itsTopLeft;
};

struct Circle 
{
    ShapeType itsType;
    double itsRadius; 
    Point itsCenter;
};

void DrawSquare(struct Square*);
void DrawCircle(struct Circle*);
typedef struct Shape *ShapePointer;

void DrawAllShapes(ShapePointer list[], 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;
        }
    }
}

    我们将上面的代码进行适当修改,使其满足开放封闭原则。

class Shape {
    public:
        virtual void Draw() const = 0;
};

class Square: public Shape {
    public:
        virtual void Draw() const;
};

class Circle: public Shape {
    public:
        virtual void Draw() const;
};

void DrawAllShapes(vector<Shape*> list){
    vetor<Shape*>::iterator I;
    for (i = list.begin(); i != list.end(); i++) {
        (*i)->Draw();
    }
}

    经过上面的改进,我们如果要增加一个新的图形,只需要创建一个衍生类并实现 Shape 类 中包含的方法就可以了,不再需要去修改DrawAllShapes()函数。

    但是要知道程序不可能达到完全封闭,如果对上述程序提出新的要求:所有的 Circle 都应该在 Square 之前先进行绘制,DrawAllShapes()函数无法对这样的变化保持封闭,一般来说无论模块设计的有多么封闭,总会有变化来打破这种封闭的。

    因此程序设计者必须考虑要对哪些变化封闭,需要讲究策略,也需要基于一定的经验去预测,了解用户及行业的的情况,可以更加准确的判断各种变化的可能性。

    当然,我们可以在 Shape 中定义一个名为 Precedes 的方法,它可以接受另一个 Shape 作为参数并返回一个 bool 类型的结果,如果结果为 true 则表示接收调用的 Shape 对象应排在被作为参数的 Shape 对象的前面。(限于篇幅太长,下面不贴所有代码了)

bool Precedes(Shape another){
    return another is Circle;
}

    另外也可以使用数据驱动的方法获取封闭性,这里使用一个表格来存储绘制的顺序,如下所示。需要注意的是我们仍然无法对多种 Shape 的顺序保持封闭的就是表本身,可以将这个表定义放置在单独的模块中,使表与其他模块隔离,这样对表的更改则不再会对任何其他模块产生影响。

private Dictionary<Type, int> _typeOrderTable = new Dictionary<Type, int>();

private void Initialize()
{
  _typeOrderTable.Add(typeof(Circle), 2);
  _typeOrderTable.Add(typeof(Square), 1);
}

public bool Precedes(Shape another)
{
  return _typeOrderTable[this.GetType()] > _typeOrderTable[another.GetType()];
}

参考资料:《敏捷软件开发 原则、模式与实践》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Guanngxu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值