面向对象五大基本原则(二)

来源:互联网 发布:中国高铁 知乎 编辑:程序博客网 时间:2024/05/29 04:49

前言

时间如梭,少年仍在奔跑!!!

Ⅰ.里氏替换原则

简述:关于里氏替换原则,可能在每天的代码中都有出现关于这一原则的使用,只是一直都在使用,而没有意识到这就是所谓的里氏替换原则。里氏替换原则的思想是:”基类可实现的功能,子类也可以实现”,

下面代码,假设有一个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 层是依赖于业务逻辑层的接口的,业务逻辑层的接口依赖于数据层的接口.

总结:依赖倒置原则的使用前提得看场景,其具体的体现是在抽象类型上,不要为了抽象而去抽象,一些相对稳定、保持不变的类也没有使用的必要.

Ⅳ.总结…继续…

0 0
原创粉丝点击