五大基本原则之开闭原则

  • 原文连接:https://academy.realm.io/posts/donn-felker-solid-part-2/
  • 译文出自:kailaisi的
  • 译 者:kailaisi

这是安卓开发系列文章中关于SOLID原则的第二部分。如果你没有没有阅读过第一部分,或者不了解什么是SOLID原则,请点击第一部分,其中我们介绍了SOLID原则并讨论了单一职责原则。
SOLID中的"O"是开闭原则的缩略词。开闭原则描述如下

一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

虽然听起来简单,但是如果你在自己的脑海里多思考几次,你可能就会怀疑自己对这句话的理解。简单来说就是,你应该努力写出这样的代码:当需求变更的时候,并不一定需要改变原有代码就可以实现功能。由于在Android中使用Java语言进行开发,因此可以通过继承和多态来实现这一功能。

一个开闭原则的简单实例

下面的例子是一个非常典型的开闭原则及其实现。非常简单,但却能够很好的说明开闭原则。

假设有一个应用程序,能够计算任意形状面积。这是几年前我在明尼苏达州农作物保险公司遇到的一个非常简单问题。app程序必须能够计算出指定区域的农作物总的保险报价。正如你所知道的,农作物有各种形状和大小,有可能是圆的,有可能是三角形的也可能是其他各种多边形。

OK,让我们回到我们之前的例子中....

作为一名优秀的程序员,我们将这个面积计算类命名为 AreaManager。这个 AreaManager是单一职责的类:计算形状的总面积 。
假设我们现在有一块矩形的农作物,我omen用一个Rectangle类来表示。相关类代码如下:

public class Rectangle {
    private double length;
    private double height; 
    // getters/setters ... 
}

public class AreaManager {
    public double calculateArea(ArrayList... shapes) {
        double area = 0;
        for (Rectangle rect : shapes) {
            area += (rect.getLength() * rect.getHeight()); 
        }
        return area;
    }
}

AreaManager类现在运行良好,直到几周之后,我们又有一种新的形状——圆形:

public class Circle {
    private double radius; 
    // getters/setters ...
}

由于有新的形状需要考虑,我们必须修改我们的AreaManager类:

public class AreaManager {
    public double calculateArea(ArrayList... shapes) {
        double area = 0;
        for (Object shape : shapes) {
            if (shape instanceof Rectangle) {
                Rectangle rect = (Rectangle)shape;
                area += (rect.getLength() * rect.getHeight());                
            } else if (shape instanceof Circle) {
                Circle circle = (Circle)shape;
                area += (circle.getRadius() * cirlce.getRadius() * Math.PI;
            } else {
                throw new RuntimeException("Shape not supported");
            }            
        }
        return area;
    }
}
 
 

从这段代码开始,我们察觉到了问题。
如果我们遇到一个三角形,或者其他形状呢,这时候我们就必须一次又一次的修改AreaManager类。

这个类的设计就违背了开闭原则,没有做到对修改的封闭性以及对扩展的开放性。我们必须避免这种事情的发生~

基于继承的开闭原则的实现

AreaManager类的职责是计算各种形状的面积,而每一种形状都有其独特的计算面积的方法,因此将面积的计算放入到各个形状类中是特别合理的。

AreaManager类仍然需要知道所有的形状,否则它就无法判断所有的形状类是否都包含了计算面积的方法。当然了,我们可以通过反射来实现。其实有一种更简单的方式也可以实现——让所有的形状类都继承一个接口: Shape(也可以是抽象类)

public interface Shape {
    double getArea(); 
}

每一个形状类都实现这个接口(如果接口无法满足你的需求,也可以通过继承某个抽象类):

public class Rectangle implements Shape {
   private double length;
   private double height; 
   // getters/setters ... 

   @Override
   public double getArea() {
       return (length * height);
   }
}

public class Circle implements Shape {
   private double radius; 
   // getters/setters ...

   @Override
   public double getArea() {
       return (radius * radius * Math.PI);
   }
}

现在,我们可以通过这个抽象方法将AreaManager构造成一个符合开闭原则的类。

public class AreaManager {
    public double calculateArea(ArrayList shapes) {
        double area = 0;
        for (Shape shape : shapes) {
            area += shape.getArea();
        }
        return area;
    }
}

通过这种方式, AreaManager类符合了对修改关闭,对扩展开放的要求。如果我们需要增加一种新形状,比如:八边形。新的类只需要继承Shape接口即可,AreaManager根本不需要做任何的修改。

Android中的开闭原则

在农作物保险工作中,Shape类非常有用,但是这种模式在Android中如何应用呢?其实开闭原则和语言无关,它适用于任何语言。Android中也有一些典型的开闭原则的应用实例。我们慢慢来....
很多Android开发人员可能没有注意到-Android内置的一些控件,比如Button、Switch、Checkbox都是TextView类。我们来看一下关于各种继承TextView类的截图:

五大基本原则之开闭原则_第1张图片
TextView.png

这意味着Android控件对修改封闭,对继承开放。如果你想自定义一个CurrencyTextView,并修改字体的显示样式,你只要简单的继承TextView类,并重写你自己的逻辑即可。Android控件不关心你是否创建了新类,它只在意你的类是否遵循了TextView的特定约束。Android通过特定的约束将自定义的控件绘制在屏幕上。

ViewGroup也是这样的:

五大基本原则之开闭原则_第2张图片
QQ截图20170928142150.png

Android有各种不同的ViewGroup(RelativeLayout,LinearLayout等等),Android系统也能够很好的协同合作。你可以通过继承ViewGroup类来实现自己的ViewGroup。
通过继承抽象类View,TextView,ViewGroup,允许你编写符合开放原则的控件。

结论

开闭原则不仅限于Android控件,但是Android控件是所有的开发者每天都能够用到的一种典型的开闭原则的是想方式。你也可以自己编写更加有好的符合开闭原则的代码。通过一些简单的抽象方法,你可以很方便的创建一些能够进行继承和扩展的类,而不必在每次增加新特性的时候去修改原有代码。

对于一些新的项目,你可能找不到需要进行抽象的类。此外,只是为了实现模式而产生一些过度复杂的代码是不明智的。据我以往的经验,当多次修改一个类之后,就会发现需要使用开闭原则。这时候,我会对代码进行充分的测试,然后重构代码以实现对修改关闭,对扩展开放。有了个覆盖率高的测试代码,我才能在剩余的时间编写更多可维护的代码。

敬请期待本系列中的下一篇文章——里氏替换原则,这是目前为止我最喜欢的开发原则。

你可能感兴趣的:(五大基本原则之开闭原则)