六大原则(练习笔记)

一、单一职责原则

定义:就一个类而言,应该仅有一个引起他变化的原因
问题:对职责的定义,什么是类的职责,如何划分类的职责
需求:实现图片加载,将图片缓存起来

package com.example.homework;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.widget.ImageView;

import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * 图片加载类
 * 实现图片加载并将图片缓存起来
 * */
public class ImageLoaderold {
    //图片缓存
    LruCache<String, Bitmap> mImageCache;
    //线程池,线程数量为CPU的数量
    ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //UI Handler
    Handler mUiHandler=new Handler(Looper.getMainLooper());
    public ImageLoaderold(){
        initImageCache();
    }

    private void initImageCache() {
        //计算可使用的最大内存
        final int maxMemory=(int) (Runtime.getRuntime().maxMemory()/1024);
        //取四分之一的可用内存作为缓存
        final int cacheSize=maxMemory/4;
        mImageCache=new LruCache<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){
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap=downloadImage(url);
                if (bitmap==null){
                    return;
                }
                if (imageView.getTag().equals(url)){
                    updateImageView(imageView,bitmap);
                }
                mImageCache.put(url,bitmap);
            }

            private void updateImageView(ImageView imageView, final Bitmap bmp) {
                mUiHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(bmp);
                    }
                });
            }

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

问题:所有功能都写在一个类里
解决:拆分,把各个功能独立出来,让他们满足单一职责原则
修改:
在这里插入图片描述
将ImageLoader一分为二,ImageLoader只负责图片加载的逻辑,添加一个ImageCache类用于处理图片缓存,并且 ImageCache只负责处理图片缓存的逻辑
1、当缓存相关的逻辑需要修改时,不需要修改ImageLoader类
2、图片加载的逻辑需要修改时,也不会影响到缓存处理逻辑

package com.example.homework;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 图片加载类
* 实现图片加载并将图片缓存起来
* */
public class ImageLoader {
    //图片缓存
    LruCache<String, Bitmap> mImageCache;
    //线程池,线程数量为CPU的数量
    ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    //UI Handler
    Handler handler= new Handler(Looper.getMainLooper());

    private void updateImageView(final ImageView imageView,final Bitmap bmp){
        handler.post(new Runnable(){

            @Override
            public void run() {
                imageView.setImageBitmap(bmp);
            }
        });
    }
    //加载图片
    public void displayImage(final String url,final ImageView imageView){
        Bitmap bitmap=mImageCache.get(url);
        if (bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return;
        }
        imageView.setTag(url);
        mExecutorService.submit(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap=downloadImage(url);
                if (bitmap==null){
                    return;
                }
                if (imageView.getTag().equals(url)){
                    updateImageView(imageView,bitmap);
                }
                mImageCache.put(url,bitmap);
            }
        });
    }
    public Bitmap downloadImage(String imageUrl){
        Bitmap bitmap=null;
        URL url= null;
        try {
            url = new URL(imageUrl);
            final HttpURLConnection conn=(HttpURLConnection) url.openConnection();
            bitmap= BitmapFactory.decodeStream(conn.getInputStream());
            conn.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }
}

ImageCache类:

package com.example.homework;

import android.graphics.Bitmap;
import android.util.LruCache;

/*
* 处理图片缓存
* */
public class ImageCache {
    //图片LRU缓存
    LruCache<String, Bitmap>mImageCache;
    public ImageCache(){
        initImageCache();
    }

    private void initImageCache() {
        //计算可使用的最大内存
        final int maxMemory=(int) (Runtime.getRuntime().maxMemory()/1024);
        //取四分之一的可用内存作为缓存
        final int cacheSize=maxMemory/4;
        mImageCache=new LruCache<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);
    }
}

小结:
1、两个完全不一样的功能不应该放在一个类中
2、一个类应该是一组相关性很高的函数、数据的封装

二、开闭原则

定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,对于修改时封闭的
在现实开发中,只通过继承的方式来升级、维护原有系统不现实,往往修改原有代码和扩展代码同时存在
继承:已存在的 实现类对于修改是封闭的,但是新的实现类可以通过覆写父类的接口应对变化
案例:
问题:缓存系统中内存缓存解决了每次从网络加载图片的问题,但是内存有限,且容易失去,重新启动程序之后需要重新下载
解决:引入SD卡缓存

DiskCache 类 ,将图片缓存到SD卡中

