O 代表著開閉原則
這是 SOLID 安卓開發系列原則的第二部分。如果你錯過了第一部分或者不明白 SOLID 原則是什么,看看第一部分,這里介紹了 SOLID 和單一職責原則。
開閉原則
SOLID 中的 ‘O’ 指的是開閉原則。開閉原則如是說:
軟件實體(類,模塊,方法,等等)應該對擴展開放,對修改關閉。
這聽起來簡單,但它也是那些你在腦海里重復足夠多次后,你會發覺非常迷惑的原則。基本原則是你應該 力圖寫出你不需要每次需求一改變代碼就要改變的代碼 。在安卓中,我們使用 Java,所以這個原則可以用繼承和多態來實現。
一個開閉原則的例子
下面是個工程上常見的例子,它簡單地說明了開閉原則的內容和實現方法。下面的這個例子非常簡單,而且清晰地可視化了開閉原則。
讓我們假設你有一個這樣的應用,這個應用計算你提供的圖形的面積。雖然簡單,我幾年前在明尼蘇達的一家農作物保險公司工作的時候就遇到過一模一樣的問題。那個應用需要計算一個給定區域的所有農作物的保險報價。正如你可能知道的那樣 - 農作物有各種圖形和大小,對的,甚至是圓形、三角形和各種其他的多邊形。
好吧,回到例子……
作為一個好的程序員,我們把面積計算抽象到一個叫做 AreaManager 的類中。 AreaManager 類具備單一職責 - 計算形狀的全部面積。
讓我們假設我只工作在矩形的農作物上,所以我們有一個 Rectangle 類來表示它。這是這些類的代碼:
Rectangle.java
public class Rectangle {
private double length;
private double height;
// getters/setters ...
}
AreaManager.java
public class AreaManager {
public double calculateArea(ArrayList<Rectangle>... shapes) {
double area = 0;
for (Rectangle rect : shapes) {
area += (rect.getLength() * rect.getHeight());
}
return area;
}
}
AreaManager 類一直都把它的工作完成得很好,直到下周我們有一個新的作物的類型出現 - 一個圓形:
Circle.java
public class Circle {
private double radius;
// getters/setters ...
}
因為我們有一個新的形狀需要計算,我們需要修改 AreaManager :
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;
}
}
這段代碼有些壞代碼的味道了。
如果我們有一個三角形,或者其他多邊形,我們需要一遍一遍地修改這段代碼。
這個類違反了開閉原則。它沒有針對修改關閉和針對擴展開放。每一次新的形狀出現的時候,我們都要修改 AreaManager。我們想避免這種情況。
我們如何使得 AreaManager 類的開閉原則友好些呢?
采用繼承實現開閉原則
采用繼承實現開閉原則
因為 AreaManager 負責計算所有形狀的全部面積,而且形狀計算對每一個圖形都是唯一的,所以看起來我們需要做的就是把每個形狀的面積計算移動到對應的類中間。
嗯,但是 AreaManager 仍然需要知道所有的圖形,對吧?不然它是怎么知道它遍歷的對象有一個面積函數呢?當然,這可以用反射來實現 ( 咳咳 又 咳咳 ) 或者我們可以讓每一個形狀類繼承自一個接口: Shape 接口 (這也可以是一個抽象類):
Shape.java
public interface Shape {
double getArea();
}
每一個類必須實現這個接口 (或者擴展抽象類,如果這是你想要的話 - 無論什么原因) 像這樣:
Rectangle.java
public class Rectangle implements Shape {
private double length;
private double height;
// getters/setters ...
@Override
public double getArea() {
return (length * height);
}
}
Circle.java
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<Shape> shapes) {
double area = 0;
for (Shape shape : shapes) {
area += shape.getArea();
}
return area;
}
}
我們對 AreaManager 做了一些改變,來使它對于修改閉合但是對于擴展開放。如果我們需要增加一個新的形狀,例如八角形, AreaManager 就不需要做修改了,因為它對于擴展 Shape 接口是開放的。
安卓中的開閉原則
安卓中的開閉原則
形狀是個學習的好例子,而且當你在農作物保險公司工作的時候是非常有用的。但是這對于安卓系統來說,如何應用呢?好吧,它不僅僅用于安卓,它適用于任何語言。安卓有一些非常優美的開閉原則的實現,它們十分有用。讓我們看看 -
安卓開發的初學者一般都不知道的事情是,那些內嵌的安卓視圖,像 Button , Switch 和 Checkbox 都是 TextView 對象。看看這個截圖,你可以發現其他許多視圖也是繼承于 TextView 的。
這意味著安卓的視圖系統是對修改閉合但對擴展開發的。如果你打算通過創建你自己的 CurrencyTextView 改變文字的樣子,你只需要擴展(繼承) TextView,然后在那里實現你的視圖邏輯。安卓視圖系統不關心你使用的是一個新的 CurrencyTextView, 它只關心你的類是不是遵循了一個特定的 TextView 的要求。安卓會依賴一些特定的函數來展示自己,然后你的視圖就被畫在屏幕上了。
同樣的事情也發生在 ViewGroup 類中:
有許多不同的 ViewGroups (RelativeLayout, LinearLayout, etc) 而且安卓視圖系統知道它們是如何一起工作的。你可以非常容易地 創建你自己的 ViewGroup 通過擴展 ViewGroup 。最后,你可以寫些依賴于 ViewGroup , TextView ,或者 View 類的代碼來做些特別的事情。
依賴于抽象類,比如 View , TextView , ViewGroup 和其他更多的類,允許你寫出對修改閉合對擴展開發的代碼。
結論
結論
開閉原則不僅限于安卓視圖系統,但是安卓視圖系統是該原則在真實世界里運用的一個簡單體現,成千上萬的開發者每天都在使用它。你也可以寫些開閉原則友好的代碼。通過少許計劃和抽象,你可以寫出更容易維護和擴展的代碼,每次當新功能來臨的時候,你不需要每次都做代碼修改了。
正如所有的事情一樣,你不可能在項目開始的時候看到創建抽象的可能性。而且,這樣做只會帶來過度復雜的代碼,而且只是實現了某個模式。在我的經驗里面,我發現我常常在多次修改某個類的時候才會使用到開閉原則。那時,我會保證代碼是被測試驗證過的,然后我會運用開閉原則來重構它。這使我能夠有一個安全的測試保障和寫出更多的可維護的代碼,同時也保持輕量和每天開發的最小變化。
把里氏替換原則作為本系列的下一篇,它是我最喜歡的原則之一。
1. 從技術上說,開閉原則有兩個變種。開閉原則是 Bertrand Meyer 創建的,它的一個變種是 Meyer 的開閉原則。另一個變種是 Polymorphic 的開閉原則。這兩個原則都使用繼承作為解決方案。這里,我引用 Robert C. Martin 的多態解釋
看看本系列的第三部分,里氏替換原則 !
來自: https://realm.io/cn/news/donn-felker-solid-part-2/