Android makes the program more stable and flexible - the principle of opening and closing

Open and close principle

The English full name of the opening and closing principle is OpenClosePrinciple, and the abbreviation is OCP. It is the most basic design principle in the Java world. It guides us how to build a stable and flexible system. The definition of the open-closed principle is: objects in software (classes, modules, functions, etc.) should be open for extension, but closed for modification. During the life cycle of the software, when the original code of the software needs to be modified due to reasons such as changes, upgrades, and maintenance, errors may be introduced into the old code that has already been tested, destroying the original system. Therefore, when the software needs to change, we should try our best to achieve the change through extension instead of modifying the existing code. Of course, in real development, it is only an idealized vision to upgrade and maintain the original system only through inheritance. Therefore, in the actual development process, modifying the original code and extending the code often exist at the same time.

In software development, the last thing to change is change itself. Products need to be continuously upgraded and maintained. No product will remain unchanged since the first version is developed, unless it has been terminated before the next version is born. The product needs to be upgraded, and modifying the original code may cause other problems. Then, how to ensure the correctness of the original software modules and minimize the impact on the original modules, the answer is to follow the principle of opening and closing as much as possible.

Brandt Meyer put forward this principle in the book "Object-Oriented Software Construction" published in 1988 - the principle of opening and closing. The idea is that once a program is developed, the implementation of a class in the program should only be modified for errors, and new or changed features should be implemented by creating a different class that can reuse the original class by inheritance code. Obviously, Mayer's definition advocates implementation inheritance. Existing implementation classes are closed to modification, but new implementation classes can respond to changes by overriding the interface of the parent class.

example

Continue to use the first step to optimize code for Android - Single Responsibility Principle Here is an example of ImageLoader.

before optimization

The problem of loading pictures from the network is solved by memory caching. However, the memory of Android applications is limited and volatile. That is, when the application is restarted, the pictures that have been loaded will be lost. A new download is required. This in turn leads to slow loading and drains user traffic. So it is natural to introduce the SD card cache, so that the downloaded pictures will be cached locally, and there is no need to download them again even if the application is restarted.

DiskCache.java class, cache pictures to SD card.

public class DiskCache {
    static String cacheDir = "sdcard/cache/";

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

    // 将图片缓存到内存中
    public void put(String url, Bitmap bmp) {
        FileOutputStream fileOutputStream = null;
        try {
            fileOutputStream = new FileOutputStream(cacheDir + url);
            bmp.compress(CompressFormat.PNG, 100, fileOutputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            CloseUtils.closeQuietly(fileOutputStream);
        }
    }
}

/**
 * 图片加载类
 */
public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // sd卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 使用sd卡缓存
    boolean isUseDiskCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private Handler mUiHandler = new Handler();

    public void displayImage(final String url, final ImageView imageView) {
        // 判断使用哪种缓存
        Bitmap bmp = null;
        if (isUseDiskCache) {
            bmp = mDiskCache.get(url);
        } else {
            bmp = mImageCache.get(url);
        }

        if (bmp != null) {
            imageView.setImageBitmap(bmp);
            return;
        }

        // 没有缓存,则提交给线程池进行异步下载
    }

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

As can be seen from the above code, only a new DiskCache class and a small amount of code are added to the ImageLoader class to add the SD card cache function. Users can use the useDiskCache method to set which cache to use, for example:

ImageLoader imageLoader = new ImageLoader() ;
// 使用sd卡缓存
imageLoader.useDiskCache(true);
// 使用内存缓存
imageLoader.useDiskCache(false);

But there will be a problem with this, that is, users cannot use SD card cache when using memory cache. Similarly, users cannot use memory cache when using SD card cache. Users need a combination of these two strategies. First, use the memory cache first. If there is no picture in the memory cache, then use the SD card cache. If there is no picture in the SD card, then get it from the network. This is the best cache strategy.

Create a new double cache class DoubleCache, the specific code is as follows.

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

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

