public enum CloudId {
DROPBOX,
BOX,
}
public class FileInfo {
private CloudId cloudId;
private String fileId;
private String name;
private long length;
}
// 추상화하지 않은 구현
public class CloudFileManager {
// 파일 목록 조회
public List<FileInfo> getFileInfos(CloudId cloudId){
if(cloudId == CloudId.DROPBOX){
DropboxClient db = //...;
List<DbFile> dbFiles = db.getFiles();
List<FileInfo> result = new ArrayList<>();
for(DbFile dbFile : dbFiles){
FileInfo fi = new FileInfo();
fi.setCloudId(CloudId.DROPBOX);
fi.setFileId(fi.getFileId());
result.add(fi);
}
return result;
} else if (cloudId == CloudId.BOX){
BoxClient bc = //...;
}
}
// 다운로드
public void download(FileInfo file, File localTarget){
if(cloudId == CloudId.DROPBOX){
DropboxClient db = //...;
FileOutputStream out = new FileOutputStream(localTarget);
db.copy(file.getCloudId(), out);
out.close();
} else if (cloudId == CloudId.BOX){
BoxClient bc = //...;
InputStream is = bc.getInputStream(file.getId());
FileOutputStream out = new FileOutputStream(localTarget);
CopyUtil.copy(is, out);
}
}
}
이제 여기에 여러 다른 클라우드를 지원하고 기능(클라우드간 복사)을 추가한다고 해보겠습니다.
// 추상화하지 않은 구현 : 새로운 클라우드를 지원
public List<FileInfo> getFileInfos(CloudId cloudId){
if(cloudId == CloudId.DROPBOX){
DropboxClient db = //...;
List<DbFile> dbFiles = db.getFiles();
List<FileInfo> result = new ArrayList<>();
for(DbFile dbFile : dbFiles){
FileInfo fi = new FileInfo();
fi.setCloudId(CloudId.DROPBOX);
fi.setFileId(fi.getFileId());
result.add(fi);
}
return result;
} else if (cloudId == CloudId.BOX){
BoxClient bc = //...;
} else if (cloudId == CloudId.NCLOUD){
// ...
} else if (cloudId == CloudId.DCLOUD){
// ...
} else if (cloudId == CloudId.SCLOUD){
// ...
}
}
// 추상화하지 않은 구현 : 클라우드간 복사
public FileInfo copy(FileInfo fileInfo, CloudId to){
CloudId from = fileInfo.getCloudId();
if(to == CloudId.DROPBOX){
if (cloudId == CloudId.BOX){
//...;
} else if (cloudId == CloudId.NCLOUD){
// ...
} else if (cloudId == CloudId.DCLOUD){
// ...
} else if (cloudId == CloudId.SCLOUD){
// ...
}
} else if(){
//...
} // ....
}
만약 위와 같이 추상화하지 않은 구현을 통해 기능을 추가하려고 하면 if-else가 난무한다는 것을 알 수 있다.
현재는 실제 구현을 생략했지만 만약 실제로 표현했다면, 위 코드는 엄청 복잡해질 것이다.
그리고 저기에 또 새로운 클라우드를 지원하게 된다면, 또 한번 여러 복잡한 코드를 수정해야하는 불상사가 생기게 된다.
여러 클라우드의 공통점은 ⇒ 클라우드 파일 시스템
이것을 추상화해보자.
public class DropBoxFileSystem implements CloudFileSystem {
private DropBoxClient dbClient = new DropBoxClient();
@override
public List<CloudFile> getFiles(){
List<DbFile> dbFiles = dbClient.getFiles();
// DropBoxCloudFile List가 아닌 인터페이스를 사용
List<CloudFile> results = new ArrayList<>();
for(DbFile dbFile : dbFiles){
DropBoxCloudFile cf = new DropBoxCloudFile(dbFile, dbClient);
results.add(cf);
}
return results;
}
}
public class DropBoxCloudFile implements CloudFile {
private DropBoxClient dbClient;
private DbFile dbFile;
public DropBoxCloudFile(DbFile dbFile, DropBoxClient dbClient){
this.dbFile = dbFile;
this.dbClient = dbClient;
}
// getId, getName, getLength....
}
위 코드를 보면, DropBoxFileSystem과 DropBoxCloudFile 모두 인터페이스를 implements 하고 있는 것을 볼 수 있음.
public List<CloudFile> getFileInfos(CloudId cloudId){
CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(cloudId);
return fileSystem.getFiles();
}
public void download(CloudFile file, File localTarget){
file.write(new FileOutputStream(localTarget));
}
위 기능들도 구체 클래스가 아닌 인터페이스를 사용함으로써 유연함을 증가시킴.
CloudFile과 CloudFileSystem을 구현한 구체 클래스를 구현하면 된다.
그리고 CloudFileSystemFactory에는 cloudId에 맞는 CloudFileSystem을 return하면 된다.
BOX 클라우드가 추가되고 파일 목록, 다운로드 기능을 봐보자.
public List<CloudFile> getFileInfos(CloudId cloudId){
CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(cloudId);
return fileSystem.getFiles();
}
public void download(CloudFile file, File localTarget){
file.write(new FileOutputStream(localTarget));
}
구체 클래스가 아닌 인터페이스를 사용했기때문에, 변경할 필요가 없어진다.
public void copy(CloudFile file, CloudId cloudId){
CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(cloudId);
fileSystem.copyFrom(file);
}
각 CloudFileSystem 구체 클래스의 실제 구현이 달라도 인터페이스에만 의존하기 때문에 내부를 모르더라도 구현이 가능.
public class CloudFileManager {
public List<CloudFile> getFileInfos(CloudId cloudId){
CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(cloudId);
return fileSystem.getFiles();
}
public void download(CloudFile file, File localTarget){
file.write(new FileOutputStream(localTarget));
}
public void copy(CloudFile file, CloudId cloudId){
CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(cloudId);
fileSystem.copyFrom(file);
}
}
추상화한 타입으로만 핵심 기능 구현이 가능해졌다.
그리고 결과적으로 CloudFileManager의 코드 수정없이, 새로운 클라우드 지원을 추가할 수 있게 된다.
새로운 클라우드를 지원하는 확장에는 열려있고, CloudFileManger의 수정에는 닫혀있다.