前言
適配器模式是把一個類的接口變換成客戶端所期待的另一中接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作庆冕。
用電器做例子,筆記本電腦的插頭一般都是三相的,即除了陽極批旺、陰極外,還有一個地極诵姜。而有些地方的電源插座卻只有兩極汽煮,沒有地極。電源插座與筆記本電腦的電源插頭不匹配使得筆記本電腦無法使用茅诱。這時候一個三相到兩相的轉(zhuǎn)換器(適配器)就能解決此問題逗物,而這正像是本模式所做的事情(這個例子是我抄來的)。
適配器模式的類型
關(guān)于適配器模式的類型這個小節(jié)太過枯燥無味瑟俭,概念定義生搬硬套太過牽強令人難以理解翎卓,雖然基本是我復制粘貼的過來的但是仍一度讓我惱火到不想繼續(xù)寫下去。建議大家跳過這一部分直接看下面具體的業(yè)務場景摆寄。
適配器模式有類的適配器和對象的適配器兩種不同的形式失暴。
-
類適配器:
類的適配器模式把適配的類的API轉(zhuǎn)換成目標類的API,假設有一個接口A和一個具體的類B微饥,B不是A的實現(xiàn)類逗扒,現(xiàn)在客戶端希望在一個A的是實現(xiàn)類C中使用B的功能,最簡單的方法就是使C實現(xiàn)A的同時又繼承B欠橘。這其中涉及到了三個角色
目標角色(target):就是所期待得到的目標接口A
源角色(Adapee):現(xiàn)在要適配的類B
適配器角色:
適配器類是本模式的核心矩肩。適配器把源接口轉(zhuǎn)換成目標接口。顯然肃续,這一角色不可以 是接口黍檩,而必 須是具體類,在本例中C就是適配器-
對象適配器
與類的適配器模式一樣始锚,對象的適配器模式把被適配的類的API轉(zhuǎn)換成為目標類的API刽酱,與類的適配器模式不同的是,對象的適配器模式不是使用繼承關(guān)系連接到Adaptee類瞧捌,而是使用委派關(guān)系連接到Adaptee類棵里。
使用適配器模式實現(xiàn)文件服務
最近一個月都在做公司的項目遷移润文,把集團的項目(包括服務器)遷移到上海這邊,我們原有的框架下有一套自研的文件服務實現(xiàn)殿怜,是將文件內(nèi)容byte數(shù)組及元數(shù)據(jù)存放在mongo中典蝌,這個自研的文件服務有些年頭了而且引入了這個文件服務之后在項目啟動時會自動加載mongo連接(無論是否實際使用),重度依賴基礎環(huán)境头谜,所以趁著這次項目遷移準備完全移除自研的文件服務使用第三方文件服務實現(xiàn)赠法,最終選用了阿里云oss作為第三方文件服務實現(xiàn),以下就用oss表示新的文件服務乔夯。
因為要考慮到歷史文件的遷移砖织,文件服務遷移主要分成了兩個階段:
一、文件雙寫:
增量數(shù)據(jù)(新上傳的文件)雙寫末荐,同時上傳到自研文件服務和oss侧纯,同一個文件在自研文件服務和oss中的文件id是一致的,讀取文件仍然從自研文件服務中讀燃自唷(因為存量數(shù)據(jù)尚未同步至oss中)眶熬。
二、存量數(shù)據(jù)同步:
因為有很多文件id是作為歷史數(shù)據(jù)(存量數(shù)據(jù))保存在mysql中的各個業(yè)務表中的(比如營業(yè)執(zhí)照块请、合同等)娜氏,為了確保切換到oss后歷史文件能夠正確顯示,最簡單的方法就是將自研文件服務中的存量數(shù)據(jù)同步到oss中并且確保文件id一致墩新,這樣同步完成之后就能夠無縫切換到oss贸弥。
在文件雙寫階段要實現(xiàn)的目標是 使用新的文件服務依賴替代原有的自研框架的文件服務依賴,對外只暴露新的文件服務接口定義的接口和類(接口和類除了包路徑與自研服務不同之外海渊,接口名類名和參數(shù)名都完全相同绵疲,這樣各個依賴方只需要在導包處把包路徑替換即可無需改動代碼邏輯),所以這個時候就需要我們將自研的文件服務和新的文件服務做一個適配臣疑。
以下是代碼部分:
自研文件服務:
package com.cube.dp.adapter.fs.custom;
import com.cube.dp.adapter.fs.custom.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.custom.dto.FileDownloadForStreamDto;
import com.cube.dp.adapter.fs.custom.dto.FileUploadDto;
import com.cube.dp.adapter.fs.custom.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 15:32
* <p>
* 自研文件服務接口
*/
public interface IFileOperationService {
/**
* 上傳文件
*
* @param dto 參數(shù)
* @return 文件id
*/
String upload(FileUploadDto dto);
/**
* 流式上傳文件
*
* @param streamDto 參數(shù)
* @return 文件id
*/
String upload4Stream(FileUploadForStreamDto streamDto);
/**
* 下載文件
*
* @param fileId 文件id
* @return 結(jié)果
*/
FileDownloadDto download(String fileId);
/**
* 流式下載文件
*
* @param fileId 文件id
* @return 結(jié)果
*/
FileDownloadForStreamDto download4Stream(String fileId);
}
自研文件服務實現(xiàn)類
package com.cube.dp.adapter.fs.custom;
import com.cube.dp.adapter.fs.custom.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.custom.dto.FileDownloadForStreamDto;
import com.cube.dp.adapter.fs.custom.dto.FileUploadDto;
import com.cube.dp.adapter.fs.custom.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 15:48
* <p>
* 自研文件服務實現(xiàn)
*/
public class CustomFileOperationServiceImpl implements IFileOperationService {
@Override
public String upload(FileUploadDto dto) {
System.out.println("自研文件服務上傳文件...");
return null;
}
@Override
public String upload4Stream(FileUploadForStreamDto streamDto) {
System.out.println("自研文件服務流式上傳文件...");
return null;
}
@Override
public FileDownloadDto download(String fileId) {
System.out.println("自研文件服務下載文件...");
return null;
}
@Override
public FileDownloadForStreamDto download4Stream(String fileId) {
System.out.println("自研文件服務流式下載文件...");
return null;
}
}
第三方文件服務:
package com.cube.dp.adapter.fs.third;
import com.cube.dp.adapter.fs.third.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.third.dto.FileDownloadForStreamDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 15:32
* <p>
* 第三方文件服務接口
*/
public interface IThirdPartyFileOperationService {
/**
* 上傳文件
*
* @param dto 參數(shù)
* @return 文件id
*/
String upload(FileUploadDto dto);
/**
* 流式上傳文件
*
* @param streamDto 參數(shù)
* @return 文件id
*/
String upload4Stream(FileUploadForStreamDto streamDto);
/**
* 下載文件
*
* @param fileId 文件id
* @return 結(jié)果
*/
FileDownloadDto download(String fileId);
/**
* 流式下載文件
*
* @param fileId 文件id
* @return 結(jié)果
*/
FileDownloadForStreamDto download4Stream(String fileId);
}
oss文件服務實現(xiàn)
package com.cube.dp.adapter.fs.third;
import com.cube.dp.adapter.fs.third.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.third.dto.FileDownloadForStreamDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 15:48
* <p>
* 自研文件服務實現(xiàn)
*/
public class OssFileOperationServiceImpl implements IThirdPartyFileOperationService {
@Override
public String upload(FileUploadDto dto) {
System.out.println("oss文件服務上傳文件...");
return null;
}
@Override
public String upload4Stream(FileUploadForStreamDto streamDto) {
System.out.println("oss文件服務流式上傳文件...");
return null;
}
@Override
public FileDownloadDto download(String fileId) {
System.out.println("oss文件服務下載文件...");
return null;
}
@Override
public FileDownloadForStreamDto download4Stream(String fileId) {
System.out.println("oss文件服務流式下載文件...");
return null;
}
}
看一下兩者的目錄結(jié)構(gòu):
這里保證了自研文件服務和第三方文件服務的類名盔憨、接口完全一致,原因上面已經(jīng)說過了讯沈。
這里因為是為了方便演示放在同一個項目下所以兩者的包路徑差別不大郁岩,實際上兩者的包路徑可以說是沒有任何相似之處。
再復述一下我們在雙寫階段的目標:文件雙寫+對外暴露第三方文件服務定義的接口和方法
缺狠,提供如下適配器
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.third.IThirdPartyFileOperationService;
import com.cube.dp.adapter.fs.third.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.third.dto.FileDownloadForStreamDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 15:28
* <p>
* 文件操作適配器
* 將自研文件服務與第三方文件服務適配
*/
public interface FileOperationAdaptee extends IThirdPartyFileOperationService {
/**
* 轉(zhuǎn)換
*
* @param dto 參數(shù)
* @return 結(jié)果
*/
default com.cube.dp.adapter.fs.custom.dto.FileUploadDto to(FileUploadDto dto) {
if (dto == null) {
return null;
}
com.cube.dp.adapter.fs.custom.dto.FileUploadDto fileUploadDto = new com.cube.dp.adapter.fs.custom.dto.FileUploadDto();
fileUploadDto.setFileName(dto.getFileName());
fileUploadDto.setFileContent(dto.getFileContent());
fileUploadDto.setContentType(dto.getContentType());
fileUploadDto.setMetadata(dto.getMetadata());
return fileUploadDto;
}
/**
* 轉(zhuǎn)換
*
* @param dto 參數(shù)
* @return 結(jié)果
*/
default com.cube.dp.adapter.fs.custom.dto.FileUploadForStreamDto to(FileUploadForStreamDto dto) {
if (dto == null) {
return null;
}
com.cube.dp.adapter.fs.custom.dto.FileUploadForStreamDto fileUploadDto = new com.cube.dp.adapter.fs.custom.dto.FileUploadForStreamDto();
fileUploadDto.setFileName(dto.getFileName());
//輸入流一般是不能重復讀取的,實際上應該采用byte數(shù)組拷貝或者轉(zhuǎn)換成可重復讀去的輸入流,本案例只是為了演示適配器模式的用法,不嚴格考慮具體的實現(xiàn)細節(jié)了
fileUploadDto.setFileContent(dto.getFileContent());
fileUploadDto.setContentType(dto.getContentType());
fileUploadDto.setMetadata(dto.getMetadata());
return fileUploadDto;
}
/**
* 轉(zhuǎn)換
*
* @param dto 參數(shù)
* @return 結(jié)果
*/
default com.cube.dp.adapter.fs.custom.dto.FileDownloadDto to(FileDownloadDto dto) {
if (dto == null) {
return null;
}
com.cube.dp.adapter.fs.custom.dto.FileDownloadDto fileUploadDto = new com.cube.dp.adapter.fs.custom.dto.FileDownloadDto();
fileUploadDto.setFileName(dto.getFileName());
fileUploadDto.setFileContent(dto.getFileContent());
fileUploadDto.setContentType(dto.getContentType());
fileUploadDto.setMetadata(dto.getMetadata());
return fileUploadDto;
}
/**
* 轉(zhuǎn)換
*
* @param dto 參數(shù)
* @return 結(jié)果
*/
default FileDownloadDto to(com.cube.dp.adapter.fs.custom.dto.FileDownloadDto dto) {
if (dto == null) {
return null;
}
FileDownloadDto fileUploadDto = new FileDownloadDto();
fileUploadDto.setFileName(dto.getFileName());
fileUploadDto.setFileContent(dto.getFileContent());
fileUploadDto.setContentType(dto.getContentType());
fileUploadDto.setMetadata(dto.getMetadata());
return fileUploadDto;
}
/**
* 轉(zhuǎn)換
*
* @param dto 參數(shù)
* @return 結(jié)果
*/
default com.cube.dp.adapter.fs.custom.dto.FileDownloadForStreamDto to(FileDownloadForStreamDto dto) {
if (dto == null) {
return null;
}
com.cube.dp.adapter.fs.custom.dto.FileDownloadForStreamDto fileUploadDto = new com.cube.dp.adapter.fs.custom.dto.FileDownloadForStreamDto();
fileUploadDto.setFileName(dto.getFileName());
//輸入流一般是不能重復讀取的,實際上應該采用byte數(shù)組拷貝或者轉(zhuǎn)換成可重復讀去的輸入流,本案例只是為了演示適配器模式的用法,不嚴格考慮具體的實現(xiàn)細節(jié)了
fileUploadDto.setFileContent(dto.getFileContent());
fileUploadDto.setContentType(dto.getContentType());
fileUploadDto.setMetadata(dto.getMetadata());
return fileUploadDto;
}
/**
* 轉(zhuǎn)換
*
* @param dto 參數(shù)
* @return 結(jié)果
*/
default FileDownloadForStreamDto to(com.cube.dp.adapter.fs.custom.dto.FileDownloadForStreamDto dto) {
if (dto == null) {
return null;
}
FileDownloadForStreamDto fileUploadDto = new FileDownloadForStreamDto();
fileUploadDto.setFileName(dto.getFileName());
//輸入流一般是不能重復讀取的,實際上應該采用byte數(shù)組拷貝或者轉(zhuǎn)換成可重復讀去的輸入流,本案例只是為了演示適配器模式的用法,不嚴格考慮具體的實現(xiàn)細節(jié)了
fileUploadDto.setFileContent(dto.getFileContent());
fileUploadDto.setContentType(dto.getContentType());
fileUploadDto.setMetadata(dto.getMetadata());
return fileUploadDto;
}
}
該適配器首先繼承了第三方文件服務的定義的接口IThirdPartyFileOperationService
问慎,提供了一些將新舊文件服務參數(shù)相互轉(zhuǎn)換的默認方法。
再提供一個抽象的雙寫適配器
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.custom.IFileOperationService;
import com.cube.dp.adapter.fs.third.IThirdPartyFileOperationService;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 16:52
* <p>
* 抽象文件雙寫適配器,文件上傳時,會先將文件上傳只自研文件服務實現(xiàn),然后再將該文件上傳至第三方文件服務實現(xiàn),
* 同一個文件上傳后在自研文件服務和第三方文件服務內(nèi)的文件id是一致的
*/
public abstract class AbstractFileDoubleWriteOperationAdaptor implements FileOperationAdaptee {
private final IFileOperationService fileOperationService;
private final IThirdPartyFileOperationService thirdPartyFileOperationService;
public AbstractFileDoubleWriteOperationAdaptor(IFileOperationService fileOperationService,
IThirdPartyFileOperationService thirdPartyFileOperationService) {
this.fileOperationService = fileOperationService;
this.thirdPartyFileOperationService = thirdPartyFileOperationService;
}
@Override
public String upload(FileUploadDto dto) {
String fileId = fileOperationService.upload(to(dto));
//使用上傳至自研文件服務的文件id作為第三方文件服務上傳文件時的key,這樣就能夠報證同一個文件在兩個文件服務的文件id是一致的,這個邏輯這里就不寫了
return thirdPartyFileOperationService.upload(dto);
}
@Override
public String upload4Stream(FileUploadForStreamDto streamDto) {
String fileId = fileOperationService.upload4Stream(to(streamDto));
//使用上傳至自研文件服務的文件id作為第三方文件服務上傳文件時的key,這樣就能夠報證同一個文件在兩個文件服務的文件id是一致的,這個邏輯這里就不寫了
return thirdPartyFileOperationService.upload4Stream(streamDto);
}
public IFileOperationService getFileOperationService() {
return fileOperationService;
}
public IThirdPartyFileOperationService getThirdPartyFileOperationService() {
return thirdPartyFileOperationService;
}
}
再提供一個文件上傳時雙寫且從自研文件服務中讀取文件的適配器
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.custom.IFileOperationService;
import com.cube.dp.adapter.fs.third.IThirdPartyFileOperationService;
import com.cube.dp.adapter.fs.third.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.third.dto.FileDownloadForStreamDto;
/**
* @author litb
* @date 2022/5/21 17:08
* <p>
* 上傳時雙寫,讀取時從自研文件服務中讀取的適配器
*/
public class DoubleWriteAndReadCustomOperationAdaptor extends AbstractFileDoubleWriteOperationAdaptor {
public DoubleWriteAndReadCustomOperationAdaptor(IFileOperationService fileOperationService,
IThirdPartyFileOperationService thirdPartyFileOperationService) {
super(fileOperationService, thirdPartyFileOperationService);
}
@Override
public FileDownloadDto download(String fileId) {
return to(getFileOperationService().download(fileId));
}
@Override
public FileDownloadForStreamDto download4Stream(String fileId) {
return to(getFileOperationService().download4Stream(fileId));
}
}
這里不提供抽象的文件雙寫適配器AbstractFileDoubleWriteOperationAdaptor
直接在DoubleWriteAndReadCustomOperationAdaptor
實現(xiàn)雙寫邏輯也是可以的(因為實際上只能從自研文件服務中讀取儒老,這樣才能確保增量數(shù)據(jù)和存量數(shù)據(jù)都能讀取成功)
再提供一個獲取文件服務的工廠
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.custom.CustomFileOperationServiceImpl;
import com.cube.dp.adapter.fs.third.IThirdPartyFileOperationService;
import com.cube.dp.adapter.fs.third.OssFileOperationServiceImpl;
/**
* @author litb
* @date 2022/5/21 17:12
* <p>
* 文件服務工廠
*/
public class FileOperationFactory {
public static IThirdPartyFileOperationService getDefault() {
return getInstance(EnumFileOperationType.DOUBLE_WRITE_AND_READ_FROM_CUSTOM);
}
/**
* 獲取對應的文件服務實例
*
* @param operationType 類型
* @return 實例
*/
public static IThirdPartyFileOperationService getInstance(EnumFileOperationType operationType) {
switch (operationType) {
case OSS:
return new OssFileOperationServiceImpl();
case CUSTOM:
return new CustomFileOperationAdaptor(new CustomFileOperationServiceImpl());
case DOUBLE_WRITE_AND_READ_FROM_CUSTOM:
return new DoubleWriteAndReadCustomOperationAdaptor(new CustomFileOperationServiceImpl(),
new OssFileOperationServiceImpl());
default:
throw new IllegalArgumentException("storageType is not support");
}
}
}
各個依賴方可以通過getDefault
方法獲取默認的文件服務實例蝴乔,在雙寫階段提供的是上傳時雙寫+從自研文件服務中讀取的實現(xiàn)记餐,在第二階段存量文件同步完成后升級一下第三方文件服務的版本完全移除自研文件服務的依賴并默認提供第三方文件服務實例即可驮樊,各個依賴方引入新的版本重新發(fā)布一下項目即可,改動程度相對較小。
假設有以下客戶端依賴了文件服務:
一囚衔、文件雙寫+存量數(shù)據(jù)同步階段
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.third.IThirdPartyFileOperationService;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
/**
* @author litb
* @date 2022/5/21 17:16
* <p>
* 假裝這是一個項目
*/
public class ProjectClient {
public static void main(String[] args) {
IThirdPartyFileOperationService operationService =
FileOperationFactory.getInstance(EnumFileOperationType.DOUBLE_WRITE_AND_READ_FROM_CUSTOM);
operationService.upload(new FileUploadDto());
operationService.download("this is fileId");
}
}
模擬下文件上傳及下載挖腰,控制臺輸出如下:
自研文件服務上傳文件...
oss文件服務上傳文件...
自研文件服務下載文件...
可以看出,上傳時是雙寫练湿,讀取時是從自研文件服務中讀取猴仑。
二、存量數(shù)據(jù)同步完成之后
在存量數(shù)據(jù)同步完成之后肥哎,這個時候自研文件服務和oss中的存量數(shù)據(jù)和增量數(shù)據(jù)都是完全一致的辽俗,可以無縫切換到oss文件服務了,完全移除自研文件服務篡诽。
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.third.IThirdPartyFileOperationService;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
/**
* @author litb
* @date 2022/5/21 17:16
* <p>
* 假裝這是一個項目
*/
public class ProjectClient {
public static void main(String[] args) {
IThirdPartyFileOperationService operationService =
//切換到oss實現(xiàn),這一步在文件服務工程下完成即可,切換實現(xiàn)并完全移除自研文件服務后升級打包讓各個依賴方重新引入
FileOperationFactory.getInstance(EnumFileOperationType.OSS);
operationService.upload(new FileUploadDto());
operationService.download("this is fileId");
}
}
模擬下文件上傳和下載崖飘,控制臺輸出如下:
oss文件服務上傳文件...
oss文件服務下載文件...
而且,考慮到不可控因素杈女,還提供了基于第三方文件服務接口定義但是由自研文件服務提供實現(xiàn)的適配器
package com.cube.dp.adapter;
import com.cube.dp.adapter.fs.custom.IFileOperationService;
import com.cube.dp.adapter.fs.third.dto.FileDownloadDto;
import com.cube.dp.adapter.fs.third.dto.FileDownloadForStreamDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadDto;
import com.cube.dp.adapter.fs.third.dto.FileUploadForStreamDto;
/**
* @author litb
* @date 2022/5/21 16:23
* <p>
* 文件適配器,將第三方文件服務與自研文件服務適配
*/
public class CustomFileOperationAdaptor implements FileOperationAdaptee {
private final IFileOperationService customFileOperationService;
public CustomFileOperationAdaptor(IFileOperationService customFileOperationService) {
this.customFileOperationService = customFileOperationService;
}
@Override
public String upload(FileUploadDto dto) {
return customFileOperationService.upload(to(dto));
}
@Override
public String upload4Stream(FileUploadForStreamDto streamDto) {
return customFileOperationService.upload4Stream(to(streamDto));
}
@Override
public FileDownloadDto download(String fileId) {
return to(customFileOperationService.download(fileId));
}
@Override
public FileDownloadForStreamDto download4Stream(String fileId) {
return to(customFileOperationService.download4Stream(fileId));
}
}
假設要突然完全切換到自研文件服務朱浴,在第三方文件服務項目中切換到自研文件重新升級打包即可,各個項目重新引入新版本即可达椰,無需回滾代碼(主要是包路徑調(diào)整)翰蠢。
上述適配器模式的類圖如下所示:
表達能力欠缺一直是我的諸多缺點之一,不知道我上面一頓啰嗦有沒有把我的想法表達清楚啰劲,參考代碼也許更易理解一點梁沧,附上鏈接:適配器模式代碼
總結(jié)
說一下為什么中途寫到適配器模式類型的定義時讓我覺得突然不想再寫了,就像倚天屠龍記里張三豐教張無忌太極拳和太極劍一樣蝇裤,張無忌看了一遍之后雖然全部都忘記了但是最后卻能夠嫻熟的運用出來趁尼,忘與不忘在我看來其中的差別其實就是無形和有形的區(qū)別,有形只是生搬硬套猖辫,而無形卻是融會貫通酥泞;設計模式難學的主要原因就是人為的界定了許多界限,當我在看完適配器類型的定義之后我把我上面寫的代碼往這個定義去套啃憎,嘗試去解釋這里面的一些概念芝囤,但是最終我發(fā)現(xiàn)連我自己都難以說服,所以我決定不再糾結(jié)這些具體的概念和定義辛萍,只是把我的想法表達出來就行了悯姊。
最后,說一些我個人最近的一些感悟贩毕,我寫代碼的時間不算長悯许,最開始時寫代碼的時候,我覺得代碼是難以控制的辉阶,不確定在什么地方會出問題先壕,總是害怕出bug瘩扼;不知道是上個月啥時候開始,我突然覺得我是能夠控制住代碼的垃僚,能夠參照一部分源碼寫出一部分我覺得還不錯的代碼(起碼看起來還不錯)集绰,這應該還是處于有形的階段;但是谆棺,我覺得后面至少應該還有一個階段栽燕,當我寫出一段代碼后我會由衷的感嘆:這段代碼是這樣不是因為我想要這樣寫,而是這段代碼原本就應該是這個樣子改淑,就算是再讓我寫一千遍一萬遍碍岔,它也應該就是這個樣子。