객체 지향 발담그기 jco 컨퍼런스 14회

Post on 12-Jun-2015

7.143 Views

Category:

Technology

6 Downloads

Preview:

Click to see full reader

DESCRIPTION

2014년 JCO 컨퍼런스 14회, 객체 지향 발들이기 세션 발표 자료.

TRANSCRIPT

객체 지향 발담그기 최범균, madvirus@madvirus.net

오늘의 주제

추상화 + 유연함

출처: http://www.flickr.com/photos/42402605@N07/5693120074

여러 클라우드에 올라간 파일들을 통합 관리하는 앱을 만들어보자.

빨리 선보이기 위한 최초 0.1 버전

•  대상 클라우드 o  드롭박스, 박스

•  주요 기능 o  각 클라우드의 목록 조회하기 o  각 클라우드에서 파일 다운로드 하기

파일 정보 표현

public class FileInfo { private CloudId cloudId; private String name; private long length; … // get 메서드 }

public enum CloudId { DROPBOX, BOX; }

파일 목록 조회 public List<FileInfo> getFileInfos(CloudId cloudId) { if (cloudId == CloudId.DROPBOX) { DropboxClient dc = …; List<DbFile> dbFiles = db.getFiles(); List<FileInfo> result = new ArrayList<>(); for (DbFile dbFile : dbFiles) { FileInfo fi = new FileInfo(); fi.setCloudId(CloudId.DROPBOX); … result.add(fi); } return result; } else if (cloudId == CloudId.BOX) { BoxService boxSvc = …; … // } }

파일 다운로드 (계속) public void download(FileInfo file, File localTarget) { if (file.getCloudId() == CloudId.DROPBOX) { DropboxClient dc = …; FileOutputStream out = new FileOutputStream(localTarget); dc.copy(file.getFileId(), out); out.close(); } else if (file.getCloudId() == CloudId.BOX) { // TODO } }

public class FileInfo { private CloudId cloudId; private String fileId; private String name; ...

for (DbFile dbFile : dbFiles) { FileInfo fi = new FileInfo(); fi.setFileId(dbFile.getId()); fi.setCloudId(cloudId); … }

동작하도록 반영

파일 다운로드 public void download(FileInfo file, File localTarget) { if (file.getCloudId() == CloudId.DROPBOX) { ... } else if (file.getCloudId() == CloudId.BOX) { BoxService boxSvc = …; InputStream is = boxSvc.getInputStream(file.getId()); FileOutputStream out = new FileOutputStream(localTarget); CopyUtil.copy(is, out); } }

야호!

•  무사히 동작하는 0.1 버전 완성 •  0.5 버전을 향해

o  기능 확대 §  파일 업로드 §  파일 삭제 §  검색 §  이미지 미리보기

0.5v 기능 추가 public FileInfo upload(File file, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } }

public void delete(String fileId, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } }

public List<FileInfo> search(String query, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } }

음...

•  무사히 동작하는 0.5 버전 완성 •  베타 버전을 향해

o  클라우드 추가 §  S클라우드 §  D클라우드 §  N클라우드 §  ...

o  기능 추가 §  클라우드 간 파일 복사

과정 public FileInfo upload(File file, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } }

public FileInfo upload(File file, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } else if (cid == CloudId.BOX) { ... } }

public FileInfo upload(File file, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } else if (cid == CloudId.SCLOUD) { ... } }

public FileInfo upload(File file, CloudId cid) { if (cid == CloudId.DROPBOX) { ... } else if (cid == CloudId.BOX) { ... } else if (cid == CloudId.SCLOUD) { ScloudClient scClient = …; ... } }

코드복사

조건변경

구현변경

흔한 실수 public List<FileInfo> getFileInfos(CloudId cloudId) { if (cloudId == CloudId.DROPBOX) { … } else if (cloudId == CloudId.BOX) { … } else if (cloudId == CloudId.SCLOUD) { … } else if (cloudId == CloudId.DCLOUD) { … fi.setCloudId(CloudId.SCLOUD); … } else if (cloudId == CloudId.NCLOUD) { … } }

