[オブジェクト向けプログラミング入門]機能と責任の分離


きのうぶんかい

  • 機能はサブ機能に分解可能
  • 上記のように、パスワードを変更する機能を複数のサブ機能に分解することができます.

    誰が機能を提供しますか?

  • 機能
    -切断された各機能を正しく割り当てます.
  • これらのサブ機能は、担当するクラスに割り当てる必要があります.

    サブ機能の使用

    public class ChangePasswordService {
    	public Result changePassword(String id, String oldPw, String newPw) {
    		Member mem = memeberRepository.findOne(id);
    		if(mem == null){
    			return Result.NO_MEMBER;
    		}
    		try {
    			mem.changePassword(oldPw, newPw);
    			return Result.SUCCESS;
    		} catch(BadPasswordException ex){
    			return Result.BAD_PASSWORD;
    		}
    
    	}
    
    }  
    上記のコードから見ると、MemebeRepositoryMemberはそれぞれの機能を提供している.

    大類、大法

  • クラスまたは方法の増加に伴い、プロセス駆動の問題も増加する.
  • 大クラス→マルチフィールド、マルチメソッド共有
  • 大方法→多変数、多コード共有
  • 複数の機能が1つのクラス/メソッドに混在する可能性が高い
  • の責任に従って適切なコード分離を行う必要がある
  • いくつかの責任配分/分離方法


    アレイの適用

  • 通常のロール分離
  • 単純ネットワーク
  • コントローラ、サービス、DAO
  • 複雑なドメイン
  • エンティティ、価値、リポジトリ、ドメインサービス
  • AOP
  • 規格(共通)
  • GoF
  • ファクトリ、ビルダー、ポリシー、テンプレートメソッド、エージェント/デコーダなど.
  • ぶんりけいさんきのう



    上記の計算部分を新しいクラスに分割する実装方法

    れんどうぶんり

  • ネットワーク、メッセージ、ファイルなどの連動処理コードを分離する
  • ネットワーク処理などの外部接続を独立したクラスに分離

    条件分岐は抽象的である

  • 連続if-else抽象化の悩み
  • 抽象に関する文章で述べたように,複数の条件分岐が必要であれば抽象的に解決する.

    注意:明確な意図のある名前を使用する

  • 例:HTTP読み出し推奨データを分離する機能.
  • RecommendService > HttpService
  • は、上記のような意図の名称
  • を用いる.

    ロールの分離とテスト

  • キャラクタの分離に成功すると、テストがより容易になります.
  • 左側のコードのように記述されている場合は、リポジトリとともにテストする必要があります.
    ただし,右側のように切り離すと,PointCalculatorのみが単独でテストできる.

    分離練習


    分離練習1

    public class CashClient {
    	private SecretKeySpec keySpec;
    	private IvParamterSpec ivSpec;
    
    	private Res post(Req req){
    		String reqBody = toJson(req);
    
    		Cipher cipher = Cipher.getInstance(DEFAULT_TRANSFORM);
    		cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
    		String encReqBody = new String(Base64.getEncoder().encode(chipher.doFinal(reqBody)));
    		
    		ResponseEntity<String> responseEntity = restTemplate.postForEntity(api, encReqBody, String.class);
    		
    		String encRespBody = responseEntity.getBody();
    		
    		Cipher cipher2 = Cipher.getInstance(DEFAULT_TRANSFORM);
    		cipher2.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
    		String respBody = new String(chipher2.doFinal(Base64.getDecoder().decode(encRespBody)));
    	
    		return jsonToObj(respBody);
    	}
    
    }
    上記コードでは、Cipherにより暗号化と復号化(計算機能)を分離することができる.
    分離は以下の通りです.
    public class CashClient {
    	private Cryptor cryptor;
    
    	private Res post(Req req){
    		String reqBody = toJson(req);
    
    		String encReqBody = cryptor.encrypt(reqBody);
    		
    		ResponseEntity<String> responseEntity = restTemplate.postForEntity(api, encReqBody, String.class);
    		String encRespBody = responseEntity.getBody();
    		
    		String respBody = cryptor.decrypt(encRespBody);
    	
    		return jsonToObj(respBody);
    	}
    
    }
    
    public class Cryptor {
    	private SecretKeySpec keySpec;
    	private IvParamterSpec ivSpec;
    	
    	public String encrypt(String plain){
    		Cipher cipher = Cipher.getInstance(DEFAULT_TRANSFORM);
    		cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
    		return new String(Base64.getEncoder().encode(chipher.doFinal(reqBody)));
    	}
    
    	public String decrypt(String encrypted){
    		Cipher cipher = Cipher.getInstance(DEFAULT_TRANSFORM);
    		cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
    		return new String(chipher.doFinal(Base64.getDecoder().decode(encRespBody)));
    	}
    
    }

    分離練習2

    public class Rental {
    	private Movie movie;
    	private int daysRented;
    
    	public int getFrequentRenterPoints(){
    		if(movie.getPriceCode() == Movie.NEW_RELEASE && daysRented > 1 ){
    			return 2;
    		}
    		return 1;
    	}
    
    }
    
    public class Movie {
    	public static int REGULAR = 0;
    	public static int NEW_RELEASE = 1;
    	private int priceCode;
    		
    	public int getPriceCode(){
    		return priceCode;
    	}
    }
    抽象的に上記コードの条件分岐を以下のように分離する.
    public class Rental {
    	private Movie movie;
    	private int daysRented;
    
    	public int getFrequentRenterPoints(){
    		return movie.getFrequentRenterPoints(daysRented);
    	}
    
    }
    
    public abstract class Movie {
    	public abstract int FrequentRenterPoints(int daysRented);
    }
    
    public class NewRelaseMovie extends Movie {
    	public int FrequentRenterPoints(int daysRented){
    		return daysRented > 1 ? 2 : 1;
    	}
    }
    
    public class RegularMovie extends Movie {
    	public int FrequentRenterPoints(int daysRented){
    		return 1;
    	}
    }
    以上のようにMovieをサブクラスとして抽象化して分離する.

    分離練習3

  • 機能:会員加入
    -ユーザーが電子メール、名前、パスワードを入力します.
    -すべて必須
    -パスワードが次のルールを満たしていない場合は、再入力してください.
    -ルール1、ルール2、ルール3...
    -同じEメールで登録されているメンバーがいる場合は、再入力してください.
    -電子メール認証用にメールを送信
    -暗号化タグを使用して検証
    -会員登録済み
  • すべての会員加入機能をWebリクエストと会員加入に分離し,各機能をより小さなサブ機能に分離する.

    実際に、機能を別々に設計すると、以下のようになります.