面向对象的六大原则

来源:互联网 发布:c 11 并行编程 编辑:程序博客网 时间:2024/05/22 06:19

在开发过程中,面向对象的六大原则非常重要,所以本节给大家带来了这六大原则的讲解,全文会以一个简单的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/";@Overridepublic Bitmap get(String imageUrl) {    return BitmapFactory.decodeFile(imageUrl);}@Overridepublic 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 0
原创粉丝点击