五大基本原則之開閉原則

這是安卓開發(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類的截圖:

TextView.png

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

ViewGroup也是這樣的:

QQ截圖20170928142150.png

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ā)原則。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評(píng)論 25 708
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 7,391評(píng)論 0 17
  • 一、貌若潘安群英妒,剎那芳華負(fù)如蘇 (寰迷小夢(mèng),原創(chuàng)作品,紀(jì)念黃鷹,幫助寰哥,轉(zhuǎn)載告知,盜文必究) 白皙的面,濃密...
    寰迷小夢(mèng)葉非楊閱讀 1,601評(píng)論 35 105
  • 清幽若灰
    漫青云閱讀 316評(píng)論 0 0
  • 今天看到一個(gè)好玩的命題,與大家分享:假設(shè)你正在前往殯儀館的路上,要去參加一位至親的喪禮。抵達(dá)之后,居然發(fā)現(xiàn)親朋好友...
    kileen_閱讀 1,297評(píng)論 0 0

友情鏈接更多精彩內(nèi)容