コマンドオブジェクト



多くのアプリケーションでは、長い機能は動作するのが非常に難しいです.レガシーコードベースでは、多くの依存関係を持つ大きなクラスにしばしば含まれます.テストハーネスにそのような機能とクラスをもたらすのにかかる仕事はしばしば重要です、そして、あなたが作る必要がある変更のためにoverkillさえするかもしれません.
あなたが働く必要がある機能が大きいか、インスタンス変数と機能を使用するならば、ブレークアウトコマンドオブジェクトを使ってください.長い話を短くするために、このリファクタリング技術を使用すると、新しいクラスに長い機能を移動することができます.その新しいクラスを使用して作成されたオブジェクトは、コマンドオブジェクトと呼ばれます.なぜなら、それらはほとんどの場合、単一の関数の周りに構築されています.リクエストをカプセル化するオブジェクトですDesign Patterns .
ブレイクアウトコマンドオブジェクトを使用した後、古い関数の場合は、新しいクラスのテストを簡単に書くことができます.古い関数のローカル変数は、新しいクラスのインスタンス変数になります.多くの場合、依存関係を壊し、コードを改善することが容易になります.
以下に例を示します.
class ApplicantInfoComponent {
  applicantCommonModel = new ApplicantCommonEditModel();
  isSmartMoveActive = false;
  datePickerComponents: QueryList<CustomDatePicker>;
  ...

  constructor(private flashMessageService: NotifyFlashMessageService, ...) {
    ...
  }

  validateDates() { ... }
  ...

  validateBirthDate(finalValidation) {
    let isValid = true;
    let tempDate = new Date(this.applicantCommonModel.DateOfBirth);

    if (
      this.isSmartMoveActive && new Date(
        tempDate.getFullYear() + 18, tempDate.getMonth() - 1, 
        tempDate.getDay()
      ) >= new Date()
    ) {
      isValid = false;
      let birthDate = this.datePickerComponents['_results']
        .find(item => item.Name == 'birthDate');
      birthDate.invalidDate = true;
      this.flashMessageService.warning('Age must not be less than 18 years.');
    }
    else if (
      this.isSmartMoveActive && new Date(
        tempDate.getFullYear() - 125, tempDate.getMonth() - 1,
        tempDate.getDay()
      ) >= new Date()) {
      isValid = false;
      let birthDate = this.datePickerComponents['_results']
        .find(item => item.Name == 'birthDate');
      birthDate.invalidDate = true;
      this.flashMessageService.warning('Age must not be more than 125 years.');
    }
    if (finalValidation)
      return isValid && this.validateDates();
    return isValid;
  }
  ...
}
The ApplicantInfoComponent クラスには長い名前の関数がありますvalidateBirthDate . 私たちは簡単にテストを書くことができません、そして、それは非常に難しいですApplicantInfoComponent テストハーネスで.ブレークアウトコマンドオブジェクトを使いましょうvalidateBirthDate 新しいクラスへ.
最初のステップは、誕生日の検証作業を行う新しいクラスを作成することです.私たちはそれを呼び出すことができますBirthDateValidator . 我々はそれを作成した後、我々はそれをコンストラクタを与える.コンストラクターの引数は、元のクラスへの参照、および元の関数への引数である必要があります.
class BirthDateValidator {
  constructor(component: ApplicantInfoComponent, finalValidation: boolean) {}
}
あなたは、これを見ているかもしれません、そして、言っているかもしれませんApplicantInfoComponent , そして、我々はすでに我々がテストハーネスのそれらの1つを例示したくないと決めました.いったいどうしたんだ?」待って、我々は物事をより良くするつもりです.
コンストラクタを作った後で、クラスにもう一つの機能を加えることができますvalidateBirthDate() 関数.私たちはそれを呼び出すことができますvalidate() .
class BirthDateValidator {
  constructor(component: ApplicantInfoComponent, finalValidation: boolean) {}

  validate() {}
}
今、我々はvalidateBirthDate() 関数へBirthDateValidator . 我々は、古いものの体をコピーしますvalidateBirthDate() 新機能validate() 関数.
class BirthDateValidator {
  constructor(component: ApplicantInfoComponent, finalValidation: boolean) {}

