引言
上篇文章我們討論了設計模式在軟件開發中的重要性,今天我們就來做個實際的例子展示一下設計模式的魅力。在日常開發中經常會遇到某種實現的不同選擇問題,如圖片上傳可能分:阿里雲上傳和私有云上傳,客戶端的圖片展示也可能分: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