掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
冗余代碼向來是代碼的一種壞味道,也是我們程序員要極力避免的。今天我通過一個示例和大家分享下解決冗余代碼的3個手段,看看哪個最好。

創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供天峨網(wǎng)站建設(shè)、天峨做網(wǎng)站、天峨網(wǎng)站設(shè)計(jì)、天峨網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)與制作、天峨企業(yè)網(wǎng)站模板建站服務(wù),10多年天峨做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡(luò)服務(wù)。
為了描述這個問題,我將使用 FtpClient 作為示例。要從 ftp 服務(wù)器獲取一些文件,你需要先建立連接,下一步是登錄,然后執(zhí)行查看ftp文件列表、刪除ftp文件,最后注銷并斷開連接, 代碼如下:
public class FtpProvider{
private final FTPClient ftpClient;
public FTPFile[] listDirectories(String parentDirectory) {
try {
ftpClient.connect("host", 22);
ftpClient.login("username", "password");
return ftpClient.listDirectories(parentDirectory);
} catch (IOException ex) {
log.error("Something went wrong", ex);
throw new RuntimeException(ex);
} finally {
try {
ftpClient.logout();
ftpClient.disconnect();
} catch (IOException ex) {
log.error("Something went wrong while finally", ex);
}
}
}
public boolean deleteFile(String filePath) {
try {
ftpClient.connect("host", 22);
ftpClient.login("username", "password");
return ftpClient.deleteFile(filePath);
} catch (IOException ex) {
log.error("Something went wrong", ex);
throw new RuntimeException(ex);
} finally {
try {
ftpClient.logout();
ftpClient.disconnect();
} catch (IOException ex) {
log.error("Something went wrong while finally", ex);
}
}
}
}
正如上面代碼所示,listDirectories和downloadFtpFile?中都包含了ftp連接、登錄以及最后的注銷操作,存在大量冗余的代碼,那有什么更好的辦法清理冗余代碼呢?下面推薦3個做法,所有三個提出的解決方案都將實(shí)現(xiàn)以下 FtpProvider 接口,我將比較這些實(shí)現(xiàn)并選擇更好的一個。
public interface FtpProvider {
FTPFile[] listDirectories(String directory) throws IOException;
boolean deleteFile(String filePath) throws IOException;
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface FtpOperation {
}
@Slf4j
@Service
class FtpProviderImpl implements FtpProvider {
private final FTPClient ftpClient;
@Override
public FTPFile[] listDirectories(String directory) throws IOException {
return ftpClient.listDirectories(directory);
}
@Override
public boolean deleteFile(String filePath) throws IOException {
return ftpClient.deleteFile(filePath);
}
}
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class FtpOperationProxy {
private final FTPClient ftpClient;
@Around("@annotation(daniel.zielinski.redundancy.proxyaop.infrastructure.FtpOperation)")
public Object handle(ProceedingJoinPoint joinPoint) throws Throwable {
try {
ftpClient.connect("host", 22);
ftpClient.login("username", "password");
return joinPoint.proceed();
} catch (IOException ex) {
log.error("Something went wrong", ex);
throw new RuntimeException(ex);
} finally {
try {
ftpClient.logout();
ftpClient.disconnect();
} catch (IOException ex) {
log.error("Something went wrong while finally", ex);
}
}
}
}
所有用@FtpOperation? 注解的方法都會在這個地方執(zhí)行joinPoint.proceed()。
@FunctionalInterface
interface FtpOperation{
R apply(T t) throws IOException;
}
@RequiredArgsConstructor
@Slf4j
@Service
public class FtpOperationTemplate {
private final FTPClient ftpClient;
publicK execute(FtpOperation ftpOperation) {
try {
ftpClient.connect("host", 22);
ftpClient.login("username", "password");
return ftpOperation.apply(ftpClient);
} catch (IOException ex) {
log.error("Something went wrong", ex);
throw new RuntimeException(ex);
} finally {
try {
ftpClient.logout();
ftpClient.disconnect();
} catch (IOException ex) {
log.error("Something went wrong while finally", ex);
}
}
}
}
@RequiredArgsConstructor
@Slf4j
@Service
class FtpProviderFunctionalInterfaceImpl implements FtpProvider {
private final FtpOperationTemplate ftpOperationTemplate;
public FTPFile[] listDirectories(String parentDirectory) {
return ftpOperationTemplate.execute(ftpClient -> ftpClient.listDirectories(parentDirectory));
}
public boolean deleteFile(String filePath) {
return ftpOperationTemplate.execute(ftpClient -> ftpClient.deleteFile(filePath));
}
}
我們正在 FtpOperationTemplate? 上執(zhí)行方法 execute? 并且我們正在傳遞 lambda? 表達(dá)式。我們將放入 lambda? 中的所有邏輯都將代替 ftpOperation.apply(ftpClient) 函數(shù)執(zhí)行。
@RequiredArgsConstructor
@Slf4j
@Service
abstract class FtpOperationTemplate{
protected abstract K command(FTPClient ftpClient, T input) throws IOException;
public K execute(FTPClient ftpClient, T input) {
try {
ftpClient.connect("host", 22);
ftpClient.login("username", "password");
return command(ftpClient, input);
} catch (IOException ex) {
log.error("Something went wrong", ex);
throw new RuntimeException(ex);
} finally {
try {
ftpClient.logout();
ftpClient.disconnect();
} catch (IOException ex) {
log.error("Something went wrong while finally", ex);
}
}
}
}
@Slf4j
@Service
class FtpOperationListDirectories extends FtpOperationTemplate{
@Override
protected FTPFile[] command(FTPClient ftpClient, String input) throws IOException {
return ftpClient.listDirectories(input);
}
}
@Slf4j
@Service
class FtpOperationDeleteFile extends FtpOperationTemplate{
@Override
protected Boolean command(FTPClient ftpClient, String input) throws IOException {
return ftpClient.deleteFile(input);
}
}
@RequiredArgsConstructor
@Slf4j
@Service
public class FtpProviderTemplateImpl implements FtpProvider {
private final FtpOperationTemplateftpOperationListDirectories;
private final FtpOperationTemplateftpOperationDeleteFile;
private final FTPClient ftpClient;
public FTPFile[] listDirectories(String parentDirectory) {
return ftpOperationListDirectories.execute(ftpClient, parentDirectory);
}
public boolean deleteFile(String filePath) {
return ftpOperationDeleteFile.execute(ftpClient, filePath);
}
}
我們正在 FtpOperationTemplate? 上執(zhí)行方法 execute? 并在那里傳遞我們的參數(shù)。因此執(zhí)行方法的邏輯對于 FtpOperationTemplate 的每個實(shí)現(xiàn)都是不同的。
我們現(xiàn)在來比較下上面種方式:
向 FtpProvider? 接口添加一個新方法,需要我們僅在一個地方進(jìn)行更改。我們可以輕松地將我們的 FtpProvider? 注入到其他服務(wù)中。此解決方案的強(qiáng)項(xiàng)可能是 @FtpOperation? 注釋,它可以在 FtpProvider 上下文實(shí)現(xiàn)之外使用,但是將 Ftp 操作的邏輯劃分到單獨(dú)的類中并不是一個好方法。
向接口 FtpProvider? 添加一個新方法,需要我們僅在一個地方進(jìn)行更改。我們可以輕松地將我們的 FtpProvider 注入到其他服務(wù)中。我們將ftp操作的邏輯封裝在一個類中。相對于上面的方式,我們也沒有用到AOP的庫,所以我個人還是比較推薦的。
向接口 FtpProvider? 添加一個新方法,需要我們在兩個地方進(jìn)行更改。我們需要添加一個新的類,會導(dǎo)致類爆炸,另外,我們還需要將實(shí)現(xiàn)注入到 FtpProvider。
如果是你,你會選擇哪種方式呢?還是有更好的方法?

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流