Java与Android代码的根基-面向对象六大基本原则 定义+举例说明,这是周会分享讲满3小时的文章

前言:有色无阉割版请参见 Android面向对象六大基本原则-团队技术分享

这是团队技术分享前,编写的文档,一篇文章,讲满了技术分享的3个小时。
围绕ImageLoader通俗易懂的讲解了各原则的使用场景与优缺点。帮助非常的大!不管是新的,还是老的程序员,都推荐来看一下。复习一下。相比理解了,记住了,更推荐朋友们能够面向大家讲出来,进步更大。

Android 面向对象六大基本原则

本文几个方面来介绍

1、面向对象六大基本原则的定义+举例说明
2、代码中的使用
3、优缺点,
4、小结
会围绕一个图片加载框架,一步步讲解代码优化代码,从而表明六大基本原则的应用与效果。
因为这是设计模式的基础与核心参照原则

第一章、走向灵活软件之路-面向对象六大基本原则

前言:面向对象六大基本原则,是代码编写和代码优化的基础,也是核心,虽然是基础,但是非常重要,而且没有深入了解过的话,一知半解,也不好。本次围绕 图片加载库 举例说明。深入浅出透彻的理解什么是六大基本原则,如何运用。以及优缺点。

基本原则就好比 建造房租一样,首先要打地基,明白建造房租的流程,哪些节点是可以优化的,比如水电如何优化、开关如何设计、接口如何预留等等。明白了基本原则后,做任何事,都会在正常安全的范围内,不会导致大错

这种例子 生活中其实比比皆是,比如相机、手机的设计和制造 都有属于各自领域的基本原则,遵循原则可以让一切变得有规律、顺畅、符合逻辑。

如果某家单反生产商,搞特殊,相机镜头搞了个18:9的接口,那基本上就废了,市面上所有的镜头接口都不适合他。无法更换镜头,也注定了,他永远都做不大。不管多牛逼,遵循基本原则,是首要前提。

比如一个摄影师,要拍人像,却用广角镜头,人当然可以拍出来,但是效果就差太多了。或者说P图的基本原则,磨皮、瘦脸、缩鼻翼,大眼、瘦腰、大长腿。遵循这些基本原则,不管是拍出来的照片、还是p出来的图片,都要比无章法的自由发挥好很多。

1.1 优化代码的第一步-【单一职责原则(SRP)】

1.1.1 定义,什么是单一职责原则?

     英文名称是 SIngle Responsibility Principle(SRP),定义为:就一个类而言,应该仅有一个引起他变化的原因。简单来说,一个类中,应该是一组相关性很高的函数、数据的封装。
     但是 单一职责的划分界限并不算很清晰,很多时候都需要靠个人经验来界定,最大的问题就算对职责的定义,什么是类的职责,以及怎么划分类的职责。

     比如:我经常看到一些Android开发在Activity中写Bean文件,网络数据处理,如果有列表的话Adapter 也写在Activity中,问他们为什么除了好找也没啥理由了,把他们拆分到其他类岂不是更好找,如果Activity过于臃肿行数过多,显然不是好事,如果我们要修改Bean文件,网络处理和Adapter都需要上这个Activity来修改,就会导致引起这个Activity变化的原因太多,我们在版本维护时也会比较头疼。也就严重违背了定义“就一个类而言,应该仅有一个引起它变化的原因”。

     当然如果想争论的话,这个模式是可以引起很多争论的,但请记住一点,你写代码不只是为了你也是为了其他人

     举个栗子:小到灯的开关、马桶的冲水按钮、大一点到房屋设计,厨房是厨房、卫生间是卫生间,电闸是单独在一个固定区域的。从来没见过卫生间和厨房是在一起的吧?也没见过,按一下马桶冲水按钮,既能冲水,又能充话费吧?

1.1.2 代码举例说明:

举例就举大家最熟悉的例子,以 图片加载库为例,可以分析下,这个类,在设计上有什么问题

public class ImageLoader{
    // 图片缓存
    LruCache<String,Bitmap> mImageCache;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    public ImageLoader(){
        initImageCache();
    }

