我々は異なるクラスに責任をseggregateときSRPを理解しようとしています

Manikandan KBK DIP:

私は、SRPの原理を理解しようとしているとSOFのスレッドのほとんどは私がいるこの特定のクエリに応答しませんでした

使用事例

私は彼がウェブサイトにユーザーアカウントを作成/登録しようとするたびに自分自身を確認するために、ユーザーの電子メールアドレスに電子メールを送信しようとしています。

SRPなし

class UserRegistrationRequest {
    String name;
    String emailId;
}
class UserService {
    Email email;

    boolean registerUser(UserRegistrationRequest req) {
        //store req data in database
        sendVerificationEmail(req);
        return true;
    }

    //Assume UserService class also has other CRUD operation methods()    

    void sendVerificationEmail(UserRegistrationRequest req) {
        email.setToAddress(req.getEmailId());
        email.setContent("Hey User, this is your OTP + Random.newRandom(100000));
        email.send();
    }
}

上記のクラスのUserServiceの私たちは「UserServiceの」CRUD操作をクラブにし、1つのクラスに確認メールコードをトリガしているとして、SRPルールに違反します。

そこで私は、

SRPと

class UserService {
    EmailService emailService;

    boolean registerUser(UserRegistrationRequest req) {
        //store req data in database
        sendVerificationEmail(req);
        return true;
    }

    //Assume UserService class also has other CRUD operation methods()    

