面向對象五大基本原則(二)

前言

時間如梭,少年仍在奔跑!!!

Ⅰ.里氏替換原則

簡述:關於里氏替換原則,可能在每天的代碼中都有出現關於這一原則的使用,只是一直都在使用,而沒有意識到這就是所謂的里氏替換原則。里氏替換原則的思想是:”基類可實現的功能,子類也可以實現”,

下面代碼,假設有一個List參數的方法C,裏面的邏輯是根據索引找到相應的集合元素,那麼當需要list的實現類的索引查找功能時,可以將list的實現類(ArrayList、LinkedList等)任意一個作爲方法C的參數傳入,類似這樣的代碼可能每天都會遇到,可這就是里氏替換原則的體現;

public String C(List<String> contents,int index){

        return contents.get(index);

    }   

下面代碼,接着假設有一個方法D聲明的返回類型是List,可下面代碼真實返回的類型卻是ArrayList,而調用者並不知道返回的類型是ArrayList,只知道給其返回了List,這也是里氏替換原則的體現;

public List<Integer> D(){

        //...省略
        //ArrayList<Integer> ids = new ArrayList<Integer>;
        return id;

}

上面列舉的兩個例子都是集合中接口和實現類之間的關係,也就是基類與子類的關係。基類可以實現的功能,子類可以代替基類實現相應的功能,這也是java面向對象特性的體現,所以在java語言裏,更實在的體現出了里氏替換原則。其實在開發中的代碼,里氏替換原則應該是隨處可見的,再看看下面的代碼,是不是跟上面都同樣的體現;

//主函數
  class App{

        public static void main(String args[]){

            ArrayList<String> str = getHappyNeyYear();
            NewYearFactory newYearFactory = new NewNewYearFactoryImpl();
            List<Integer> year = newYearFactory.createNewYear(str);
        }
    }

    //演示接口
    interface NewYearFactory{
        List<Integer> createNewYear(List<String> strContent);
    }

    //實現類
    class NewYearFactoryImpl implements NewYearFactory{
        @Override
        public List<Integer> createNewYear(List<String> strContent) {
            ArrayList<Integer> year = api.getYear(strContent);
            return year;
        }
   }

總結:基類可實現的功能,子類同樣可以實現,在繼承或實現中,子類延續了基類的特性。

Ⅱ.接口隔離原則

簡述:接口隔離原則的核心思想是要求接口不要過於通用,須追求專一的功能.

假設下面Hobit接口是提供給開發者選擇愛好的,而剛好A同學的愛好是逛街,那麼A同學是不是就要重寫Hobit接口,接着實現shoppting函數的邏輯代碼.

public interface Hobit{

        void coding();
        void readBook();
        void shopping();
    }

    public static void doHobit(){
         Hobit hobit = new Hobit() {
            @Override
            public void coding() {

            }
            @Override
            public void readBook() {

            }
            @Override
            public void shopping() {
                //...
            }
        };

    }

上面假設A同學的愛好是逛街,可在實現Hobit接口的時候卻也得實現coding和readBook這兩個方法,這不是多餘嗎?那麼可以去掉這兩個方法嗎? 假如去掉這兩個方法,這時候剛好B同學的愛好是讀書,那麼Hobit接口沒有了readBook這個方法,又該怎麼辦呢?

上面的假設會不會覺得貌似很矛盾,接口太通用,會覺得出現多餘的代碼;接口太專一,那多出來的功能又該怎麼實現呢?如果你熟悉Android關於設置控件(比如TextView、Button等)的點擊事件/長按事件/觸摸事件,或許你會恍然大悟,看看下面的代碼,怎麼實現設置控件的點擊事件

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
});
button.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return false;
            }
});
button.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                return false;
            }
});

點擊事件由OnClickListener接口負責,長按事件由OnLongClickListener接口負責,觸摸事件由OnTouchListener接口負責,開發者要處理什麼事件,只需要實現相應的接口。這樣的話不是可以解決上面出現的兩個問題

  • 接口通用導致代碼多餘的問題;
  • 接口專一導致功能欠缺的問題.

接着再看看下面關於Android屬性動畫監聽的接口實現,是不是覺得Android系統關於實現動畫的監聽怎麼違背了接口隔離原則,實現動畫監聽接口,還得重寫裏面的四個方法,太多餘了吧!其實可能Android系統開發者當初在設計這個接口的時候,考慮到開發者可能需要同時使用到其中幾個方法,所以一併提供了.當然,這也有好處,也是有壞處的,爲了完善這個接口,Android系統開發者也重新提供了另一接口AnimatorListenerAdapter,在該接口中已對AnimatorListener的方法進行了空實現,開發者只需要重寫所需的方法即可.

Button button = new Button(this);
     ObjectAnimator animator = ObjectAnimator.ofFloat(button, "rotationX", 0, 360);
     animator.addListener(new Animator.AnimatorListener() {

            @Override
            public void onAnimationStart(Animator animation) {

            }
            @Override
            public void onAnimationEnd(Animator animation) {

            }
            @Override
            public void onAnimationCancel(Animator animation) {

            }
            @Override
            public void onAnimationRepeat(Animator animation) {

            }
});

