前言
時間如梭,少年仍在奔跑。。。 Ⅰ.裡氏替換原則
簡述:關於裡氏替換原則,可能在每天的代碼中都有出現關於這一原則的使用,只是一直都在使用,而沒有意識到這就是所謂的裡氏替換原則。裡氏替換原則的思想是:”基類可實現的功能,子類也可以實現”,
下面代碼,假設有一個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 層是依賴於商務邏輯層的介面的,商務邏輯層的介面依賴於資料層的介面. 總結:依賴倒置原則的使用前提得看情境,其具體的體現是在抽象類別型上,不要為了抽象而去抽象,一些相對穩定、保持不變的類也沒有使用的必要. Ⅳ.總結…繼續…