面向对象的原则、模式、语言及框架(四)

[b]开-闭原则:[/b]
任何软件在其生命周期内都会发生变化,如果我们期望开发出来的系统不会在第一版之后就被抛弃,就必须面对需求的变化而保持相对稳定.开-闭原则(The open-close principle)为我们提供了指引.
那什么是开-闭原则呢?
软件实体(模块,类,方法等)应该是可以扩展的,但是不可修改的.
这句话说出了软件实体应该具备的两个特征:
1、对扩展式开放的(Open for extension)
当需求变化时,我们可以对模块进行扩展,使其具有满足新需求的新行为。
2、对于更改时封闭的(Closed modification)
对模块进行扩展时,不能修改已有的源代码或二进制代码。
这两个方面似乎是矛盾的,我们怎么才能无需对模块进行改动的情况下改变它的功能呢?
关键是抽象,我们可以把一组行为抽象出一个接口/抽象类,而任意一个可能的行为则表现为
可能的派生类。这个原则的直接结果产生了策略模式(Strategy pattern),策略模式定义了
一个算法的接口,而其派生类则定义了不同的实现。当我们需要新的策略时,我们只需要重新
实现这个接口即可,而其他部分只依赖于这个接口,不依赖具体的实现。
下面我们看看一个使用策略模式满足开闭原则的例子:
[code]
public interface Sort<T>{
void sort(T t[]);
}
public class BubbleSort<T> implements BubbleSort<T>{
public void sort(T t[]){
//the bubble sort code
}
}
[/code]
我们可能需要一个效率更高的算法,而BubbleSort不能够满足我们的需求时,我们不需要修改原来的代码,直接实现一个新的算法来扩展新的功能。
[code]
public class QuickSort<T> implements BubbleSort<T>{
public void sort(T t[]){
//the quick sort code
}
}
[/code]
而其他的代码则只依赖于我们的Sort接口:
[code]
class SomeClassUseSort{
private Sort sort;
//other codes
}
[/code]
下面我们看看Bob大叔举的一个违反OCP的例子:
绘制图形的例子:
[code]
class Shape{}
class Circle extends Shape{}
class Square extends Shape{}

class DrawAll{
public void drawAll(List<Shape> shapes){
for(Shape shape : shapes){
if(shape instanceof Circle)
drawCircle();
else if(shape instanceof Square){
drawSquare();
}
}
}
private void drawCircle(){
//draw circle;
}
private void drawSquare(){
//draw square
}
}
[/code]
当我们需要绘制三角形时,我们需要在ShapeDraw中添加drawTriangle方法
并且需要修改drawAll方法,以便能够绘制三角形。
这个例子违反了开闭原则,我们下面考虑如何重构这个例子:
[code]
class Shape{
public abstract void draw();
}
class Circle extends Shape{
public void draw(){
//draw circle
}
}
class Square extends Shape{
public void draw(){
//draw Shape
}
}
class DrawAll{
public void drawAll(List<Shape> shapes){
for(Shape shape : shapes){
shape.draw();
}
}
}
[/code]
当我们怎加新的图形时,我们只需要继承Shape并实现自己的draw方法即可,而无需
修改DrawAll类。这就是遵循开闭原则的威力。其实大部分违反开闭原则都是过程化
的思想所致,当你发现你的类中充斥着一连串的if语句时,基本上说它违反了开闭原则。
现在我们的代码真的满足开闭原则了么?
那我们再看看吧,新的需求来了,要求我们按照形状的顺序来绘制图形,我们现在的代码
如果不作修改的话显得无能为力了。
好的,那我们再抽象出DrawAll接口:
[code]
interface DrawAll{
public abstract void drawAll(List<Shape> shapes);
}

class DrawAllByListIndex implements DrawAll{
public void drawAll(List<Shape> shapes){
for(Shape shape : shapes){
shape.draw();
}
}
}
[/code]
这样我们可以
[code]
class DrawAllByShape implements DrawAll{
public void drawAll(List<Shape> shapes){
sortByShape(shapes);
for(Shape shape : shapes){
shape.draw();
}
}
}
[/code]
来扩展DrawAll就ok了。
就像我们开始抽象的那样,我们根本没有预测到按照图形顺序绘制的需求,我们也不可能
每个东西都抽象出一个接口,因为这样会产生不必要的复杂性,有点过度设计的味道。所以
我们能做的只是去刺激需求,尽快地了解需求可能的变化,我们可以采用测试驱动的方法,首先构建一个可测试的抽象,并且通常这个可测试的抽象会隔离以后可能要发生的其他种变化。
[b]结论[/b]
OCP可以说是面向对象设计的核心,遵循这个原则,可以使系统具有更好的灵活性、重用性和可维护性。但是如果肆无忌惮的使用这个原则,进行过度的抽象同样不是好的做法。正确的做法是仅对频繁发生变化的地方进行抽象,拒绝不成熟的抽象和抽象本身同样重要。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值