package com.example.homework;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 图片加载类
* 实现图片加载并将图片缓存起来
* */
public class ImageLoader {
    //内存缓存
    ImageCache mImageCache=new ImageCache();
    //SD卡缓存
    DiskCache mDiskCache=new DiskCache();
    //是否使用SD卡缓存
    boolean isUseDiskCache=false;
    //线程池,线程数量为CPU的数量
    ExecutorService mExecutorService= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    //加载图片
    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  useDiskCache(boolean useDiskCache){
        isUseDiskCache=useDiskCache;
    }
}


新增DiskCache添加了SD卡缓存的功能。
通过 useDiskCache方法来对使用哪种缓存进行设置 :

 public void  useDiskCache(boolean useDiskCache){
        isUseDiskCache=useDiskCache;
    }
    ImageLoader imageLoader=new ImageLoader();
    //使用SD卡缓存
    imageLoader.useDiskCache(true);
    //使用内存缓存
    imageLoader.useDiskCache(false);

问题:使用内存缓存时用户就不能使用SD卡缓存,而用户需要两种策略的综合,首先优先使用内存缓存,如果内存缓存没有图片再使用SD卡 缓存,如果SD卡中也没有,最后才从网络上获取
解决:新建一个双缓存类
在这里插入图片描述
更新ImageLoader
在这里插入图片描述
问题:每次 加入新的缓存方法都需要修改 原来的代码,会使原来的代码逻辑越来越复杂,而且 无法自定义缓存,用户 不能自己实现缓存注入到ImageLoader中,可扩展性差
解决:尽量通过扩展的方式 来实现变化,而不是通过修改已有的代码来实现
UML图:
在这里插入图片描述
重构ImageLoader
在这里插入图片描述
在这里插入图片描述
将ImageCache提取成一个图片缓存的接口,用来抽象图片缓存的功能,接口的声明如下:
在这里插入图片描述
定义了获取和缓存图片两个函数
缓存的key是图片的URL,值是图片本身
内存缓存、SD卡缓存、双缓存都实现了该接口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ImageLoader类中增加了一个函数setImageCache(ImageCache cache),用户可以通过该函数设置缓存实现。也就是依赖注入:
在这里插入图片描述
用户设置缓存实现:
在这里插入图片描述
总结:通过setImageCache(ImageCache cache)方法注入不同的缓存实现,使得ImageLoader更简单、健壮,使得其可扩展性、灵活性更高,MemoryCache、DiskCache、DoubleCache缓存图片的具体实现完全不一样,但是他们都实现了ImageCache接口,当用户需要 自定义实现缓存策略时,只需要新建一个实现ImageCache接口的类,然后构造该类的对象,并且通过setImageCache(ImageCache cache)方法注入到ImageLoader中,这样ImageLoader实现了可以变化的缓存 策略 ,而且扩展这些缓存策略不会 导致ImageLoader类的修改

三、里氏替换原则