總結:在開發中,應追求專一接口,這樣可以避免很多的問題;那麼假設你在開發項目的第一版本時,寫了上面的Hobit接口,當某天由於需求變更而得去增加愛好選項,那麼這時是否將增加的愛好選項添加到Hobit接口裏,這樣是不是會導致項目之前所有實現Hobit接口的地方都得進行更改,這樣就麻煩~~,所以專一接口是能避免很多問題的

附加:上面Hobit接口關於愛好的,或許列舉得不是很好,但大致瞭解就好.

Ⅲ.依賴倒置原則

在傳統軟件的開發中,通常是接口或抽象類依賴於具體類,當具體類有變動,那麼就得改變其接口或抽象類,而依賴倒置原則的出現,正是將這一傳統的依賴倒置過來,使得具體類依賴於接口或抽象類。

現在假設有這麼一需求,用戶輸入信息後點擊提交,系統負責讀取、校驗和永久存儲,下面是Android代碼實現:

findViewById(R.id.tv_submit).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String userInput = mEditText.getText().toString();

                if(!TextUtils.isEmpty(userInput) && inputNews.check(userInput)){   //驗證輸入的數據是否符合業務邏輯
                    SQLiteDatabase db = new DbOpenHelper(ProviderActivity01.this).getWritableDatabase();
                    ContentValues contentValues = new ContentValues();
                    contentValues.put("input", userInput);
                    db.insert("user", null, contentValues);
                }else{
                    Toast.makeText(ProviderActivity01.this,"抱歉,你輸入的數據不合法",Toast.LENGTH_SHORT).show();
                }
            }
});

一個普遍的現象,高層級模塊依賴於底層級模塊,比如上面代碼中的UI層依賴業務層,業務層依賴數據層。那麼如何用抽象來實現依賴倒置原則,解決上面高層模塊依賴底層模塊的現象呢?另一方面,我們也不想要一個簡單的完整的類來完成所有的事情,這時就可以想到單一職責原則,將各個職能進行劃分,來優化上面的代碼.

數據層

public interface DataLayer {
        void insert(String value);
    }   

    /**實現類*/
public class DataLayerImpl implements DataLayer{
        private Context mContext;

        public DataLayerImpl(Context context){
            mContext = context;
        }
        @Override
        public void insert(String value){
            SQLiteDatabase db = new DbOpenHelper(mContext).getWritableDatabase();

            ContentValues contentValues = new ContentValues();
            contentValues.put("input",value);
            db.insert("user",null,contentValues);
        }
    }

業務層

public interface BusinessLayer {
        void check(String str);
    }

    /**實現類*/
public class BusinessLayerImpl implements BusinessLayer {
        private DataLayer mDataLayer;
        public BusinessLayerImpl(DataLayer mDataLayer) {
            this.mDataLayer = mDataLayer;
        }
        @Override
        public void check(String str){
            //...業務邏輯校驗,略
            mDataLayer.insert(str);
        }

    }

UI層

public class ProviderActivity extends AppCompatActivity {

        private EditText mEditText;
        private DataLayer mDataLayer;
        private BusinessLayer mBusinessLayer;

        @Override
        protected void onCreate(Bundle savedInstanceState){
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_provider);
            initView();

            findViewById(R.id.tv_submit).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    String userInput = mEditText.getText().toString();

                    if(!TextUtils.isEmpty(userInput)){
                        mDataLayer = new DataLayerImpl(ProviderActivity01.this);
                        mBusinessLayer = new BusinessLayerImpl(dataLayer);
                        mBusinessLayer.check(userInput);
                    }else{
                        Toast.makeText(ProviderActivity01.this,"抱歉,你輸入的數據不合法",Toast.LENGTH_SHORT).show();
                    }
                }
            });

    }

數據層和業務層的解耦,通過接口和構造注入解決;在面向對象的世界裏,類與類之間可以有這麼幾種關係:

  • 零耦合:表現在兩個類之間沒有任何耦合關係;
  • 具體耦合:表現在一個類對另一個類的直接引用;
  • 抽象耦合:表現在一個具體類和一個抽象類之間;

那上面的僞代碼還是主要體現在了抽象耦合,通過對業務層的構造函數傳入數據層的接口,這樣久使得依賴關係存在了最大的靈活性。最後,我們高層級的模塊都依賴於抽象了(接口)。更進一步,我們的抽象不依賴於細節,它們也依賴於抽象。
總結上面的僞代碼,UI 層是依賴於業務邏輯層的接口的,業務邏輯層的接口依賴於數據層的接口.

總結:依賴倒置原則的使用前提得看場景,其具體的體現是在抽象類型上,不要爲了抽象而去抽象,一些相對穩定、保持不變的類也沒有使用的必要.

Ⅳ.總結…繼續…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章