[オブジェクト向けプログラミング入門]抽象例


  • 機能例
    開発
  • クラウドファイル統合管理機能
  • ターゲットクラウド:Dropbox,box
  • キー機能
  • 各クラウドのファイルリスト
  • を参照、ダウンロード、アップロード、削除、検索する

    非抽象実装

    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は複雑さを2倍に
  • if-elseが多ければ多いほど、進展が速くなります.
  • 関連コードが複数の場所に分散
  • クラウド処理に関するコードは、複数の方法において
  • に分散する.
  • は、コードの可読性と分析速度の低下をもたらす
  • コードは稼働時間
  • を増加させた.
  • エラーが発生しやすく、不要なデバッグ時間を増やす
  • 抽象的な話。


    複数のクラウドの共通点は、1クラウドファイルシステム
    これを抽象化してください.

    抽象的な実装


    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がインタフェースを実現していることがわかります.

    ファイルリスト、ダウンロード機能の実装

    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クラウドのサポートを追加



    クラウドファイルとクラウドファイルシステムの具体的なクラスを実現すればよい.
    その後、CloudFileSystem FactoryでCloudIdに該当するCloudFileSystemを戻せばよい.
    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)です



    新しいクラウドをサポートする拡張機能はオープンであり、CloudFileManagerの変更は閉じられています.