    // 将图片缓存到内存和sd卡中
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}

Let's take a look at the latest ImagcLoader class, and there are not many code updates.

/**
 * 图片加载类
 */
public class ImageLoader {
    // 内存缓存
    ImageCache mImageCache = new ImageCache();
    // sd卡缓存
    DiskCache mDiskCache = new DiskCache();
    // 双缓存
    DoubleCache mDoubleCache = new DoubleCache() ;
    // 使用sd卡缓存
    boolean isUseDiskCache = false;
    // 使用双缓存
    boolean isUseDoubleCache = false;
    // 线程池,线程数量为CPU的数量
    ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime()
            .availableProcessors());
    private Handler mUiHandler = new Handler() ;

    public void displayImage(final String url, final ImageView imageView) {
        Bitmap bmp = null;
        if (isUseDoubleCache) {
            bmp = mDoubleCache.get(url);
        } else if (isUseDiskCache) {
            bmp = mDiskCache.get(url);
        } else {
            bmp = mImageCache.get(url);
        }
        
        if ( bmp != null ) {
            imageView.setImageBitmap(bmp);
            return;
        }
        
        // 没有缓存,则提交给线程池进行异步下载
    }
    
    public void useDiskCache(boolean useDiskCache) {
        isUseDiskCache = useDiskCache ;
    }
    
    public void useDoubleCache(boolean useDoubleCache) {
        isUseDoubleCache = useDoubleCache ;
    }
}

But every time a new cache method is added, the original code must be modified, which is likely to introduce bugs and make the original code logic more and more complicated. According to this method, users cannot customize the cache implementation.

Let's analyze the above program. Every time a new cache implementation is added to the program, the ImageLoader class needs to be modified, and then a Boolean variable is used to allow the user to choose which cache to use. Therefore, there are various if-else judgment statements in ImageLoader, and through these judgments to determine which cache to use. With the introduction of these logics, the code becomes more and more complex and fragile. If you accidentally write a certain calculation condition (too many conditions, it is easy to appear), it will take more time to Otherwise, the entire ImageLoader class would become more and more bloated. The most important thing is that users cannot implement cache injection into ImageLoader by themselves, and the scalability is poor, which is one of the most important features of the framework.

Objects in software (classes, modules, functions, etc.) should be open for extension, but closed for modification, which is the open-closed principle. That is to say, when the software needs to change, we should try our best to achieve the change through extension instead of modifying the existing code.

Optimized

image.png

