[객체 지향 프로그래밍 입문] 추상화의 예시

kshired·2021년 8월 19일
0

예시

  • 기능 예시
    • 클라우드 파일 통합 관리 기능 개발
    • 대상 클라우드 : 드롭박스, 박스
    • 주요 기능
      • 각 클라우드의 파일 목록 조회, 다운로드, 업로드, 삭제, 검색

추상화하지 않은 구현

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가 난무한다는 것을 알 수 있다.

현재는 실제 구현을 생략했지만 만약 실제로 표현했다면, 위 코드는 엄청 복잡해질 것이다.

그리고 저기에 또 새로운 클라우드를 지원하게 된다면, 또 한번 여러 복잡한 코드를 수정해야하는 불상사가 생기게 된다.

개발 시간 증가 이유

  • 코드 구조가 길어지고 복잡해짐
    • 새로운 클라우드 추가시 모든 메서드에 새로운 if 블록 추가
      • 중첩 if-else는 복잡도를 배로 증가시킴
      • if-else가 많을수록 진척이 더딤
  • 관련 코드가 여러 곳에 분산됨
    • 한 클라우드 처리와 관련된 코드가 여러 메서드에 흩어짐
  • 결과적으로 코드 가독성과 분석 속도 저하
    • 코드 추가에 따른 노동 시간 증가
    • 실수하기 쉽고 이로 인한 불필요한 디버깅 시간 증가

추상화해보면

여러 클라우드의 공통점은 ⇒ 클라우드 파일 시스템

이것을 추상화해보자.

추상화한 구현

DROPBOX용 구현

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

위 기능들도 구체 클래스가 아닌 인터페이스를 사용함으로써 유연함을 증가시킴.

BOX 클라우드 지원을 추가

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의 코드 수정없이, 새로운 클라우드 지원을 추가할 수 있게 된다.

이것이 바로 OCP(Open-Closed Principle)

새로운 클라우드를 지원하는 확장에는 열려있고, CloudFileManger의 수정에는 닫혀있다.

profile
글 쓰는 개발자

0개의 댓글