私は、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を()クラビングされていませんか?
あなたの気持ちは絶対的に正しいです。仰るとおりです。
私はあなたが何をかなり意識するように見えるので、あなたの問題は、あなたの命名スタイルから始まると思いますSRP手段。「...サービス」や「...マネージャー」のようなクラス名は、非常にあいまいな意味やセマンティクスを運びます。彼らはより多くの記述一般コンテキストやコンセプトを。言い換えれば「...マネージャー」クラスの招待は、あなたはすべての内部を置くために、それはだから、それはまだ、右に感じるマネージャー。
あなたはクラスや責任の真の概念に焦点を当てしようとすることで、より具体的なを取得すると、あなたは自動的に強い意味やセマンティクスを持つ大きな名前を検索します。これは本当にあなたがクラスを分割すると責任を識別するのに役立ちます。
SRP:
特定のモジュールを変更するには、複数の理由があってはなりません。
あなたは、名前の変更を開始することができますUserService
にをUserDatabaseContext
。さて、これは自動的にこのクラス(例えばCRUD操作)にのみ置くのデータベース関連の操作にあなたを強制します。
あなたも、ここで、より具体的に得ることができます。あなたは、データベースと何をしているのか?あなたはから読み取ると、それに書き込みます。読み取り操作用と書き込み操作のための別の責任:2つのクラスを意味明らかに2人の責任。これは非常に一般的なだけで読むことができますクラスまたは書き込み可能性があり何でも。レッツ・コールそれらDatabaseReader
とDatabaseWriter
、我々は我々がどこにでもインタフェースを使用しようとしているデカップルすべてにしようとしているからです。この方法では、我々は2つの取得IDatabaseReader
とIDatabaseWriter
のインターフェイスを。彼らはデータベース(マイクロソフト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);
...
}
トップ上では、読み取りおよび書き込み操作のより専門層、例えば、ユーザに関連するデータを実装することができます。私たちは、導入するIUserDatabaseReader
とIUserDatabaseWriter
のインターフェイスを。このインタフェースは、データベースまたはどのようなデータベースの種類が使用されているに接続する方法がわかりません。このインタフェースは(使用例:ユーザの詳細読み取りまたは書き込みに必要なものの情報を知っている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
追加のユーザー関連の読み取りまたは書き込み操作を追加したい場合(例えば、ユーザーの役割を処理します)- 新しい実装を提供する
IDatabaseReader
かIDatabaseWriter
、データベースの基礎となる切り替えたいときや、データベースへのアクセスを変更する必要があります - 実装
IAuthentication
手順の変更(例えばOS認証でビルドを使用)する場合
今、すべてがきれいに分離されています。認証は、CRUD操作を混在させることはできません。我々は、基礎となる永続化システムに関する柔軟性を追加するために、アプリケーションと永続化層との間に追加の層を持っています。だから、CRUD操作は、実際の永続操作を混在させないでください。
ヒントとして:将来的にはあなた方の思考(デザイン)最初の部分で始まる:私のアプリケーションは、何をしなければなりませんか?
- ハンドル認証
- ハンドルユーザー
- データベースを扱います
- ハンドルのメール
- ユーザーのレスポンスを作成します
- ユーザーに表示するページを表示
- 等
あなたが見ることができるように、あなたは別に、各ステップや要件を実装するために開始することができます。しかし、これは各要件を正確に一つのクラスによって実現されているわけではありません。あなたは覚えているように、我々は、4人の責任かのクラスにデータベースへのアクセスを分割:実際のデータベース(低レベル)に、読み取りと書き込み、具体的なユースケース(ハイレベル)を反映するために、データベース抽象化レイヤに読み取りおよび書き込み。インターフェースを使用すると、アプリケーションに柔軟性とテスト容易性を追加します。