- 原文連接:https://academy.realm.io/posts/donn-felker-solid-part-2/
- 譯文出自:kailaisi的簡(jiǎn)書
- 譯 者:kailaisi
這是安卓開發(fā)系列文章中關(guān)于SOLID原則的第二部分。如果你沒有沒有閱讀過第一部分,或者不了解什么是SOLID原則,請(qǐng)點(diǎn)擊第一部分,其中我們介紹了SOLID原則并討論了單一職責(zé)原則。
SOLID中的"O"是開閉原則的縮略詞。開閉原則描述如下
一個(gè)軟件實(shí)體如類、模塊和函數(shù)應(yīng)該對(duì)擴(kuò)展開放,對(duì)修改關(guān)閉。
雖然聽起來簡(jiǎn)單,但是如果你在自己的腦海里多思考幾次,你可能就會(huì)懷疑自己對(duì)這句話的理解。簡(jiǎn)單來說就是,你應(yīng)該努力寫出這樣的代碼:當(dāng)需求變更的時(shí)候,并不一定需要改變?cè)写a就可以實(shí)現(xiàn)功能。由于在Android中使用Java語言進(jìn)行開發(fā),因此可以通過繼承和多態(tài)來實(shí)現(xiàn)這一功能。
一個(gè)開閉原則的簡(jiǎn)單實(shí)例
下面的例子是一個(gè)非常典型的開閉原則及其實(shí)現(xiàn)。非常簡(jiǎn)單,但卻能夠很好的說明開閉原則。
假設(shè)有一個(gè)應(yīng)用程序,能夠計(jì)算任意形狀面積。這是幾年前我在明尼蘇達(dá)州農(nóng)作物保險(xiǎn)公司遇到的一個(gè)非常簡(jiǎn)單問題。app程序必須能夠計(jì)算出指定區(qū)域的農(nóng)作物總的保險(xiǎn)報(bào)價(jià)。正如你所知道的,農(nóng)作物有各種形狀和大小,有可能是圓的,有可能是三角形的也可能是其他各種多邊形。
OK,讓我們回到我們之前的例子中....
作為一名優(yōu)秀的程序員,我們將這個(gè)面積計(jì)算類命名為 AreaManager。這個(gè) AreaManager是單一職責(zé)的類:計(jì)算形狀的總面積 。
假設(shè)我們現(xiàn)在有一塊矩形的農(nóng)作物,我omen用一個(gè)Rectangle類來表示。相關(guān)類代碼如下:
public class Rectangle {
private double length;
private double height;
// getters/setters ...
}
public class AreaManager {
public double calculateArea(ArrayList<Rectangle>... shapes) {
double area = 0;
for (Rectangle rect : shapes) {
area += (rect.getLength() * rect.getHeight());
}
return area;
}
}
AreaManager類現(xiàn)在運(yùn)行良好,直到幾周之后,我們又有一種新的形狀——圓形:
public class Circle {
private double radius;
// getters/setters ...
}
由于有新的形狀需要考慮,我們必須修改我們的AreaManager類:
public class AreaManager {
public double calculateArea(ArrayList<Object>... 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;
}
}
從這段代碼開始,我們察覺到了問題。
如果我們遇到一個(gè)三角形,或者其他形狀呢,這時(shí)候我們就必須一次又一次的修改AreaManager類。
這個(gè)類的設(shè)計(jì)就違背了開閉原則,沒有做到對(duì)修改的封閉性以及對(duì)擴(kuò)展的開放性。我們必須避免這種事情的發(fā)生~
基于繼承的開閉原則的實(shí)現(xiàn)
AreaManager類的職責(zé)是計(jì)算各種形狀的面積,而每一種形狀都有其獨(dú)特的計(jì)算面積的方法,因此將面積的計(jì)算放入到各個(gè)形狀類中是特別合理的。
AreaManager類仍然需要知道所有的形狀,否則它就無法判斷所有的形狀類是否都包含了計(jì)算面積的方法。當(dāng)然了,我們可以通過反射來實(shí)現(xiàn)。其實(shí)有一種更簡(jiǎn)單的方式也可以實(shí)現(xiàn)——讓所有的形狀類都繼承一個(gè)接口: Shape(也可以是抽象類)
public interface Shape {
double getArea();
}
每一個(gè)形狀類都實(shí)現(xiàn)這個(gè)接口(如果接口無法滿足你的需求,也可以通過繼承某個(gè)抽象類):
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);
}
}
現(xiàn)在,我們可以通過這個(gè)抽象方法將AreaManager構(gòu)造成一個(gè)符合開閉原則的類。
public class AreaManager {
public double calculateArea(ArrayList<Shape> shapes) {
double area = 0;
for (Shape shape : shapes) {
area += shape.getArea();
}
return area;
}
}
通過這種方式, AreaManager類符合了對(duì)修改關(guān)閉,對(duì)擴(kuò)展開放的要求。如果我們需要增加一種新形狀,比如:八邊形。新的類只需要繼承Shape接口即可,AreaManager根本不需要做任何的修改。
Android中的開閉原則
在農(nóng)作物保險(xiǎn)工作中,Shape類非常有用,但是這種模式在Android中如何應(yīng)用呢?其實(shí)開閉原則和語言無關(guān),它適用于任何語言。Android中也有一些典型的開閉原則的應(yīng)用實(shí)例。我們慢慢來....
很多Android開發(fā)人員可能沒有注意到-Android內(nèi)置的一些控件,比如Button、Switch、Checkbox都是TextView類。我們來看一下關(guān)于各種繼承TextView類的截圖:

