面向对象的六大原则

在开发过程中,面向对象的六大原则非常重要,所以本节给大家带来了这六大原则的讲解,全文会以一个简单的ImageLoader为例进行讲解,为以后学习设计模式做铺垫。

一、单一职责原则(Single Responsibility Principle)

定义:就一个类而言,应该仅有一个引起它变化的原因,简单来说,一个类中应该是一组相关性很高的函数、数据的封装。
一般情况下,我们会这样写一个ImageLoader:

/**
 * 图片加载类
 */
public class ImageLoader {
    //图片缓存
    private LruCache<String,Bitmap> mImageCache;
    //线程池,数量为CPU数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public ImageLoader() {
        initCache();
    }

    private void initCache() {
        //可使用的最大内存
        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 value) {
                return value.getRowBytes()*value.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)){
                    imageView.setImageBitmap(bitmap);
                }
                mImageCache.put(url,bitmap);
            }
        });
    }

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

但是这个类的耦合性太严重,所有的功能都写在一个类中了,如果功能增加了,ImageLoader类就会越来越大,代码越来越差,也就是可维护性很差,就会导致图片加载会越来越差。为了符合单一原则,我们应该做如下修改:

/**
 * 图片加载类
 */
public class ImageLoader {
    //图片缓存
    private ImageCache mImageCache;
    //线程池,数量为CPU数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

     public ImageLoader() {
        mImageCache=new ImageCache();
    }

    public void displayImage(final String url, final ImageView imageView) {
            Bitmap bmp = mImageCache.get(url);
            if (bmp != null) {
                imageView.setImageBitmap(bmp);
                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)) {
                imageView.setImageBitmap(bitmap);
            }
            mImageCache.put(url, bitmap);
         }
      });
    }
    private Bitmap downloadImage(String imageUrl) {
         Bitmap bitmap = null;
            try {
                URL url = new URL(imageUrl);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                bitmap = BitmapFactory.decodeStream(conn.getInputStream());
            } catch (Exception e) {
                    e.printStackTrace();
            }
            return bitmap;
    }
}

/**
 * 图片缓存类
 */
public class ImageCache {
    //图片缓存
    private LruCache<String, Bitmap> mImageCache;

     public ImageCache(){
         initCache();
    }

