【C#】簡単なログインシステムを作ってみる

はいどーもこんにちはフリーランスプログラマ(年収10万)のツミオです。
今年は年収100万くらいを目指したいところなんですが、仕事が入ってくる気配がありません。

まあそれはそれとして、年末からの勉強のまとめとして、本日は簡単なログインシステム(IDとPWを入力してホニャララする感じのやつ)を作ってみます。
言語はC#です。
本当はIDisposableインターフェイスの記事を書こうとしていただけなのですが、なぜかこうなっちゃいました。

完成コードが長めなので、Gistsにもあげておきました
完全版のコードを見ながら進めたいという方はどーぞ。

ほないくで。

ログインシステムの仕様

仕様をまとめときます。

・コンソールアプリケーション
・ユーザーIDとパスワードを入力させる
・認証成功や失敗を報告

はい、至って普通のログインシステムですね。
ただセキュリティ的にどうとかいう話は知りません(セキュリティについて何も知らない)。
と言うか、あとでコードを示しますが、IDとPWの一覧はハードコーディングしてます。
暗号化した外部ファイルから読み込むとかやってもいいんですが、ログインシステムそのものとあんまり関係なさそうだったので今回は省略しました。

余談ですが、僕はセキュリティのことがよくわからないのでサーバーサイド技術を未だに触ったことがありません。
仕事ほしいならやった方がいいんですかねえ。

入力されたIDパスワード記録用のクラスの作成

今までのサンプルコードに比べたら少し長めなので、どんな感じで作っていくのかを先に示しておきます。

1.入力されたIDパスワード記録用のクラスを作成
2.パスワードをチェックするためのクラスを作成
3.ユーザーからの入力を受け取るためのクラスを作成

はいこんな感じです。
ではさっそく「入力されたIDパスワード記録用のクラス」を作成してみましょう。

こんな感じですね。
コンストラクタでUserNameとPasswordをセットするだけです。

で、このクラスはIDisposableインターフェイスを実装しているので、IDEの機能を使ってIDisposableインターフェイスを実装しましょう。
Disposeパターンと言うらしいです。

よくわからない日本語が追加されまくっていますが、IDEが自動で生成した文章です。
細かい話は省略しますが、Disposeさせたいものを
// TODO: マネージ状態を破棄します (マネージ オブジェクト)。
と書かれている箇所の下に追加していけばよい
です。
今回だと以下の二行ですね。

確認用のコンソール出力と、パスワードの初期化です。

と言っても実はこれ、単なる僕のIDisposableインターフェイスの実験です。
深い意味はない(またString.Emptyを代入することに意味があるとも思えない)です。

IDisposableインターフェイスを実装したクラスは、using文を使うとスコープを抜けるときに自動でDisposeメソッドが呼ばれます(ファイル読み込みとかのときに使うアレ)。
自分で実装するとどうなるのかなーと思っただけです。

いやほんと深い意味はないです。
UserInfoクラスの作成が終ったので、次行きましょう。

パスワードをチェックするためのクラスの準備

お次はパスワードをチェックするためのクラスを作成します。
ここではインターフェイスを使ってみましょう。
こんな感じです。

見ての通り、イベントが4つとメソッドが一つですね。
それぞれの役割はこんな感じ。

・CertificationSucceeded
 認証が成功したときにイベント発生
・CertificationFailed
 認証が失敗したときにイベント発生
・UndefinedUserEntered
 未定義のユーザー名が入力されたときに発生
・InvalidPasswordEntered
 パスワードが間違っていたときに発生

具象クラスでこれらを実装していきます。

さてインターフェイスにUserInfoEventArgsクラスなるものが利用されています。
これはまだ作っていませんでした。
というわけで作成しましょう。

EventArgsを継承していることからわかる通り、イベントで使用します。
やってることは非常に単純で、先の節で作成したUserInfoクラスをコンストラクタで受け取り、その情報を保持しているだけです。

パスワードをチェックするためのクラスを作成する

まずは外側をちゃちゃっと作っちゃいましょう。
こんな感じ。

IPasswordCheckerインターフェイスが追加されていますね。
先ほど作ったやつです。

ローカルフィールドにDictionary<string, string>型の_userInfoDictが宣言されています。
IDictionary<string, string>型に代入している理由は、『実践で役立つC#プログラミングのイディオム/定石&パターン』に「インターフェイスに対してプログラミングする癖をつけろ!」みたいなことが書いてあったからです。
あとで色々と変えたいと思ったときの汎用性が上がるんだろうなー程度の認識しか今のところはなく、ぶっちゃけこの辺の真髄はきちんと理解できていませんw
そのうちがんばります。

で、このコレクションにKeyとしてIDを、Valueとしてパスワードを用意しています。
プロパティはこのコレクションを返しているものだけですね。