Refactor the code again:

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

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

    public void displayImage(String imageUrl, ImageView imageView) {
        Bitmap bitmap = mImageCache.get(imageUrl);
        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)) {
                    updateImageView(imageView, bitmap);
                }
                mImageCache.put(imageUrl, bitmap);
            }
        });
    }
    
    private void updateImageView(final ImageView imageView, final Bitmap bitmap) {
      mUiHandler.post(new Runnable() {
         
         @Override
         public void run() {
             imageView.setImageBitmap(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();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return bitmap;
    }
}

After this refactoring, there are no so many if-else statements, various cache implementation objects, and Boolean variables. The code is indeed clear and simple. It should be noted that the ImageCache class here is not the original ImageCache. This time the program is refactored to extract it into an image cache interface, which is used to abstract the function of image cache. Let's take a look at the declaration of this interface:

/**
 * 图片缓存接口
 * 
 * @author mrsimple
 */
public interface ImageCache {
    public Bitmap get(String url);

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

The ImageCache interface simply defines two functions for obtaining and caching images. The key of the cache is the url of the image, and the value is the image itself. Memory cache, SD card cache, and double cache all implement this interface. Let's take a look at the implementation of these caches.

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

    public MemoryCache() {
        // 计算可使用的最大内存
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 取4分之一的可用内存作为缓存
        final int cacheSize = maxMemory / 4;
        mMemeryCache = new LruCache<String, Bitmap>(cacheSize) {

            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
            }
        };

    }

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

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

/**
 * 本地(sd卡)图片缓存
 */
public class DiskCache implements ImageCache {

    @Override
    public Bitmap get(String url) {
        return null/* 从本地文件中获取该图片 */;
    }

    private String imageUrl2MD5(String imageUrl) {
        // 对imgeUrl进行md5运算, 省略
        String md5 = imageUrl;
        return md5;
    }

    @Override
    public void put(String url, Bitmap bmp) {
        // 将Bitmap写入文件中
        FileOutputStream fos = null;
        try {
            // 构建图片的存储路径 ( 省略了对url取md5)
            fos = new FileOutputStream("sdcard/cache/" + imageUrl2MD5(url));
            bmp.compress(CompressFormat.JPEG, 100, fos);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if ( fos != null ) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

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

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

    // 将图片缓存到内存和sd卡中
    public void put(String url, Bitmap bmp) {
        mMemoryCache.put(url, bmp);
        mDiskCache.put(url, bmp);
    }
}

A setImageCache(ImageCachecache) function is added to the ImageLoader class, through which users can set the cache implementation, which is commonly referred to as dependency injection. Let's take a look at how the user sets up the cache implementation.

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(String url, Bitmap bmp) {
        // 缓存图片
    }
    
    @Override
    public Bitmap get(String url) {
        return null /*从自定义的缓存实现中获取图片*/;
    }
});

In the above code, different cache implementations are injected through the setlmageCache(ImageCachecache) method, which not only makes ImageLoader simpler and more robust, but also makes ImageLoader more scalable and flexible. The specific implementations of MemoryCache, DiskCache, and DoubleCache cache images are completely different, but one of their characteristics is that they all implement the ImageCache interface. When users need to customize the cache strategy, they only need to create a new class that implements the ImageCache interface, then construct the object of this class, and inject it into ImageLoader through setimageCache(ImageCachecache), so that ImageLoader realizes ever-changing cache strategies, and expands These caching policies do not result in modifications to the ImageLoader class.

Summarize

The principle of opening and closing guides us that when the software needs to change, we should try our best to achieve the change through expansion instead of modifying the existing code. The four words "should try to" here show that the OCP principle does not mean that the original class must never be modified. When we smell the "corruption smell" of the original code, we should refactor as early as possible so that the code can return to the normal "evolution" process, instead of adding new implementations through inheritance, etc., which will lead to type inflation and history. Redundancy in legacy code. Our development process is not in such an ideal situation, and there is no need to modify the original code at all. Therefore, we need to consider the specific situation during the development process, whether to make the software system more stable and more stable by modifying the old code or by inheriting Flexible, while ensuring the removal of "code corruption", it also ensures the correctness of the original modules.

at last

If you want to become an architect or want to break through the 20-30K salary range, then don't be limited to coding and business, but you must be able to select models, expand, and improve programming thinking. In addition, a good career plan is also very important, and the habit of learning is very important, but the most important thing is to be able to persevere. Any plan that cannot be implemented consistently is empty talk.

If you have no direction, here I would like to share with you a set of "Advanced Notes on the Eight Major Modules of Android" written by the senior architect of Ali, to help you organize the messy, scattered and fragmented knowledge systematically, so that you can systematically and efficiently Master the various knowledge points of Android development.
insert image description here
Compared with the fragmented content we usually read, the knowledge points of this note are more systematic, easier to understand and remember, and are arranged strictly according to the knowledge system.

Full set of video materials:

1. Interview collection

insert image description here
2. Source code analysis collection
insert image description here

3. Collection of open source frameworks
insert image description here

Welcome everyone to support with one click and three links. If you need the information in the article, just click on the CSDN official certification WeChat card at the end of the article to get it for free↓↓↓

Guess you like

Origin blog.csdn.net/m0_56255097/article/details/131964760