    // 初始化
    private void initImageCache(){
        // 计算可使用的最大内存
        final int maxMenory = (int)(Runtime.getRuntime().maxMenory()/1024);
        // 取1/4的可用内存作为缓存
        final int cacheSize = maxMenory / 4;
        mImageCache = new LryCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key,Bitmap Bitmap){
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        }
    }

    public void displayImage(final String url,final ImageView ImageView){
        image.setTag(url);
        mExecutorService.submit(new Runnable(){
            @Override
            public void run(){
                Bitmap bitmap = downloadImage(url);
                if(bitmap == null){
                    return ;
                }
                // 图片显示
                if(imageView.getTag().equals(url)){
                    imageView.setImageBitmap(bitmap)
                }
                // 图片缓存
                mImageCache.put(url,bitmap);
            }
        });
    }

    public Bitmap downloadImage(String imageUrl){
        Bitmap bitmap = null;
        try{
            URL url = new URL(ImageUrl);
            final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        }cache(Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }

}</pre>

问题解析:1、耦合严重 2、拓展性,灵活性差。

主要表现在:图片缓存逻辑 与 展示逻辑,写在同一个类,随着业务增多,个性化需求增多,代码会越来越复杂和无法维护

1.1.3 通过单一职责原则优化

public class ImageLoader{

    // 图片缓存-新建一个图片缓存类
    ImageCache mImageCache = new ImageCache() ;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    public void displayImage(final String url,final ImageView ImageView){
        image.setTag(url);
        mExecutorService.submit(new Runnable(){
            @Override
            public void run(){
                Bitmap bitmap = downloadImage(url);
                if(bitmap == null){
                    return ;
                }
                // 图片显示
                if(imageView.getTag().equals(url)){
                    imageView.setImageBitmap(bitmap)
                }
                // 图片缓存
                mImageCache.put(url,bitmap);
            }
        });
    }

    public Bitmap downloadImage(String imageUrl){
        Bitmap bitmap = null;
        try{
            URL url = new URL(ImageUrl);
            final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        }cache(Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }

}</pre>

将图片缓存逻辑拆分出来。

public class ImageCache{

    // 图片LRU缓存
    LruCache<String,Bitmap> mImageCache;

    // 初始化
    private void initImageCache(){
        // 计算可使用的最大内存
        final int maxMenory = (int)(Runtime.getRuntime().maxMenory()/1024);
        // 取1/4的可用内存作为缓存
        final int cacheSize = maxMenory / 4;
        mImageCache = new LryCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key,Bitmap Bitmap){
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        }
    }

    public void put(String url ,Bitmap bitmap){
        mImageCache.put(url,bitmap);
    }

    public Bitmap get(String url){
        return mImageCache.get(url);
    }

}

优点:将ImageLoader一拆为二,ImageLoader只负责图片加载的逻辑,ImageCache只负责处理图片缓存的逻辑,这样相互代码量少了。职责也清晰了。 修改任一方逻辑,都相互不干预

小结:一个函数的职责,每个人都有自己的看法,这要根据经验而定,具体逻辑而定,但是也有基本指导原则:两个完全不一样的功能,就不应该放在一个类中,一个类中应该是一组相关性很高的函数、数据的封装。这需要不断的审视代码,根据具体业务、功能进行拆分优化;

1.2 让程序更稳定、更灵活-**【开闭原则(OCP)】重要

1.2.1 定义,什么是开闭原则

开闭原则的英文全称是 Open Close Principle ,缩写是OCP,定义为:软件中的对象(类、模块、函数等)应该对于拓展是开放的,但是,对于修改是封闭的

实际应用举例说明:在软件生命周期内,因为变化、升级和维护等原因,需要对软件原有代码进行修改时,可能会错误引入原本已经经过测试的旧代码中,破坏原有系统,本来是正常的,一修改 全挂了。 。 。

因此,当软件需要变化时,我们应该尽量通过拓展的方式来实现变化,而不是通过修改已有代码来实现。但是,实际开发中,拓展代码、在原有基础上修改,是经常同时存在的