    void sendVerificationEmail(UserRegistrationRequest req) {
        emailService.sendVerificationEmail(req);
    }
}

class EmailService {
    void sendVerificationEmail(UserRegistrationRequest req) {
        email.setToAddress(req.getEmailId());
        email.setContent("Hey User, this is your OTP + Random.newRandom(100000));
        email.send();
    }

この時間は、それが電子メールを送信するの全体の論理を保持していないにもかかわらずしかし、たとえ「SRPと」、クラスとしてUserServiceのは再び、)(sendVerificationEmailの振る舞いを保持しています。

それは再び私たちも、SRPを適用した後に1つのクラスにCRUD操作のとsendVerificationEmailを()クラビングされていませんか?

BionicCode:

あなたの気持ちは絶対的に正しいです。仰るとおりです。

私はあなたが何をかなり意識するように見えるので、あなたの問題は、あなたの命名スタイルから始まると思いますSRP手段。「...サービス」や「...マネージャー」のようなクラス名は、非常にあいまいな意味やセマンティクスを運びます。彼らはより多くの記述一般コンテキストやコンセプトを。言い換えれば「...マネージャー」クラスの招待は、あなたはすべての内部を置くために、それはだから、それはまだ、右に感じるマネージャー

あなたはクラスや責任の真の概念に焦点を当てしようとすることで、より具体的なを取得すると、あなたは自動的に強い意味やセマンティクスを持つ大きな名前を検索します。これは本当にあなたがクラスを分割すると責任を識別するのに役立ちます。

SRP:

特定のモジュールを変更するには、複数の理由があってはなりません。

あなたは、名前の変更を開始することができますUserServiceにをUserDatabaseContextさて、これは自動的にこのクラス(例えばCRUD操作)にのみ置くのデータベース関連の操作にあなたを強制します。

あなたも、ここで、より具体的に得ることができます。あなたは、データベースと何をしているのか?あなたはから読み取る、それに書き込みます。読み取り操作用と書き込み操作のための別の責任:2つのクラスを意味明らかに2人の責任。これは非常に一般的なだけで読むことができますクラスまたは書き込み可能性があり何でもレッツ・コールそれらDatabaseReaderDatabaseWriter、我々は我々がどこにでもインタフェースを使用しようとしているデカップルすべてにしようとしているからです。この方法では、我々は2つの取得IDatabaseReaderIDatabaseWriterのインターフェイスを。彼らはデータベース(マイクロソフトSQLやMySQLを)知っているので、このタイプは、(例えばSQLまたはMySQLを使用して)それを照会することと正確な言語への接続方法を、非常に低いレベルです。

// Knows how to connect to the database
interface IDatabaseWriter {
  void create(Query query);
  void insert(Query query);
  ...
}

// Knows how to connect to the database
interface IDatabaseReader {
  QueryResult readTable(string tableName);
  QueryResult read(Query query);
  ...
}

トップ上では、読み取りおよび書き込み操作のより専門層、例えば、ユーザに関連するデータを実装することができます。私たちは、導入するIUserDatabaseReaderIUserDatabaseWriterのインターフェイスを。このインタフェースは、データベースまたはどのようなデータベースの種類が使用されているに接続する方法がわかりません。このインタフェースは(使用例:ユーザの詳細読み取りまたは書き込みに必要なものの情報を知っているQuery低レベルによって実際のクエリに変換されたオブジェクトIDatabaseReaderまたはIDatabaseWriter)を:

// Knows only about structure of the database (e.g. there is a table called 'user') 
// Implementation will use IDatabaseWriter to access the database
interface IDatabaseWriter {
  void createUser(User newUser);
  void updateUser(User user);
  void updateUserEmail(long userKey, Email emailInfo); 
  void updateUserCredentials(long userKey, Credential userCredentials); 
  ...
}

// Knows only about structure of the database (e.g. there is a table called 'user') 
// Implementation will use IDatabaseReader to access the database
interface IUserDatabaseReader {
  User readUser(long userKey);
  User readUser(string userName);
  Email readUserEmail(string userName);
  Credential readUserCredentials(long userKey);
  ...
}

私たちは、まだ永続化層で行われていません。私たちは、別のインターフェイスを導入することができますIUserProviderアイデアは、我々のアプリケーションの残りの部分からデータベースへのアクセスを分離することです。言い換えれば、私たちは、このクラスに、ユーザに関連するデータのクエリ操作を統合します。そのため、IUserProviderデータ層への直接アクセスを持っているタイプのみとなります。これは、アプリケーションの永続化層との界面を形成し:

interface IUserProvider {
  User getUser(string userName);
  void saveUser(User user);
  User createUser(string userName, Email email);
  Email getUserEmail(string userName);
}

の実装IUserProvider参照することにより、データ層への直接アクセスがあるアプリケーション全体で唯一のクラスIUserDatabaseReaderとはIUserDatabaseWriterこれは、データ処理をより便利にするためにデータの読み書きラップします。このタイプの責任は、アプリケーションへのユーザー・データを提供することです:

class UserProvider {
  IUserDatabaseReader userReader;
  IUserDatabaseWriter userWriter;

    // Constructor
    public UserProvider (IUserDatabaseReader userReader, 
          IUserDatabaseWriter userWriter) {
      this.userReader = userReader;
      this.userWriter = userWriter;
    }

  public User getUser(string userName) {
    return this.userReader.readUser(username);
  }

  public void saveUser(User user) {
    return this.userWriter.updateUser(user);
  }

  public User createUser(string userName, Email email) {
    User newUser = new User(userName, email);
    this.userWriter.createUser(newUser);
    return newUser;
  }

  public Email getUserEmail(string userName) {
    return this.userReader.readUserEmail(userName);
  }
}

今、私たちは、データベース操作に取り組んでいることを、私たちは、認証プロセスに焦点を当ててからの認証ロジックを抽出するために続けることができるUserService新しいインターフェースを追加することによってIAuthentication

interface IAuthentication {
  void logIn(User user)
  void logOut(User);
  void registerUser(UserRegistrationRequest registrationData);
} 

実装IAuthenticationを実装特別な認証手続き:

class EmailAuthentication implements IAuthentication {
  EmailService emailService;
  IUserProvider userProvider;

// Constructor
  public EmailAuthentication (IUserProvider userProvider, 
      EmailService emailService) {
    this.userProvider = userProvider;
    this.emailService = emailService;
  }

  public void logIn(string userName) {
    Email userEmail = this.userProvider.getUserEmail(userName);
    this.emailService.sendVerificationEmail(userEmail);
  }

  public void logOut(User user) {
    // logout
  }

  public void registerUser(UserRegistrationRequest registrationData) {
    this.userProvider.createNewUser(registrationData.getUserName, registrationData.getEmail());

    this.emailService.sendVerificationEmail(registrationData.getEmail());    
  }
}

デカップリングするEmailServiceからEmailAuthenticationクラスを、我々は上の依存関係を削除することができますUserRegistrationRequestせることでsendVerificationEmail()代わりにEmail`パラメータオブジェクトを取ります:

class EmailService {
  void sendVerificationEmail(Email userEmail) {
    email.setToAddress(userEmail.getEmailId());
    email.setContent("Hey User, this is your OTP + Random.newRandom(100000));
    email.send();
}

認証がインターフェイスで定義されているのでIAuthentication、あなたが別の手順(例えばを使用することを決定したときは、いつでも新しい実装を作成することができWindowsAuthenticationますが、既存のコードを変更せずに)。また、これは動作しますIDatabaseReaderし、IDatabaseWriterあなたが(例えばSqliteを)別のデータベースにスイッチすることを決定たら。IUserDatabaseReaderおよびIUserDatabaseWriter実装はまだそのまま動作します。

このクラスのデザインで、あなたは今、既存の各タイプを変更するには、正確に一つの理由があります。

  • EmailService あなたが実装を変更する必要がある場合(例えば、異なる電子メールのAPIを使用します)
  • IUserDatabaseReaderまたはIUserDatabaseWriter追加のユーザー関連の読み取りまたは書き込み操作を追加したい場合(例えば、ユーザーの役割を処理します)
  • 新しい実装を提供するIDatabaseReaderIDatabaseWriter、データベースの基礎となる切り替えたいときや、データベースへのアクセスを変更する必要があります
  • 実装IAuthentication手順の変更(例えばOS認証でビルドを使用)する場合

今、すべてがきれいに分離されています。認証は、CRUD操作を混在させることはできません。我々は、基礎となる永続化システムに関する柔軟性を追加するために、アプリケーションと永続化層との間に追加の層を持っています。だから、CRUD操作は、実際の永続操作を混在させないでください。

ヒントとして:将来的にはあなた方の思考(デザイン)最初の部分で始まる:私のアプリケーションは、何をしなければなりませんか?

  • ハンドル認証
  • ハンドルユーザー
  • データベースを扱います
  • ハンドルのメール
  • ユーザーのレスポンスを作成します
  • ユーザーに表示するページを表示

あなたが見ることができるように、あなたは別に、各ステップや要件を実装するために開始することができます。しかし、これは各要件を正確に一つのクラスによって実現されているわけではありません。あなたは覚えているように、我々は、4人の責任かのクラスにデータベースへのアクセスを分割:実際のデータベース(低レベル)に、読み取りと書き込み、具体的なユースケース(ハイレベル)を反映するために、データベース抽象化レイヤに読み取りおよび書き込み。インターフェースを使用すると、アプリケーションに柔軟性とテスト容易性を追加します。

おすすめ

転載: http://43.154.161.224:23101/article/api/json?id=227897&siteId=1