  validate() {
    let isValid = true;
    let tempDate = new Date(this.applicantCommonModel.DateOfBirth);

    if (
      this.isSmartMoveActive && new Date(
        tempDate.getFullYear() + 18, tempDate.getMonth() - 1, 
        tempDate.getDay()
      ) >= new Date()
    ) {
      isValid = false;
      let birthDate = this.datePickerComponents['_results']
        .find(item => item.Name == 'birthDate');
      birthDate.invalidDate = true;
      this.flashMessageService.warning('Age must not be less than 18 years.');
    }
    else if (
      this.isSmartMoveActive && new Date(
        tempDate.getFullYear() - 125, tempDate.getMonth() - 1,
        tempDate.getDay()
      ) >= new Date()) {
      isValid = false;
      let birthDate = this.datePickerComponents['_results']
        .find(item => item.Name == 'birthDate');
      birthDate.invalidDate = true;
      this.flashMessageService.warning('Age must not be more than 125 years.');
    }
    if (finalValidation)
      return isValid && this.validateDates();
    return isValid;
  }
}
If the validate() on BirthDateValidator からのインスタンス変数または関数への参照をApplicantInfoComponent , コンパイルが失敗します.それを成功させるために、変数のゲッターを作成し、それがパブリックに依存する関数を作ることができます.この場合、我々はいくつかの変数とvalidateDates() 機能は、すべてのflashMessageService 変数.我々はそれを公開後ApplicantInfoComponent , 我々は、リファレンスからアクセスすることができますBirthDateValidator クラスとコードがコンパイルされます.
今、我々はApplicantInfoComponent ’s validateBirthDate 関数を新しいBirthDateValidator .
class ApplicantInfoComponent {
  ...

  validateBirthDate(finalValidation) {
    birthDateValidator = new BirthDateValidator(this, finalValidation);
    birthDateValidator.validate();
  }
  ...
}
今すぐ戻ってApplicantInfoComponent 依存.私たちが新しいクラスを作ることに決めた理由はApplicantInfoComponent テストハーネスでインスタンス化するのは難しすぎます.今の時点で、我々はまだその問題を抱えているBirthDateValidator クラスはまだApplicantInfoComponent . 私たちができることは依存性を壊すための新しいインターフェースを抽出することですApplicantInfoComponent 完全に.新しいインターフェイスを作成するにはDateValidator . それから、私たちはBirthDateValidator からのApplicantInfoComponent to DateValidator , コンパイルし、コンパイラにどのような機能とプロパティをインターフェイスに持たせるかを教えてください.最後にコードがどのように見えますか
interface DateValidator {
  applicantCommonModel: ApplicantCommonEditModel;
  isSmartMoveActive: boolean;
  datePickerComponents: QueryList<CustomDatePicker>;
  flashMessageService: NotifyFlashMessageService;
  validateDates(): boolean;
}

class ApplicantInfoComponent {
  ...

  validateBirthDate(finalValidation) {
    birthDateValidator = new BirthDateValidator(this, finalValidation);
    birthDateValidator.validate();
  }
  ...
}

class BirthDateValidator {
  constructor(component: DateValidator, finalValidation: boolean) {}

  validate() {
    let isValid = true;
    let tempDate = new Date(component.applicantCommonModel.DateOfBirth);

    if (
      component.isSmartMoveActive && new Date(
        tempDate.getFullYear() + 18, tempDate.getMonth() - 1, 
        tempDate.getDay()
      ) >= new Date()
    ) {
      isValid = false;
      let birthDate = component.datePickerComponents['_results']
        .find(item => item.Name == 'birthDate');
      birthDate.invalidDate = true;
      component.flashMessageService.warning('Age must not be less than 18 years.');
    }
    else if (
      component.isSmartMoveActive && new Date(
        tempDate.getFullYear() - 125, tempDate.getMonth() - 1,
        tempDate.getDay()
      ) >= new Date()) {
      isValid = false;
      let birthDate = component.datePickerComponents['_results']
        .find(item => item.Name == 'birthDate');
      birthDate.invalidDate = true;
      component.flashMessageService.warning('Age must not be more than 125 years.');
    }
    if (finalValidation)
      return isValid && component.validateDates();
    return isValid;
  }
}
我々は新しいクラスに変更する必要があるコードを抽出したので、我々はいくつかのテスト、リファクタリングを作成する準備が整いましたし、我々がここに来たどんな変更を作るために.ブレークアウトコマンドオブジェクト、およびテストとリファクタリングコードの書き込みの次の手順を参照することができます.
推奨事項:
  • Refactoring: Improving the Design of Existing Code, by Martin Fowler
  • Working Effectively with Legacy Code, by Michael Feathers