举个栗子:生活中常见的,电源插座,当位置不够的时候,怎么操作?没有人 把插座拿去改造吧?都是再找个插头,续上。这其实就是开闭原则,我可以支持任意拓展,但是你不能来随意修改我。

再比如,房屋的设计,电表是有个电箱单独存放的,是开放的。没见过,把电表砌在卫生间墙里面的吧?万一晚上电表爆了,难道要抹黑去一锤子80去敲墙 找电表再替换?这显然是不可能的。电表都单独存在,并且易替换、易维护。修改、替换电表,对我房子没有任何影响。

1.2.2 代码举例说明

以上面的ImageLoader展示与缓存为例,通过内存缓存解决了每次从网络加载图片的问题,但是,Android应用内存有限,并且具有易失性,应用重启后,原有加载过的图片将丢失

因此,考虑加入SD卡缓存功能来解决这一问题,说干就干,初始代码如下:

新建一个SD卡缓存实现类:

public class DiskCache {

    static String cacheDir = "sdcard/cache/"

    // 从缓存中获取图片
    public Bitmap get(String url){
        return BitmapFactory.decodeFile(cacheDir +url);
    }

    // 将图片缓存SD卡
    public void put(String url,Bitmap bitmap){
        FileOutPutStream fileOutPutStream = null ; 
        try{
            fileOutPutStream = new FileOutPutStream(cacheDir + url);
            bitmap.compress(CompressFormat.PNG,100,fileOutPutStream);

        }cache(Exception e){
            e.printStackTrace();
        }finally{
            if(fileOutPutStream!=null){
                try{
                    fileOutPutStream.close();
                }cache(Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
}

因为,要将图片缓存到SD卡中,对应ImageLoader代码修改如下

public class ImageLoader{

    // 内存缓存
    ImageCache mImageCache = new ImageCache() ;
    // SD 卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 是否
    boolean isUseDiskCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    public void displayImage(final String url,final ImageView imageView){
        // 判断使用哪种缓存
        Bitmap bitmap = isUseDiskCache ?mDiskCache.get(url):mImageCache.get(url);
        if(bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return ;
        }
        // 如果没有缓存,交给线程池下载...
    }

    public void useeDiskCache(boolean useDiskCache){
        isUseDiskCache = useDiskCache;
    }

}</pre>

用户可以自由选择,使用内存缓存 或者 SD卡缓存

问题:使用内存缓存时,不能同时SD卡缓存,使用SD卡缓存,不能同时使用内存缓存

正常的策略:优先从内存缓存,如果内存缓存没有则使用SD卡缓存,如果SD卡也没有,才从网络下载图片,这才是好的缓存策略

于是按照这个思路,又优化代码。。。新增一个DoubleCache类:

/*
* 双缓存,获取图片时,从内存中获取,如果内存中没有缓存图片,再从SD卡中获取。
* 缓存图片在内存和SD卡中都缓存一份
*/
public class DoubleCache{
    ImageCache mMemoryCache = new ImageCache();
    DiskCache mDiskCache = new DiskCache();

    // 优先从内存缓存中获取图片,如果没有,再从SD卡中获取图片
    public Bitmap get(String url){
        Bitmap bitmap =  mMemoryCache.get(url);
        if(bitmap==null){
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    public void put(String url,Bitmap bitmap){
        mMemoryCache.put(url,bitmap);
        mDiskCache.put(url,bitmap);
    }

}</pre>

再看看最新的ImageLoader类:

public class ImageLoader{

    // 内存缓存
    ImageCache mImageCache = new ImageCache() ;
    // SD 卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 双缓存
    DoubleCache mDoubleCache = new DoubleCache();
    // 是否使用SD卡缓存
    boolean isUseDiskCache = false;
    // 是否使用双缓存
    boolea isUseDoubleCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    public void displayImage(final String url,final ImageView imageView){
        // 判断使用哪种缓存
        Bitmap bitmap = null;
        if(isUseDoubleCache){
            bitmap = mDoubleCache.get(url);
        }else if(isUseDiskCache){
            bitmap = mDiskCache.get(url);
        }else{
            bitmap = mMemoryCache.get(url);
        }
        if(bitmap!=null){
            imageView.setImageBitmap(bitmap);
        }
        // 如果没有缓存,交给线程池下载...
    }

    public void useeDiskCache(boolean useDiskCache){
        isUseDiskCache = useDiskCache;
    }

    public void useDoubleCache(boolean useDoubleCache){
        isUseDoubleCache = useDoubleCache;
    }

}

问题:1、每次修改缓存逻辑,都需要修改ImageLoader类,然后通过一个布尔变量来选择使用哪种缓存,这样可能引入bug,并且使得代码越来越臃肿,容易出错

** 2、用户不能自己实现缓存策略注入到ImageLoader中,可拓展性差,可拓展性是框架最重要的特性之一**

回到开闭原则的定义,(类、模块、函数等)对于拓展应该是开放的,但是对于修改是封闭的。上面的代码明显不复合开闭原则。软件优化时,尽量通过拓展的方式来实现变化,而不是通过直接修改已有代码来实现

1.2.3 通过开闭原则重新设计与优化

public class ImageLoader{

    // 内存缓存-修改为抽象接口
    ImageCache mImageCache = new ImageCache() ;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = ExecutorService.newFixedThreadPool(Runtime.getRunTime().availiableProcesssors());

    // 注入缓存实现
    public void setImageCache(ImageCache cache){
        mImageCache = cache;
    }

    public void displayImage(String url,ImageView imageView){
         Bitmap bitmap = mImageCache.get(url);
         if(bitmap != null){
            imageView.setImageBitmap(bitmap);
            return;
         }
         // 图片没有缓存,提交到线程池中下载图片
         submitLoadRequest(imageUrl,imageView);
    }

    private void submitLoadRequest(final String imageUrl,final ImageView imageView){
        imageView.setTag(imageUrl);
        mExecutorService.submit(new Runnable(){
            @Override
            public void run(){
                Bitmap bitmap = downloadImage(imageUrl);
                if(bitmap==null){
                    return;
                }
                if(imageView.getTag().equals(imageUrl)){
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(imageUrl,bitmap);
            }
        });
    }

    public Bitmap downloadImage(String imageUrl){
        Bitmap bitmap = null;
        try{
            URL url = new URL(ImageUrl);
            final HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        }cache(Exception e){
            e.printStackTrace();
        }
        return bitmap;
    }

}

这里的ImageCache并不是原有的ImageCache,而是提取成了一个图片缓存接口,用来抽象图片缓存功能,具体声明如下:

// 图片缓存接口
public interface ImageCache{

    public Bitmap get(String url);

    public void put(String url,Bitmap bitmap);

}</pre>

ImageCache定义了获取、缓存两个函数,缓存的key是图片的url,值是图片本身,内存缓存,SD卡缓存,双缓存都实现了该接口,看看几个缓存的实现:

public class MemoryCache implements ImageCache{

     private LruCache<Stirng,Bitmap> mMemeryCache;

     public MemoryCache(){
        // 初始化LRU缓存
     }

     @Override
     public Bitmap get(String url){
        return mMemeryCache.get(url);
     }

     @Override
     public void put(String url,Bitmap bitmap){
        mMemeryCache.put(url,bitmap);
     }
}

public class DiskCache implements ImageCache{
     @Override
     public Bitmap get(String url){
        // 从本地文件获取图片
     }

     @Override
     public void put(String url,Bitmap bitmap){
        // 将Bitmap写入文件中
     }

}

public class DoubleCache implements ImageCache{
    ImageCache mMemeryCache = new MemoryCache();
    ImageCache mDiskCache = new DiskCache();

    // 先从内存缓存中获取图片,如果没有,再从SD卡中获取图片
    public Bitmap get(String url){
        Bitmap bitmap = mMemeryCache.get(url);
        if(bitmap==null){
            bitmap = mDiskCache.get(url);
        }
        return bitmap;
    }

    // 将图片写入到内存和SD卡中
    public void put(String url,Bitmap bitmap){
        mMemeryCache.put(url,bitmap);
        mDiskCache.put(url,bitmap);
    }
}

细心的人可能发现了。ImageLoader类中增加了一个setImageCache(ImageCache cache)函数,用户可以童工该函数设置缓存实现,也就是通常说的依赖注入,设置方法如下:

ImageLoader imageLoader = new ImageLoader();
// 使用内存缓存
imageLoader.setImageCache(new MemoryCache());
// 使用SD卡缓存
imageLoader.setImageCache(new DiskCache());
// 使用双缓存
imageLoader.setImageCache(new DoubleCache());
// 用户自定义缓存
imageLoader.setImageCache(new Imagecache(){
    @Override
    public void put(Strign url,Bitmap bitmap){
        // 自定义将图片缓存策略
    }
    @Override
    public Bitmap get(String url){
        // 自定义从缓存中获取图片
    }
})

小结:在上述代码中,通过setImageCache(ImageCache cache)方法注入不同的缓存实现,不仅能够使ImageLoader更简单,健壮,也使得ImageLoader的可拓展性,灵活性更高。通过ImageCache接口,注入到ImageLoader中,可以实现千变万化的缓存策略,并且拓展这些缓存策略不会导致ImageLoader修改。这就是标准的开闭原则。

PS:当软件需要优化时,应该尽量通过拓展的方式来实现变化,而不是通过修改已有代码来实现 “应该尽量”说明并非绝对不可以修改原始类。如果代码恶心到吐,还是应该尽早的重构,具体要根据开发时间以及配套资源来判定。

1.3 构建拓展新更好的系统-【里氏替换原则(LSP)】

1.3.1 定义,什么里氏替换原则?

里氏替换原则英文全称是(LisKov SubStitution Principle),缩写是LSP,定义为:所有引用基类的地方,都能透明的使用其子类对象;

面向对象三大特点,继承、封装、多态,里氏替换就是依赖了继承、多态两大特性;通俗点讲,只要父类出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者根本就不需要知道父类还是子类,但是反过来就不行了,有子类的地方,父类未必能适应。。。

总结其实就两个字:抽象

举个栗子:耳机,老的有3.5mm接口的二级,新的有type-c接口的耳机或者蓝牙耳机。

手机只有一个,但是耳机我有十个,是什么让我可以任意的、方便的替换耳机,而手机不需要任何更改。核心就在于耳机接口的设计。 只要耳机是3.5mm的,我就可以支持。不管你是森海塞尔的、铁三角的,还是华为、小米的。随便你换。

还有就是单反相机,单反相机的镜头都是可替换的,广角、微距、人像定焦、一镜走天下等等。我既可以使用原厂的镜头,也可以使用第三方镜头替换,为什么可以做到这样?因为单反设计了镜头的接口,只要是符合单反接口的镜头。都可以支持。

试想一下,如果不设计这种接口,那会怎么样?那只能是小作坊。永远做不大了。

****1.3.2 代码举例说明**

[图片上传失败...(image-b23d7a-1554990639319)]

**1.3.3 通过里氏替换原则优化代码****

可以看上面讲过的 图片加载示例,其实已经很好的体现了里氏替换原则,MemoryCache、DiskCache、DoubleCache 都可以替换ImageCache的工作,并且保证正常运行。

小结:里氏替换原则与开闭原则生死相依、不离不弃 ,通过里氏替换原则达到对拓展的开放,对修改的关闭效果,俩原则同时强调了一个OOP的重要特性-抽象,因此,在开发过程中,运用抽象是走向代码优化的重要一步。

里氏替换原则核心原理是抽象,抽象又依赖于继承这个特性,在OOP中,继承的优缺点都特别明显,

优点有以下几个方面:

(1)代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性

(2)子类与父类基本相似,但又与父类有所区别

(3)提高代码的可拓展性

继承的缺点:

(1)集成是侵入性的,只要继承就必须拥有父类的所有属性和方法

(2)可能造成子类代码冗余,灵活性降低,因为子类必须拥有父类的属性和方法;

1.4 让项目拥有变化的能力-【依赖倒置原则(DIP)】****

1.4.1 定义 什么是依赖倒置原则

依赖倒置英文全称(Dependence Inversion Principle) 缩小是DIP

定义:高层模块不应该依赖低层模块,两个都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

依赖倒置原则有以下几个关键点:

(1)高层模块不应该依赖底层模块,两者应该都依赖其抽象

(2)抽象接口不应该依赖具体实现

(3)实现类应该依赖抽象接口

解释说明:在Java中,抽象就是指接口或者抽象类,两者都是不能直接被实例化的;细节就是实现类,实现接口或者继承抽象类而产生的就是细节,也就是可以加上一个关键字new产生的对象。高层模块就是调用端,低层模块就是具体实现类。
依赖倒置原则在Java中的表现就是:模块间通过抽象发生,实现类之间不发生直接依赖关系,其依赖关系是通过接口或者抽象类产生的。如果类与类直接依赖细节,那么就会直接耦合,那么当修改时,就会同时修改依赖者代码,这样限制了可扩展性。

举个栗子:单反相机不应该直接依赖某个镜头型号,即使这个镜头跟单反可以匹配。而是依赖4:3镜头连接接口,相机支持4:3接口的所有镜头。而镜头本身也是遵循4:3接口。那么两者就可以关联到一起。****相机就是高层模块,镜头就是具体实现,而抽象接口 就是4:3的镜头连接接口。

类似的有 插座、手机耳机、usb、type-c、hdmi等

1.4.2 代码举例

public class ImageLoader{

    //内存缓存(直接依赖细节)
    MemoeryCache mMenoryCache =  new MenoryCache();

    // 加载图片到ImageLoader中
    public void displayImage(String url,ImageView imageView){
        Bitmap bitmap = mMenoryCache.get(url);
        if(bitmap==null){
            downloadImage(url,imageView);
        }else if(bitmap != null){
            imageView.setImageBitmap(bitmap);
        }
    }

    public void setImageCache(MenoryCache cache){
        mMemeryCache = cache;
    }
    // 下载图片
    ... 
}

问题分析:可以看到,ImageLoader 直接依赖了MemoryCache,MemoeryCache是具体实现,不是一个抽象类或者接口,当MemoryCache不能满足ImageLoader恶如需要被其他缓存实现替换时,此时就必须要修改ImageLoader的代码。

而且,抽象接口的定义要具体化,不要定义不需要的接口。让类的依赖关系也建立在最小的接口上。而不是提供一大堆接口,这样很臃肿,也会存在耦合不易修改。

小结:这与前文的开闭原则,其实也是对应的。里氏替换原则能够很好的保证代码的可拓展性,能随时替换具体实现并做到不影响调用端使用者,有拥抱变化的能力。让代码更灵活。其核心就是:抽象

1.4.3 通过依赖倒置原则优化代码

见1.2 开闭原则即可

1.5 系统拥有更高的灵活性-【接口隔离原则(ISP)】

1.5.1 定义 什么是接口隔离原则?

接口隔离英文全称(InterfaceSegregation Principles),缩写是ISP

定义:一个类对另一个类的依赖应该建立在最小的接口上。臃肿的接口,拆分细化成更小和更具体的接口,这样客户端将会只需要知道他们感兴趣的方法,接口隔离的原则与目的是系统解开耦合,从而容易被重构、更改和重新部署

换种说法:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

采用接口隔离原则对接口进行约束时,要注意以下几点:

(1)接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。

(2)为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。

(3)提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

举个栗子:灯的****开关、马桶的冲水按钮等等。灯的开关就是专门来控制开关的,冲水按钮就是专门冲水的。一看就知道干嘛的。而不是,我想关灯,要从无数按钮中查找。对我来说使用成本太大。而且无法量产,这是不科学的。

1.5.2 代码举例说明

// 将图片韩村到内存中
public void put(String url,Bitmap bmp){

    FileOutPutStream fileOutPutStream = null ; 
    try{
        fileOutPutStream = new FileOutPutStream(cacheDir+url);
        bmp.compress(CompressFormat.PNG,100,fileOutPutStream);
    }cache(Exception e){
        e.printStackTrace();
    }finally{
            // 注意这一段代码
            if(fileOutPutStream!=null){
                try{
                    fileOutPutStream.close();
                }cache(Exception e){

                }
            }// end if
    }//end if finally

}</pre>

代码分析:代码可读性非常的差,各种try...cache嵌套的都是些简单的代码,但是会严重影响代码的可读性,写代码的时候,也容易发生错误。

追溯fileOutPutStream.close()方法,发现,fileOutPutStream的clsoe方法,实现了Closeable接口,如果我的项目中,而系统中,有100多个类都实现了Closeable接口。这意味着,在使用这些类结束,最后关闭这100多个对象的时候,都需要写上面finally里类似的代码。

这是无法容忍的。

既然都实现了Closeable接口,干脆把这段代码抽离出来,新建一个方法统一关闭这些对象。以后只要是实现了Closeable接口的类,都可以使用。

1.5.3 通过接口隔离原则优化代码

public final class CloseUtils{

    private CloseUtils(){ }

    /**
    * 关闭Closeable对象
    */
    public static void closeQuitely(Closeable closeable){
        if(closeable != null){
            try{
                closeable.close();
            }cache(Exception e){
                e.printStackTrace();
            }
        }
    }
}

再看看,把这段代码运用到刚才put方法中的效果:

public void put(String url,Bitmap bmp){

    FileOutPutStream fileOutPutStream = null ; 

    try{
        fileOutPutStream = new FileOutPutStream(cacheDir+url);
        bmp.compress(CompressFormat.PNG,100,fileOutPutStream);
    }cache(Exception e){
        e.printStackTrace();
    }finally{
        CloseUtils.closeQuitely(fileOutPutStream);
    }

}

代码明显简洁了很多,并且,这个类可以应用到所有实现了Closeable的对象中,保证了代码的重用性;

小结:CloseUtils的closeQuality方法的基本原理,就在于依赖了Closeable抽象而不是具体实现(其实就是1.4中的依赖倒置原则),建立在最小化依赖原则的基础上,它只需要知道这个对象是可关闭的,其它的一概不关心。这就是接口隔离。

思考:设想,如果关闭一个对象时,它却暴露了其它的接口函数,比如OutPutStream的write方法,这样使得更多的细节暴露在客户端代码面前,不仅没有很好的隐藏实现,还增加了接口的使用难度。还有上文说到的ImageLoader中的ImageCache,调用者ImageLoader,只需要知道该缓存对象有存、取图片的接口即可,其它的根本不关心。将庞大的接口,拆分到更细颗粒度的接口当中,这样代码就会有更低的耦合性,更高灵活性。

Bob大叔(Robert C Martin)在21世纪早期,将 单一职责、开闭原则、里氏替换、接口隔离 以及 依赖倒置5个原则定义了SOLID原则,作为面向对象变成的5个基本原则。当这些原则被启用时,系统软件变得更加清晰、简单、最大程度的拥抱变化。

1.1-1.5的总结和概括,其实可以为:抽象、单一职责、最小化。

1.6 更好的拓展性-【迪米特原则(LOD)】

1.6.1 定义,什么是迪米特原则?

定义:迪米特原则英文全称 (LAW of Demeter) ,缩写是LOD,也被称为最少了解原则(Least Knowledage Principle),一个对象应该对其它对象有最少的了解**

通俗的讲,一个类应该对自己需要耦合或者调用类知道的最少,类的内部,如何实现与调用者或者依赖者没有关系、调用者或者依赖者,只需要知道它需要的方法即可,其具体实现,内部如何骚造作,一概不管。

类与类之间的关系越密切,耦合程度就越大,当一个类需要改变时,对另一个类的影响也越大。

举个栗子:图片加载类缓存类,ImageCache,MemoryCache、DiskCache,对于调用者ImageLoader,我只需要知道,存图、取图 就可以了。你也只需要开放这两个接口给我就好了。我根本不需要知道你是怎么设置参数的、怎么设置缓存的。我根本不关心,也不应该将这些业务逻辑暴露给调用者,

这样会增加我使用的成本也会导致不必要的问题发生。比如调用者调用约定方法后,又同时调用了你提供的其它方法。这就有可能导致不必要的错误。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343