お次はイベント一覧です。
今回は複雑なイベント処理をやっていません。
引数にUserInfoEventArgs型を取っている以外はテンプレ通りですね。
なお?.Invoke」はnull条件演算子と言って、いわゆるnullチェックをしてくれます。
イベントはnullチェックをしないと実行時にエラーが出る可能性があるため、これは必須です(普通にnullチェックをしてもよいですが、null条件演算子の方がスマート)。

さてここから実際にユーザーIDとパスワードをチェックするプログラムです。
と言っても、やはりここでも簡単なことしかやっていません。

1.入力されたIDが存在するかどうかを確認
2.存在していば場合、入力されたパスワードと、正しいパスワードを比較
3.結果を返す

それではコードをどうぞ。

TryToLoginメソッドを見てください。
このメソッドは、ログインに失敗したらfalseを返します。成功したらtrueです。
引数でUserInfo型を受け取ります。
ここにはユーザーが入力したIDとパスワードが入っていることを前提としています。

さて中身を見ていきましょう。
IsUserNameValidメソッドで、入力されたユーザーが存在しているかどうかを確認しています。
存在していなければUndefinedUserEnteredイベントを発生させ、ここでログイン処理を中断させます(ログイン失敗)。

次はLoginSuccessfullyメソッドです。
入力されたユーザーIDとパスワードを比較し、正しければtrueを返してメソッドを抜けます。
正しくなければInvalidPasswordEnteredイベントを実行し、falseを返します(ログイン失敗)。

お次はTryToLoginメソッドを使用するメソッドを作成します。
こんな感じ。

これはIPasswordCheckerインターフェイスに書かれているメソッドです。
つまり、外部に公開します。
内容も簡単に見ていきましょう。

引数でユーザーIDとパスワードを受け取ります。
あとで書きますが、今回はコンソールからの入力ですね。
お次はusing文を使い、スコープを抜けたときにパスワードを破棄するようにしています(ただし最初の方にも書いたように、この動作自体に深い意味はないですw)。

using文の中では先ほど作成したTryToLoginメソッドを実行していますね。
ログインに成功したらCertificationSucceededイベントを発生させ、失敗したらOnCertificationFailedメソッドを実行させています。

全体的にもう少しスマートに書けないかなあと思ったのですが、とりあえずこんな感じです。
残りはこのPasswordCheckerクラスを使って、実際のログイン処理を書くだけですね。

なお、PasswordCheckerクラスはコンソールクラスに依存していないので、全く別のプログラムに流用することも容易にできます。
ログイン成功時の処理を委譲させているのも、他のクラスへの依存を避けるためです。
回りくどい方法を取るのには理由があるんですねえ(でもこれが正しい設計なのかどうかは自信なし)。

ユーザー入力を受けるクラスを作成する

ユーザーからの入力を受け取るクラスを作成しましょう。
今回はコンソールから受け取ります。

まずはインターフェイス。

はいこれだけです。
次は具象クラス。

特に説明は不要かなと思います。
やっていることは以下の通り。

・PasswordChecker型のインスタンス作成
・イベントの初期化
・whileループで認証処理を延々と繰り返す

初期化の処理と認証処理はわけたほうがいいのかなーとも思いましたが、今回はこんな感じにしました。

最後にMainメソッドをこんな感じにしてください。

完成です。
実行結果の画像もどーぞ。

using文を抜けるとDisposeメソッドが実行されているのがわかりますね。
また、入力結果に対して適切なイベントが実行されていることもわかります。

今回作成したプログラムの特徴と課題

このプログラムで工夫した点(というか意図的に実装させたもの?)は以下の通りです。

・PasswordCheckerクラスをコンソールクラスに依存させないようにした
・IDisposableインターフェイスの実験
・イベントのおさらい

特にPasswordCheckerクラスをコンソールクラスに依存させないようにしたことにより、他のプログラムでも容易にこのクラスを使用することができます。
具体的には、UserInputtingReceiverクラスを参考にして、他のクラスに依存させるようにすればよいのですね。
例えばUnityのuGUIでの認証システムにも使えます。

課題は以下の通り

・ファクトリクラスを使っていない
・依存関係逆転の原則がよくわからない
・ちゅーか設計のことを何も学んでいない

UserInputtingReceiverクラスのインスタンスを作成する際、ファクトリクラスを介せば「コンソール」「uGUI」はたまた「WPF」などを容易に変更できるようになるでしょう。
Mainメソッド内でIUserInputtingReceiver main = new UserInputtingReceiver();とするのもあんまり綺麗じゃない気がします。
「依存関係逆転の原則」ってやつでしょうかね。
詳細は出井秀行氏の「具象クラスに依存しないプログラミング」を参照されよ!

今のところ設計のことを何も学んでいない(プログラミングの文法ばっかりなんですよ)ので、そろそろ設計についても勉強しないとなーと思っています。
『C#実践開発手法』に設計のこと書いてるんでしょうかねえ。
読んでみたいですね。

おわりに

いかがでしたでしょーか。
今回は少し長めでしたので、Gistsにもコードをまとめてみました
名前空間にはお目々つぶってください。

ほなそんな感じで。

フォローする