《Android 源码设计模式解析与实战》——第1章,第1.1节优化代码的第一步——单一职责原则

简介:

本节书摘来自异步社区《Android 源码设计模式解析与实战》一书中的第1章,第1.1节优化代码的第一步——单一职责原则,作者 何红辉 , 关爱民,更多章节内容可以访问云栖社区“异步社区”公众号查看

第1章 走向灵活软件之路——面向对象的六大原则
Android 源码设计模式解析与实战

1.1 优化代码的第一步——单一职责原则
单一职责原则的英文名称是Single Responsibility Principle,缩写是SRP。SRP的定义是:就一个类而言,应该仅有一个引起它变化的原因。简单来说,一个类中应该是一组相关性很高的函数、数据的封装。就像老师在《设计模式之禅》中说的:“这是一个备受争议却又及其重要的原则。只要你想和别人争执、怄气或者是吵架,这个原则是屡试不爽的”。因为单一职责的划分界限并不是总是那么清晰,很多时候都是需要靠个人经验来界定。当然,最大的问题就是对职责的定义,什么是类的职责,以及怎么划分类的职责。

对于计算机技术,通常只单纯地学习理论知识并不能很好地领会其深意,只有自己动手实践,并在实际运用中发现问题、解决问题、思考问题,才能够将知识吸收到自己的脑海中。下面以我的朋友小民的事情说起。

自从Android系统发布以来,小民就是Android的铁杆粉丝,于是在大学期间一直保持着对Android的关注,并且利用课余时间做些小项目,锻炼自己的实战能力。毕业后,小民如愿地加入了心仪的公司,并且投入到了他热爱的Android应用开发行业中。将爱好、生活、事业融为一体,小民的第一份工作也算是顺风顺水,一切尽在掌握中。

在经历过一周的适应期以及熟悉公司的产品、开发规范之后,小民的开发工作就正式开始了。小民的主管是个工作经验丰富的技术专家,对于小民的工作并不是很满意,尤其小民最薄弱的面向对象设计,而Android开发又是使用Java语言,程序中的抽象、接口、六大原则、23种设计模式等名词把小民弄得晕头转向。小民自己也察觉到了自己的问题所在,于是,小民的主管决定先让小民做一个小项目来锻炼这方面的能力。正所谓养兵千日用兵一时,磨刀不误砍柴工,小民的开发之路才刚刚开始。

在经过一番思考之后,主管挑选了使用范围广、难度也适中的图片加载器(ImageLoader)作为小民的训练项目。既然要训练小民的面向对象设计,那么就必须考虑到可扩展性、灵活性,而检测这一切是否符合需求的最好途径就是开源。用户不断地提出需求、反馈问题,小民的项目需要不断升级以满足用户需求,并且要保证系统的稳定性、灵活性。在主管跟小民说了这一特殊任务之后,小民第一次感到了压力,“生活不容易!”年仅22岁的小民发出了如此深刻的感叹!

挑战总是要面对的,何况是从来不服输的小民。主管的要求很简单,要小民实现图片加载,并且要将图片缓存起来。在分析了需求之后,小民一下就放心下来了,“这么简单,原来我还以为很难呢……”小民胸有成足地喃喃自语。在经历了10分钟的编码之后,小民写下了如下代码:

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

  public ImageLoader() {
    initImageCache();
  }

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

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

并且使用Git软件进行版本控制,将工程托管到Github上,伴随着git push命令的完成,小民的ImageLoader 0.1版本就正式发布了!如此短的时间内就完成了这个任务,而且还是一个开源项目,小民暗暗自喜,并幻想着待会儿被主管称赞。

在小民给主管报告了ImageLoader的发布消息的几分钟之后,主管就把小民叫到了会议室。这下小民纳闷了,怎么夸人还需要到会议室。“小民,你的ImageLoader耦合太严重啦!简直就没有设计可言,更不要说扩展性、灵活性了。所有的功能都写在一个类里怎么行呢,这样随着功能的增多,ImageLoader类会越来越大,代码也越来越复杂,图片加载系统就越来越脆弱……”这简直就是当头棒喝,小民的脑海里已经听不清主管下面说的内容了,只是觉得自己之前没有考虑清楚就匆匆忙忙完成任务,而且把任务想得太简单了。

“你还是把ImageLoader拆分一下,把各个功能独立出来,让它们满足单一职责原则。”主管最后说道。小民是个聪明人,敏锐地捕捉到了单一职责原则这个关键词,他用Google搜索了一些资料之后,总算是对单一职责原则有了一些认识,于是打算对ImageLoader进行一次重构。这次小民不敢过于草率,也是先画了一幅UML图,如图1-1所示。


