美文网首页
Java与Android代码的根基-面向对象六大基本原则 定义+

Java与Android代码的根基-面向对象六大基本原则 定义+

作者: 全栈小土堆堆堆 | 来源:发表于2019-04-11 22:08 被阅读0次

    前言:有色无阉割版请参见 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,我只需要知道,存图、取图 就可以了。你也只需要开放这两个接口给我就好了。我根本不需要知道你是怎么设置参数的、怎么设置缓存的。我根本不关心,也不应该将这些业务逻辑暴露给调用者,

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

    相关文章

      网友评论

          本文标题:Java与Android代码的根基-面向对象六大基本原则 定义+

          本文链接:https://www.haomeiwen.com/subject/gskfwqtx.html