美文网首页
用正反实例说说单一职责原则

用正反实例说说单一职责原则

作者: 付凯强 | 来源:发表于2017-10-10 13:54 被阅读0次

    0. 序言

    • 这篇博客从实践栗子出发,通俗易懂的讲解单一职责原则适用的场景,以及单一职责原则所应该把握的"适度".希望阅读完本文可以更好的对代码的可读性、可维护性、复杂度、变革风险有进一步的认识。
    • 博客目录如下:
      • 单一职责原则的定义
      • 单一职责原则的优缺点
      • 用正反栗子讲解如何在接口、类和方法中保持单一职责原则。
      • 用正反栗子讲解如何保持单一职责原则的“适度”

    1. 定义

    • 就一个类而言,应该仅有一个引起它变化的原因。

    2. 优点

    • 类的复杂性降低:实现什么职责都有清晰明确的定义。
    • 可读性提高:复杂性降低,那当然可读性提高了。
    • 可维护性提高:可读性提高,那当然更容易维护。
    • 变革引起的风险降低:变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性和维护性都有非常大的帮助。

    3. 缺点

    • 单一职责原则提出了一个编写程序的标准,用“职责”或“变化原因”来衡量接口或者类设计的是否优良,但是“职责”和“变化原因”都是不可度量的,因项目而异,因环境而异,因而有时候饱受争议。

    4. 栗子

    单一职责原则适用于接口、类和方法。

    • 接口:
    反面教材:
    
    public interface IUserInfo {
    
        void setUserID(String userID);
        String getUserID();
        void setPassword(String password);
        String getPassword();
        void setUserName(String userName);
        String getUserName();
        
        boolean changePassword(String oldPassword);
        boolean deleteUser();
        void mapUser();
        boolean addOrg(int orgID);
        boolean addRole(int roleID);
        
    }
    

    问题所在:用户的属性和用户的行为没有分开。

    正面栗子:
    
    //BO(Business Object) 业务对象
    public interface IUserBO {
        void setUserID(String userID);
        String getUserID();
        void setPassword(String password);
        String getPassword();
        void setUserName(String userName);
        String getUserName();
    }
    
    //Biz(Business Logic) 业务逻辑
    public interface IUserBiz {
        boolean changePassword(String oldPassword);
        boolean deleteUser();
        void mapUser();
        boolean addOrg(int orgID);
        boolean addRole(int roleID);
    }
    

    修正:两个接口有各自不同的实现类,职责单一,分工明确。

    • 类:
    反面教材:
    
    public class ImageLoader {
        //图片缓存
        LruCache<String, Bitmap> mImageCache;
        //线程池,线程数量为CPU的数量
        ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
        public ImageLoader() {
            initImageCache();
        }
    
        private void initImageCache() {
            //计算可使用的最大内存
            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;
                }
            };
        }
    
        private 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);
                final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                bitmap = BitmapFactory.decodeStream(conn.getInputStream());
                conn.disconnect();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    }
    

    问题:创建线程池、创建LruCache、下载图片、设置图片都放在了一个类,耦合性太强,触一发动全身。

    正面栗子:
    
    /**
     * Created by FuKaiqiang on 2017-10-10.
     * 图片缓存类
     */
    
    public class ImageCache {
        //图片Lru缓存
        LruCache<String, Bitmap> mImageCache;
    
        public ImageCache() {
            initImageCache();
        }
    
        private void initImageCache() {
            //计算可使用的最大内存
            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);
        }
    }
    
    /**
     * Created by FuKaiqiang on 2017-10-10.
     * 图片加载类
     */
    
    public class ImageLoader {
        //图片缓存
        ImageCache mImageCache = new ImageCache();
        //线程池,线程数量为CPU的数量
        ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    
        //加载图片
        private 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);
                }
            });
        }
    
        private 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 (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    }
    

    修正:
    ① ImageLoader负责图片加载,ImageCache负责图片缓存,ImageLoader代码量变少,职责清晰。
    ② 当缓存相关的逻辑变动时,不需要修改ImageLoader类,当图片加载相关的逻辑变动时,不需要修改缓存处理逻辑。

    • 方法
    反面教材:
    
    public interface IUserManager {
        void changeUser(String newUserName, String newHomeAddress, String telNumber);
    }
    

    问题:方法职责不清晰,不单一。

    正面栗子:
    
    public interface IUserManager {
        void changeUserName(String newsUserName);
    
        void changeHomeAddress(String newHomeAddress);
    
        void changeOfficeTel(String telNumber);
    }
    

    修正:职责单一,分工明确,你我他都好。

    5. 进阶

    • 我们首先看一个接口和它的实现类:
    public interface IPhone {
        //拨通电话
        public void dial(String phoneNumber);
    
        //通话
        public void chat(Object o);
    
        //通话完毕,挂电话
        public void hangup();
    }
    

    问题:它包含两个职责:一个是协议管理,一个是数据传输。

    public class Phone implements IPhone {
        @Override
        public void dial(String phoneNumber) {
            
        }
    
        @Override
        public void chat(Object o) {
    
        }
    
        @Override
        public void hangup() {
    
        }
    }
    
    • 我们对之进行改进:
    public interface IConnectionManager {
        //拨通电话
        public void dial(String phoneNumber);
        //通话完毕,挂电话
        public void hangup();
    }
    
    public interface IDataTransfer {
        //通话
        public void chat(Object o);
    }
    
    public class ConnectionManager implements IConnectionManager {
        @Override
        public void dial(String phoneNumber) {
            
        }
    
        @Override
        public void hangup() {
    
        }
    }
    
    public class DataTransfer implements IDataTransfer {
        @Override
        public void chat(Object o) {
            
        }
    }
    

    改进:
    ① 两个接口两个实现类----导致了Phone类要把两个实现类组合在一起才可以使用。
    ② 然而组成是一种强耦合关系,有共同的生命周期,这样的耦合关系还不如使用接口实现的方式,而且还正价了类的复杂性,多了两个类。

    • 最终改进如下:
    public interface IConnectionManager {
        //拨通电话
        public void dial(String phoneNumber);
        //通话完毕,挂电话
        public void hangup();
    }
    
    public interface IDataTransfer {
        //通话
        public void chat(Object o);
    }
    
    public class Phone implements IConnectionManager, IDataTransfer {
        @Override
        public void dial(String phoneNumber) {
    
        }
    
        @Override
        public void chat(Object o) {
    
        }
    
        @Override
        public void hangup() {
    
        }
    }
    

    进一步改进:

    1. 这样的设计才是完美的,一个类实现了两个接口,把两个职责融合在一个类中。
    2. 你肯定会问这个Phone有两个原因引起变化了呀,是的,但是别忘记了我们是面向接口编程,我们对外公布的是接口而不是实现类。
    3. 如果真要实现类的单一职责,我们就必须使用上面的组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为增加了设计的复杂性。

    6. 后续

    如果大家喜欢这篇文章,麻烦点赞。
    如果想看更多 设计模式 方面的技术,欢迎关注!

    相关文章

      网友评论

          本文标题:用正反实例说说单一职责原则

          本文链接:https://www.haomeiwen.com/subject/tlmvyxtx.html