public void delete(String fileId, CloudId cid) { if (cid == CloudId.DROPBOX) { ... return result; } else if (cloudId == CloudId.BOX) { … // } else if (cloudId == CloudId.SCLOUD) { … // alert(“파일이 존재하지 않습니다.”); ... } else if (cloudId == CloudId.DCLOUD) { … alert(“파일이 존재하지 않습니다.”); ... } else if (cloudId == CloudId.NCLOUD) { … // } }

OO;;

음.. 파일이 있는데,

왜 없다 그러지?

클라우드 간 파일 복사 기능 고려사항

•  URL로부터 복사해서 가져오는 클라우드 존재 o  그런데, URL을 제공하지 않는 클라우드 존재

•  InputStream을 제공하면 되는 클라우드 존재 o  그런데, InputStream을 제공하지 않는 클라우드 존재

•  로컬 File을 제공해야 하는 클라우드 존재

클라우드 간 파일 복사 기능 구현 ... public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) { if (to == CloudId.DROPBOX) { // URL로 복사하기 지원 DropBoxClient dbClient = …; if (from == CloudId.BOX) { dbClient.copyFromUrl(“http://www.box.com/files/”+fileInfo.getFileId()); } else if (from == CloudId.SCLOUD) { ScloudClient sClient = …; InputStream is = sClient.getInputStream(fileInfo.getFileId()); dbClient.copyFromInputStream(is, fileInfo.getName()); } else if (from ==CloudId.DCLOUD) { dbClient.copyFromUrl(“http://www.dcloud.com/getfile?fileId=”+fileInfo.getFileId()); } else if (from == CloudId.NCLOUD) { NCloudClient nClient = …; File temp = File.createTemp(); nClient.save(fileInfo.getFileId(), temp); InputStream is = new FileInputStream(temp); dbClient.copyFromInputStream(is, fileInfo.getName()); } } else if (to == CloudId.BOX) { // 코드 계속 ….

클라우드 간 파일 복사 기능 구현 ... } else if (to == CloudId.BOX) { // URL로 복사하기 지원 BoxService boxService = …; if (from == CloudId.DROPBOX) { boxService.createFileByUrl(“http://www.dropbox.com/box/files/”+fileInfo.getFileId()); } else if (from == CloudId.SCLOUD) { ScloudClient sClient = …; InputStream is = sClient.getInputStream(fileInfo.getFileId()); boxService.uploadFile(is, fileInfo.getName()); } else if (from ==CloudId.DCLOUD) { boxService.createFileByUrl(“http://www.dcloud.com/getfile?fileId=”+fileInfo.getFileId()); } else if (from == CloudId.NCLOUD) { NCloudClient nClient = …; File temp = File.createTemp(); nClient.save(fileInfo.getFileId(), temp); boxService.uploadFile(temp, fileInfo.getName()); } } else if (to == CloudId.SCLOUD) { // 코드 계속 ….

클라우드 간 파일 복사 기능 구현 ... } else if (to == CloudId.SCLOUD) { ScloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.DCLOUD) { // 코드 계속 ….

클라우드 간 파일 복사 기능 코드 (if-else 구조만 표시)

public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) { if (to == CloudId.DROPBOX) { // URL로 복사하기 지원 DropBoxClient dbClient = …; if (from == CloudId.BOX) { ... } else if (from == CloudId.SCLOUD) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.BOX) { // URL로 복사하기 지원 BoxService boxService = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.SCLOUD) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.SCLOUD) { ScloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... }

} else if (to == CloudId.DCLOUD) { DcloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.SCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.NCLOUD) { NcloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.SCLOUD) { … ... } else if (from == CloudId.DCLOUD) { … ... } } } .

새로운 요구사항 public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) { if (to == CloudId.DROPBOX) { // URL로 복사하기 지원 DropBoxClient dbClient = …; if (from == CloudId.BOX) { ... } else if (from == CloudId.SCLOUD) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.BOX) { // URL로 복사하기 지원 BoxService boxService = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.SCLOUD) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.SCLOUD) { ScloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.DCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... }

} else if (to == CloudId.DCLOUD) { DcloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.SCLOUD) { … ... } else if (from == CloudId.NCLOUD) { … ... } } else if (to == CloudId.NCLOUD) { NcloudClient sClient = …; if (from == CloudId.DROPBOX) { … ... } else if (from == CloudId.BOX) { … ... } else if (from ==CloudId.SCLOUD) { … ... } else if (from == CloudId.DCLOUD) { … ... } } } .

클라우드 10개 추가

1개 클라우드 추가 비용

시간

개발 비용

예전엔 빨리 했는데…...

개발시간이 왜

증가하는가?

•  코드 구조가 길어지고 복잡해짐 o  새로운 클라우드 추가시 모든 메서드에 새로운 if 블록 추가

§  중첩 if-else는 복잡도 배로 증가 §  if-else가 많을수록 진척 더딤 (신중 모드)

•  관련 코드가 여러 곳에 분산됨 o  한 클라우드 처리와 관련된 코드가 여러 메서드에 흩어짐

•  결과적으로, 코드 가독성과 분석 속도 저하 o  코드 추가에 따른 노동 시간 증가 o  실수하기 쉽고, 이로 인한 불필요한 디버깅 시간 증가

개발 시간 증가 이유

해결책

데이터와기능을함께갖는

객체로추상화Abstraction

DROPBOX

추상화

BOX

SCLOUD

DCLOUD

NCLOUD

클라우드 파일시스템

클라우드 파일 시스템 설계

DROPBOX용 구현 (계속)

public class DropBoxFileSystem implements CloudFileSystem { private DropBoxClient dbClient = new DropBoxClient(...); @Override public List<CloudFile> getFiles() { List<DbFile> dbFiles = dbClient.getFiles(); List<CloudFile> results = new ArrayList<>(dbFiles.size()); for (DbFile file : dbFiles) { DropBoxCloudFile cf = new DropBoxCloudFile(file, dbClient); results.add(cf); } return results; }

DROPBOX용 구현 public class DropBoxCloudFile implements CloudFile { private DropBoxClient dbClient; private DbFile dbFile; public DropBoxCloudFile( DbFile dbFile, dbClient) { this.dbFile = dbFile; this.dbClient = dbClient; } public String getId() { return dbFile.getId(); } public boolean hasUrl() { return true; } public String getUrl() { return dbFile.getFileUrl(); }

public String getName() { return dbFile.getFileName(); } public InputStream getInputStream() { return dbClient .createStreamOfFile(dbFile); } public void write(OutputStream out) { ... } public void delete() { dbClient.deleteFile(dbFile.getId()); } … }

v0.1의 파일 목록/다운로드 기능 구현

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)); }

