五大基本原則之開(kāi)閉原則

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

TextView.png

這意味著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也是這樣的:

QQ截圖20170928142150.png

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

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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

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