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

前言

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

Ⅰ.里氏替换原则

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

下面代码,假设有一个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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值