현재 드롭박스 지원만 구현 된 상태

BOX 클라우드 지원 추가

v0.1의 파일 목록/다운로드 기능 구현 (다시)

박스 지원도 반영 (코드 그대로네… 응?)

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)); }

복잡했던 파일 복사 기능의 변신: copyFrom(CloudFile file) 추가

public void copy(CloudFile file, CloudId target) { CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(target); fileSystem.copyFrom(file); }

복잡했던 파일 복사 기능의 변신: 파일시스템 별 copyFrom 메서드

-- DropBoxFileSystem private DropBoxClient dbClient = new DropBoxClient(...); public void copyFrom(CloudFile file) { if (file.hasUrl()) dbClient.copyFromUrl(file.getUrl()); else dbClient.copyFromInputStream(file.getInputStream(), file.getName()); }

-- NcloudFileSystem private NcloudClient nClient = new NCloudClient(...); public void copyFrom(CloudFile file) { File tempFile = File.createTemp(); file.write(new FileOutputStream(tempFile)); nClient.upload(tempFile, file.getName()); }

추상화를 잘 했더니 public class CloudFileManeger { 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 target) { CloudFileSystem fileSystem = CloudFileSystemFactory.getFileSystem(target); fileSystem.copyFrom(file); } … ... }

추상화된 타입으로만 핵심 기능 구현

가능

추상화를 잘 했더니

코드 수정없이

새로운 클라우드 지원을

추가

이것이 바로 OCP Open-Closed principle

Closed for Modification 수정엔 닫혀 있음

Open for Extenstion 확장에 열려 있음

추상화를 잘 했더니: 기능이 만들어 짐

•  URL 제공 여부 및 구성 코드가 메서드로 이동

public FileInfo copy(FileInfo fileInfo, CloudId from, CloudId to) { if (to == CloudId.DROPBOX) { DropBoxClient dbClient = …; if (from == CloudId.BOX) { dbClient.copyFromUrl( “http://www.box.com/files/”+ fileInfo.getFileId()); } else if (from == CloudId.SCLOUD) { ScloudClient sClient = …; InputStream is = sClient.getInputStream( fileInfo.getFileId()); dbClient.copyFromInputStream(is, fileInfo.getName());

-- DropBoxFileSystem private DropBoxClient dbClient = ...; public void copyFrom(CloudFile file) { if (file.hasUrl()) dbClient.copyFromUrl(file.getUrl()); else dbClient.copyFromInputStream( file.getInputStream(), file.getName()); }

이것이 바로 캡슐화Encapsulation

•  내부 구현을 외부에 감춤 o  내부 구현을 변경해도 이를 사용하는 코드 영향 최소화

-- DropBoxFileSystem private DropBoxClient dbClient = ... public void copyFrom(CloudFile file) { if (file.hasUrl()) dbClient.copyFromUrl(file.getUrl()); else dbClient.copyFromInputStream( file.getInputStream(), file.getName()); }

URL 생성 규칙이 변경되어도 이 코드는 바뀌지 않음

URL 제공 여부가 변경되어도 이 코드는 바뀌지 않음

OCP/캡슐화가 적용된 코드의 1개 클라우드 추가 비용

시간

개발 비용

그냥 막 작성한 코드

OCP/캡슐화 등 좋은 설계를 가진 코드

이것이 객체 지향을 해야하는 이유

비용 (즉, 변경의 유연함)

추상화는 언제?

•  유연함이 필요한 시점(변화가 생기는 시점) o  예, 1개 클라우드 지원에서 2개 클라우드로 확장 o  예, 물류업체 추가

•  아직 구현할 수 없는 부분이 존재할 때 o  예, 물류 업체와의 연동 프로토콜 미확정

§  물류 시스템 연동을 추상화해서 인터페이스로 표현

•  추상화(모델링)에는 비용이 발생 o  과도한 추상화(모델링)는 불필요한 복잡도를 증가시킴

정리

•  개발 시간을 줄이기 위해 필요한 것 중 하나, o  변화를 수용할 수 있는 설계/구현

•  잘 된 추상화/캡슐화가 해 주는 것 o  변화에 대한 유연함 제공 o  이는 개발 비용(즉, 시간) 절감 효과!

•  익혀야 할 것 → 객체 지향 o  캡슐화, 추상화, 다형성, SOLID

•  더불어 익힐 것 o  좋은 코드, 패턴, 테스트 주도 개발, 리팩토링 등

참고 서적

감사합니다.

연락은 트위터(@madvirus) 또는 이메일로(madvirus@madvirus.net)

top related