前言
時間如梭,少年仍在奔跑!!!
Ⅰ.里氏替換原則
簡述:關於里氏替換原則,可能在每天的代碼中都有出現關於這一原則的使用,只是一直都在使用,而沒有意識到這就是所謂的里氏替換原則。里氏替換原則的思想是:”基類可實現的功能,子類也可以實現”,
下面代碼,假設有一個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 層是依賴於業務邏輯層的接口的,業務邏輯層的接口依賴於數據層的接口.