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

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

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