這意味著Android控件對(duì)修改封閉,對(duì)繼承開放。如果你想自定義一個(gè)CurrencyTextView,并修改字體的顯示樣式,你只要簡(jiǎn)單的繼承TextView類,并重寫你自己的邏輯即可。Android控件不關(guān)心你是否創(chuàng)建了新類,它只在意你的類是否遵循了TextView的特定約束。Android通過特定的約束將自定義的控件繪制在屏幕上。
ViewGroup也是這樣的:

Android有各種不同的ViewGroup(RelativeLayout,LinearLayout等等),Android系統(tǒng)也能夠很好的協(xié)同合作。你可以通過繼承ViewGroup類來實(shí)現(xiàn)自己的ViewGroup。
通過繼承抽象類View,TextView,ViewGroup,允許你編寫符合開放原則的控件。
結(jié)論
開閉原則不僅限于Android控件,但是Android控件是所有的開發(fā)者每天都能夠用到的一種典型的開閉原則的是想方式。你也可以自己編寫更加有好的符合開閉原則的代碼。通過一些簡(jiǎn)單的抽象方法,你可以很方便的創(chuàng)建一些能夠進(jìn)行繼承和擴(kuò)展的類,而不必在每次增加新特性的時(shí)候去修改原有代碼。
對(duì)于一些新的項(xiàng)目,你可能找不到需要進(jìn)行抽象的類。此外,只是為了實(shí)現(xiàn)模式而產(chǎn)生一些過度復(fù)雜的代碼是不明智的。據(jù)我以往的經(jīng)驗(yàn),當(dāng)多次修改一個(gè)類之后,就會(huì)發(fā)現(xiàn)需要使用開閉原則。這時(shí)候,我會(huì)對(duì)代碼進(jìn)行充分的測(cè)試,然后重構(gòu)代碼以實(shí)現(xiàn)對(duì)修改關(guān)閉,對(duì)擴(kuò)展開放。有了個(gè)覆蓋率高的測(cè)試代碼,我才能在剩余的時(shí)間編寫更多可維護(hù)的代碼。
敬請(qǐng)期待本系列中的下一篇文章——里氏替換原則,這是目前為止我最喜歡的開發(fā)原則。