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() {
}
}
进一步改进:
- 这样的设计才是完美的,一个类实现了两个接口,把两个职责融合在一个类中。
- 你肯定会问这个Phone有两个原因引起变化了呀,是的,但是别忘记了我们是面向接口编程,我们对外公布的是接口而不是实现类。
- 如果真要实现类的单一职责,我们就必须使用上面的组合模式了,这会引起类间耦合过重、类的数量增加等问题,人为增加了设计的复杂性。
6. 后续
如果大家喜欢这篇文章,麻烦点赞。
如果想看更多 设计模式 方面的技术,欢迎关注!