3b5e49942b12c4a77a067187930eb2b4d4fe1c66

ImageLoader代码修改如下所示:

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

        // lk图片缓存
  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)) {
          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();
     } catch (Exception e) {
      e.printStackTrace();
     }

     return bitmap;
  }
}  
并且添加了一个ImageCache类用于处理图片缓存,具体代码如下:

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-1和上述代码所示,小民将ImageLoader一拆为二,ImageLoader只负责图片加载的逻辑,而ImageCache只负责处理图片缓存的逻辑,这样ImageLoader的代码量变少了,职责也清晰了;当与缓存相关的逻辑需要改变时,不需要修改ImageLoader类,而图片加载的逻辑需要修改时也不会影响到缓存处理逻辑。主管在审核了小民的第一次重构之后,对小民的工作给予了表扬,大致意思是结构变得清晰了许多,但是可扩展性还是比较欠缺。虽然没有得到主管的完全肯定,但也是颇有进步,再考虑到自己确实有所收获,小民原本沮丧的心里也略微地好转起来。

从上述的例子中我们能够体会到,单一职责所表达出的用意就是“单一”二字。正如上文所说,如何划分一个类、一个函数的职责,每个人都有自己的看法,这需要根据个人经验、具体的业务逻辑而定。但是,它也有一些基本的指导原则,例如,两个完全不一样的功能就不应该放在一个类中。一个类中应该是一组相关性很高的函数、数据的封装。工程师可以不断地审视自己的代码,根据具体的业务、功能对类进行相应的拆分,这是程序员优化代码迈出的第一步。

相关文章
|
11天前
|
数据采集 消息中间件 监控
Flume数据采集系统设计与配置实战:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入探讨Apache Flume的数据采集系统设计,涵盖Flume Agent、Source、Channel、Sink的核心概念及其配置实战。通过实例展示了文件日志收集、网络数据接收、命令行实时数据捕获等场景。此外,还讨论了Flume与同类工具的对比、实际项目挑战及解决方案,以及未来发展趋势。提供配置示例帮助理解Flume在数据集成、日志收集中的应用,为面试准备提供扎实的理论与实践支持。
24 1
|
21天前
|
XML Java Android开发
Android实现自定义进度条(源码+解析)
Android实现自定义进度条(源码+解析)
50 1
|
21天前
|
监控 前端开发 JavaScript
实战篇:商品API接口在跨平台销售中的有效运用与案例解析
随着电子商务的蓬勃发展,企业为了扩大市场覆盖面,经常需要在多个在线平台上展示和销售产品。然而,手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要,它能够帮助企业在不同的销售平台之间实现商品信息的高效同步和管理。本文将通过具体的淘宝API接口使用案例,展示如何在跨平台销售中有效利用商品API接口,以及如何通过代码实现数据的统一管理。
|
1月前
|
存储 搜索推荐 人机交互
Qt鼠标事件全面解析:从基础到实战
Qt鼠标事件全面解析:从基础到实战
117 0
|
1月前
|
Ubuntu 网络协议 Java
【Android平板编程】远程Ubuntu服务器code-server编程写代码
【Android平板编程】远程Ubuntu服务器code-server编程写代码
|
29天前
|
设计模式 编解码 C++
【ffmpeg 视频播放】深入探索:ffmpeg视频播放优化策略与设计模式的实践应用(一)
【ffmpeg 视频播放】深入探索:ffmpeg视频播放优化策略与设计模式的实践应用
47 0
|
25天前
|
安全 Java 数据安全/隐私保护
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
【深入浅出Spring原理及实战】「EL表达式开发系列」深入解析SpringEL表达式理论详解与实际应用
54 1
|
29天前
|
设计模式 存储 缓存
【ffmpeg 视频播放】深入探索:ffmpeg视频播放优化策略与设计模式的实践应用(二)
【ffmpeg 视频播放】深入探索:ffmpeg视频播放优化策略与设计模式的实践应用
27 0
|
10天前
|
SQL API 数据库
Python中的SQLAlchemy框架:深度解析与实战应用
【4月更文挑战第13天】在Python的众多ORM(对象关系映射)框架中,SQLAlchemy以其功能强大、灵活性和易扩展性脱颖而出,成为许多开发者首选的数据库操作工具。本文将深入探讨SQLAlchemy的核心概念、功能特点以及实战应用,帮助读者更好地理解和使用这一框架。
|
11天前
|
Java 数据库 Spring
切面编程的艺术:Spring动态代理解析与实战
切面编程的艺术:Spring动态代理解析与实战
25 0
切面编程的艺术:Spring动态代理解析与实战

推荐镜像

更多