引言
上篇文章我们讨论了设计模式在软件开发中的重要性,今天我们就来做个实际的例子展示一下设计模式的魅力。在日常开发中经常会遇到某种实现的不同选择问题,如图片上传可能分:阿里云上传和私有云上传,客户端的图片展示也可能分:Fresco和Glide。当然我们可以切换的时候来进行大量的修改来达到目的,但其实有更优雅的方法来实现兼容多种具体实现的方案。
最开始的写法
我们就拿图片上传举例,比如最开始的时候没考虑太多就直接使用了阿里云作为图片存储的地方,那么你可能会写出下面的代码。
public class AliyunImageStore { //... 省略属性、构造函数等...
public void createBucketIfNotExisting(String bucketName) { // ... 创建 bucket 代码逻辑...
public String generateAccessToken() {
// ... 根据 accesskey/secrectkey 等生成 access token
}
public String uploadToAliyun(Image image, String bucketName, String accessToken) {
//... 上传图片到阿里云...
//... 返回图片存储在阿里云上的地址 (url)...
}
public Image downloadFromAliyun(String url, String accessToken) {
//... 从阿里云下载图片...
}
// AliyunImageStore 类的使用举例 public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket"; //... 省略其他无关代码...
public void process() {
Image image = ...; // 处理图片,并封装为 Image 对象
AliyunImageStore imageStore = new AliyunImageStore(/* 省略参数 */);
imageStore.createBucketIfNotExisting(BUCKET_NAME);
String accessToken = imageStore.generateAccessToken();
imagestore.uploadToAliyun(image, BUCKET_NAME, accessToken);
}
}
整个上传流程包含三个步骤:创建 bucket(你可以简单理解为存储目录)、生成 access token 访问凭证、携带 access token 上传图片到指定的 bucket 中。代码实现非常简单, 类中的几个方法定义得都很干净,用起来也很清晰,乍看起来没有太大问题,完全能满足我 们将图片存储在阿里云的业务需求。
不过,软件开发中唯一不变的就是变化。过了一段时间后,我们自建了私有云,不再将图片 存储到阿里云了,而是将图片存储到自建私有云上。为了满足这样一个需求的变化,解决这个问题的根本方法就是,在编写代码的时候,要遵 从“基于接口而非实现编程”的原则,我们可能会这么改:
public interface ImageStore {
String upload(Image image, String bucketName);
Image download(String url);
}
public class AliyunImageStore implements ImageStore {
//... 省略属性、构造函数等...
public String upload(Image image, String bucketName) {
createBucketIfNotExisting(bucketName);
String accessToken = generateAccessToken();
//... 上传图片到阿里云...
//... 返回图片在阿里云上的地址 (url)...
}
public Image download(String url) {
String accessToken = generateAccessToken();
//... 从阿里云下载图片...
}
private void createBucketIfNotExisting(String bucketName) {
// ... 创建 bucket...
// ... 失败会抛出异常..
}
private String generateAccessToken() {
// ... 根据 accesskey/secrectkey 等生成 access token
}
}
// 上传下载流程改变:私有云不需要支持 access token
public class PrivateImageStore implements ImageStore {
public String upload(Image image, String bucketName) {
createBucketIfNotExisting(bucketName);
//... 上传图片到私有云...
//... 返回图片的 url...
}
public Image download(String url) {
//... 从私有云下载图片...
}
private void createBucketIfNotExisting(String bucketName) {
// ... 创建 bucket...
// ... 失败会抛出异常..
}
}
// ImageStore 的使用举例
public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket";
//... 省略其他无关代码...
public void process() {
Image image = ...;
// 处理图片,并封装为 Image 对象
ImageStore imageStore = new PrivateImageStore(...);
imagestore.upload(image, BUCKET_NAME);
}
工厂模式
我们通过接口来隔离了两个具体的实现。但如果我们还要替换图片存储方式,还是需要修改很多类似接口 = new 具体类;那样的代码。这样的设计还是不够完美,因此我们可以尝试使用工厂模式 + 配置文件的方式去做,代码如下。
public interface ImageStore {
String upload(Image image, String bucketName);
Image download(String url);
}
public class AliyunImageStore implements ImageStore {
public AliyunImageStore() {
}
@Override
public String upload(Image image, String bucketName) {
createBucketInfNotExisting(bucketName);
String accessToken = generateAccessToken();
System.out.println("AliyunImageStore upload");
return null;
}
@Override
public Image download(String url) {
return null;
}
private void createBucketInfNotExisting(String bucketName) {
}
private String generateAccessToken() {
return null;
}
}
public class PrivateImageStore implements ImageStore {
public PrivateImageStore() {
}
@Override
public String upload(Image image, String bucketName) {
createBucketInfNotExisting(bucketName);
System.out.println("PrivateImageStore upload");
return null;
}
@Override
public Image download(String url) {
return null;
}
public void createBucketInfNotExisting(String bucketName) {
}
}
//工厂类
public class ImageStoreFactory {
private static final String PRIVATE = "private";
private static final String ALIYUN = "aliyun";
private ImageStoreFactory() {}
public static ImageStore newInstance(String storeType) {
switch (storeType) {
case PRIVATE:
return new PrivateImageStore();
case ALIYUN:
return new AliyunImageStore();
default:
throw new IllegalArgumentException("not implemented yet");
}
}
}
public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket";
private static final String STORE_TYPE = "STORE_TYPE";
private static final String PROP_PATH = "./config.properties";
public void process() {
Image image = null;
//String storeType = prop.getString(STORE_TYPE);
String storeType = FileUtils.load(PROP_PATH)
.getProperty(STORE_TYPE);
ImageStore imageStore = ImageStoreFactory.newInstance(storeType);
imageStore.upload(image, BUCKET_NAME);
}
public static void main(String[] args) {
ImageProcessingJob job = new ImageProcessingJob();
job.process();
}
}
//config.properties 配置文件
STORE_TYPE=private
反射
是不是发现代码优雅了很多,其实工厂类也可以用反射的方式去实现,代码如下:
public class ImageProcessingJob {
private static final String BUCKET_NAME = "ai_images_bucket";
private static final String STORE_CLASS = "STORE_CLASS";
private static final String PROP_PATH = "./config.properties";
public void process() {
Image image = null;
try {
String storeClass = FileUtils.load(PROP_PATH)
.getProperty(STORE_CLASS);
ImageStore imageStore = (ImageStore) Class.forName(storeClass).newInstance();
imageStore.upload(image, BUCKET_NAME);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ImageProcessingJob job = new ImageProcessingJob();
job.process();
// PrivateImageStore upload
}
}
//config.properties 配置文件
STORE_CLASS=PrivateImageStore
总结
通过图片上传这个实际场景的例子,相信你对接口、工厂模式、反射都有了比较好的理解。
喜欢请关注👀、点赞👍、评论👯♀️
微信公众号:Peachou