定义1:如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T2定义的所有程序p在所有的对象o1都代换成o2时,程序p的行为没有发生变化,那么类型T1是类型T2的子类型(继承关系)
定义2:所有引用基类的地方必须能透明地使用其子类的对象(通俗的说,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常
面向对象三大特征:继承、封装、多态
里氏替换原则依赖于继承和多态

了解android中window和view的关系:
在这里插入图片描述
代码实现:
窗口类:
在这里插入图片描述
建立视图抽象,测量视图的宽高为共用代码,绘制实现交给具体的子类:
在这里插入图片描述
文本控件类的具体实现:
在这里插入图片描述
ImageView的具体实现:
在这里插入图片描述
总结:
上例中,Window依赖于View,而View定义了一个视图抽象,measure是各个子类共享的方法,子类通过覆写View的draw方法实现具有各自特色的功能,任何继承自view类的子类都可以传递给show函数,就是所说的里氏替换,通过里氏替换可以自定义各式各样的View,然后传递给Window,Window负责组织View,并且将View显示到屏幕上
里氏替换原则的核心原理是抽象,抽象又依赖于继承,继承有如下的优缺点:
优点:
1、代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性
2、子类与父类基本相似,但又与父类有所区别
3、提高代码的可扩展性
缺点:
1、继承是侵入性的,只要继承就必须拥有父类的所有属性和方法
2、可能造成子类代码冗余,灵活性降低,因为子类必须拥有父类的属性和方法
在这里插入图片描述
此图很好的反应了里氏替换原则,即MemoryCache、DiskCache、DoubleCache都可以替换ImageCache的工作,并且能够保证行为的正确性。ImageCache建立了获取缓存图片、保存缓存图片的接口规范,MemoryCache等根据接口规范实现了相应的功能,用户只需要在使用时指定具体的缓存对象 就可以动态的替换ImageLoader中的缓存策略,这就保证了可扩展性。

四、依赖倒置原则

依赖倒置原则有一下几个关键点:
(1)高层模块不应该依赖低层模块,两者都应该依赖其抽象
(2)抽象不应该依赖细节
(3)细节应该依赖抽象
抽象指的是接口或者抽象类,两者都是不能直接被实例化的。细节就是实现类,实现接口或者继承抽象类而产生的类就是细节,可以被直接实例化,也就是可以加上一个关键字new产生一个对象。高层模块就是调用端,低层模块就是具体实现类。
依赖倒置原则在java中的表现:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的,即面向接口编程或者面向抽象编程。
如ImageLoader直接依赖于MemoryCache,而MemoryCache是具体实现类,不是抽象类或者接口,这就导致ImageLoader直接依赖了具体细节,当MemoryCache不能满足ImageLoader而需要被其他缓存实现替换时,此时必须修改ImageLoader的代码
在这里插入图片描述
用户需要ImageLoader将图片同时缓存到内存和SD卡中,或者让用户自定义实现缓存,此时MemoryCache就无法满足需求。解决方法:将MemoryCache修改为DoubleCache,在DoubleCache中实现具体的缓存功能,修改ImageLoader如下:
在这里插入图片描述
还是依赖于实现类
解决方法:
依赖抽象,不依赖具体实现
针对图片缓存,建立ImageCache抽象,在抽象中增加get和put方法用以实现图片的存取每种缓存实现都必须实现这个接口,并且实现自己的存取方法。当用户需要使用不同的缓存实现时,直接通过依赖注入即可,也保证了系统的灵活性。
ImageCache缓存接口:
在这里插入图片描述
ImageLoader类:
在这里插入图片描述
总结:
在这里我们建立了ImageCache抽象,并且让ImageLoader依赖于抽象而不是具体细节。需求发生变化时,只需要实现ImageCache类完成相应的缓存功能,然后将具体的实现注入到ImageLoader即可实现缓存功能的替换,这就保证了缓存系统的高可扩展性,这就是依赖倒置原则。

五、接口隔离原则

定义1:客户端不应该依赖它不需要的接口
定义2:类间的依赖关系应该建立在最小的接口上
目的:系统解开耦合,从而容易重构、更改和重新部署
一个问题:在使用了OutputStream或者其他可以关闭的对象之后,我们必须保证他们最终被关闭了:
在这里插入图片描述
看上面这段代码,使用try…catch嵌套一些简单的代码,导致代码可读性差,并且容易出错。
解决:
java中Closeable接口,标识了一个可关闭的对象,FileOutputStream实现了这个接口
在这里插入图片描述
在这里插入图片描述
结论:closeQuitely方法可以运用到各类可关闭的对象中,保证了代码的重用性。CloseUtils的closeQuitely方法基本原理就是依赖于Closeable抽象而不是具体实现(依赖倒置原则),并且建立在最小化依赖原则的基础上,它只需要知道这个对象使可关闭的,其他的一律不关心,也就是我们说的接口隔离原则。
我们的案例中,ImageLoader中的ImageCache就是接口隔离原则的运用ImageLoader只需要知道该缓存对象有存、取缓存图片的接口即可,其他一概不管,这就使得缓存功能的具体实现对ImageLoader隐藏。
效果:
1、用最小化接口隔离实现了类的细节
2、使得系统具有更低的耦合性、更高的灵活性
前五大原则的总结:抽象、单一原则、最小化

六、迪米特原则

又称为最少知识原则:一个对象应该对其他对象有最少的了解,换句话说,只与直接的朋友通信。
什么叫做直接朋友?每个对象必然与 其他对象有耦合关系,两个对象之间的耦合就成为朋友关系,这种关系的类型有很多如 :组合、聚合、依赖
需求:我们租房子只要求房间的面积和租金,其他一律不管
在这里插入图片描述
中介:
在这里插入图片描述
租户:
在这里插入图片描述
小结:Tenant不仅仅依赖Mediator类,还频繁与Room类打交道,这样中介类的功能就被弱化了而且导致Tenant与Roon的耦合较高。
在这里插入图片描述
解决方法:解耦合
只和直接朋友通信
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
修改后:
在这里插入图片描述
将Room的判定操作移到Mediator类中,使得程序的耦合性降低,稳定性更好了。
SD卡缓存案例中:
ImageCache是用户的直接朋友,在SD卡缓存内部使用了jake wharton的DiskLruCache实现,这个DiskLruCache不属于用户的直接朋友,因为,用户不需要知道他的存在,只需要与ImageCache对象打交道即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值