    private void initCache() {
        //可使用的最大内存
        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 value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

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

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

此时ImageLoader只是负责加载图片的逻辑,ImageCache只是负责处理图片缓存的逻辑,这样职责也就非常的清晰了;当缓存相关的逻辑需要改变时,不需要修改ImageLoader类了,而图片加载的逻辑需要修改时也不会影响到缓存处理逻辑。

二、开闭原则(Open Close Principle)

定义:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。

因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。

我们仍然以ImageLoader为例,假想一下,如果现在要求把这个ImageLoader再加一个SD卡缓存,你会怎么加呢?

我们一般会这样写:刚刚咱们学习了“单一职责原则”,所以我们知道肯定不能在ImageLoader里面加一个方法进行SD卡缓存,也不会在ImageCache中加SD卡缓存,因为每一个类都应该是单一的功能,仅有一个引起它变化的原因嘛。那自然会想到我再建一个DisCache类进行SD的缓存操作,在ImageLoader中加入一个DisCahce引用,手动调用里面的功能不就可以了吗?那问题来了,万一你把ImageLoader改错了呢?特别是在开发一款上市软件的时候,那将会出大问题呀?就也是违背了“开闭原则”。

那我们应该怎么做呢?思路应该是这样的:使用接口,因为接口是一种规范,进行缓存功能的实现无非就是put与get方法,存入与取出。所以先定义一个接口ImageCache,再定义一个MemoryCache与DiskCache分别实现ImageCache接口,在ImageLoader中利用多态性持有ImageCahce的引用,只在用户传入哪个ImageCache就会执行哪个缓存。代码如下:

/**
 * 图片缓存接口
 */
public interface ImageCache {
    public Bitmap get(String imageUrl);
    public void put(String imageUrl,Bitmap bmp);
}

/**
 * 内存缓存
 */
public class MemoryCache implements ImageCache{
    //图片缓存
    private LruCache<String, Bitmap> mImageCache;

    public MemoryCache(){
         initCache();
    }

    private void initCache() {
        //可使用的最大内存
        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 value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

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

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

/**
 * SD卡缓存
 */
public class DiskCache implements ImageCache {
private String cacheDir = "sdcard/cache/";

@Override
public Bitmap get(String imageUrl) {
    return BitmapFactory.decodeFile(imageUrl);
}

@Override
public void put(String imageUrl, Bitmap bmp) {
    FileOutputStream fos=null;
    try {
         fos=new FileOutputStream(cacheDir+imageUrl);
         bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }finally {
            if (fos!=null){
                try {
                   fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

/**
 * 图片加载类
 */
public class ImageLoader {
    //图片缓存
    private ImageCache mImageCache= new MemoryCache();
    //线程池,数量为CPU数量
    private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    public void setmImageCache(ImageCache cache){
        mImageCache=cache;
    }

    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bmp = mImageCache.get(url);
        if (bmp != null) {
             imageView.setImageBitmap(bmp);
             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)) {
                imageView.setImageBitmap(bitmap);
            }
            mImageCache.put(url, bitmap);
            }
        });
    }

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

通过setImageCache(ImageCache cache)方法注入不同的缓存实现,这样不仅能够使ImageLoader更简单、健壮,也使得ImageLoader的可扩展性、灵活性更高,用户可以随便设置自己的缓存。如果现在需要加入一个双缓存机制是不是也很容易实现了,在此就不再重复了,读者可以自己尝试尝试。

三、里氏替换原则(Liskov Substitution Principle)

定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1的子类型。

定义2:所有引用基类的地方必须能透明地使用其子类的对象。

通俗的说:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是,反过来就不行了,有子类出现的地方,父类未必就能适应。

比如现在我要定义一个继承自ImageView的CircleImageView,可以将CircleImageView设置给ImageLoader.displayImage(),这就是所说的里氏替换原。通过里氏替换原,就可以自定义各式各样、千变万化的View。

里氏替换原原则的核心原理是抽象,抽象又依赖于继承这个特性,在OOP(面向对象编程)当中,继承的优缺点都相当明显。

优点如下:
代码重用,减少创建类的成本,每个子类都拥有父类的方法和属性;
子类与父类基本相似,但又与类有所区别;
提高代码的可扩展性。
缺点:
继承是侵入性的,只要继承就必须拥有父类的所有属性和方法;
可能造成子类代码冗余、灵活性降低,因为子类必须拥有父类拥有父类的属性和方法。

下面给出一个CircleImageView的实现地址:http://www.tuicool.com/articles/mQNFJ3,感兴趣的可以自己去实现一样。

四、依赖倒置原则(Dependence Inversion Principle)

定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

高层模块就是调用端,低层模块就是具体实现类。依赖倒置原则在java语言中的表现就是:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生。

例如:上面的ImageLoader是一个很好有例子,它是依赖于ImageCache而不是某个具体的缓存机制,当用户需要实现其他缓存时,可以自己实现ImageCache接口来定义一个自己的缓存,也不需要改变ImageLoader中的任何代码。

依赖倒置原则有以下几个关键点:
高层模块不应该依赖细节
抽象不应该依赖细节
细节应该依赖抽象

五、接口隔离原则(Interface Segregation Principle)

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户将会只需要知道他们感兴趣的方法。接口隔离原则的目的是系统解耦,从而容易重构、更改和重新部署。
例如:

public void put(String imageUrl, Bitmap bmp) {
    FileOutputStream fos=null;
    try {
       fos=new FileOutputStream(cacheDir+imageUrl);
       bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }finally {
        if (fos!=null){
            try {
               fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

在我们使用OutputStream或其他可关闭的对象时,我们必须保证它们最终被关闭,而各种try….catch嵌套都会使得代码的可读性很差,而且还容易发生错误,所以我们可以写一个统一的类来关闭这此对象。

public final class CloseUtils{
    private CloseUtils(){}
    /**
     * 关闭Closeable对象
     */
    public static void closeQuietly(Closeable closeable){
        if (closeable != null){
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

现在可以将上面的put方法改写一下了

public void put(String imageUrl, Bitmap bmp) {
  FileOutputStream fos=null;
  try {
    fos=new FileOutputStream(cacheDir+imageUrl);
    bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }finally {
       CloseUtils.closeQuietly(fos);
    }
}

代码简洁了很多,而且这个closeQuietly方法可以运用到各类可关闭的对象中,保证了代码的重用性。CloseUtils的closeQuietly方法的基本原理就是依赖于Closeable抽象而不是具体实现,并且建立在最小化依赖原则的基础之上,它只需要知道对象是可关闭的就可以了,这就是接口隔离原则。
再比如说,我们需要将缓存到sd卡中的图片进行其他处理,此时我们应该再写一个单独的接口来处理,而不是将这些处理方法写入到ImageCache中去,这样才会显得结构清晰,不冗余。

六、迪米特法则(Law Of Demeter)

定义:一个对象应该对其他对象保持最少的了解。

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

public void put(String imageUrl, Bitmap bmp) {
    FileOutputStream fos=null;
    try {
        fos=new FileOutputStream(cacheDir+imageUrl);
        bmp.compress(Bitmap.CompressFormat.JPEG,100,fos);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }finally {
        CloseUtils.closeQuietly(fos);
    }
}

我们可以将上面的代码改为如下的代码:

public void put(String imageUrl, Bitmap bmp) {
    DiskLruCache.Editor editor=null;
    OutputStream os=null;
    try {
        editor=mDiskLruCache.edit(imageUrl);
        if (editor != null){
            os=editor.newOutputStream(0);
            if (writeBitmapToDisk(bmp,os)){
                editor.commit();
            }else {
                editor.abort();
            }
        }
    } catch (Exception e) {
       e.printStackTrace();
    }finally {
            CloseUtils.closeQuietly(os);
    }
}

此时SD卡缓存的具体实现被替换了,但是对用户而言一点影响也没有,因为用户根本不知道DiskLruCache的存在,他们没有与DiskLruCache进行通信,他们只认识ImageCache,这使得系统具有更低的耦合性和更好的扩展性。

总结:

在应用开发过程中,这几个原则相当重要,你的程序里否健康,与这六大原则是息息相关的,我们不要刻板的去遵守这六大原则,而是要灵活的运用它们,这样才可以使你的程序的可维护性,可扩展性更好,更健康。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值