設計パターンの導入に関する講義経験

簡単な紹介をしましょう-記事の目的の概要を説明します。

土曜日に、学部生にデザインパターンの紹介をします。 それで、私の経験を共有し、最初のいくつかの講義の計画を説明したいと思います。 ほとんどの読者は、私が自分で提示した資料は長い間親しまれていたと思いますが、おそらく提示の順序と方法は奇妙に思われるでしょう。

悲しいかな、彼らは私たちに何かを話しますが、なぜそれが必要なのか、あるいはそれさえ言うのではなく、通りかかっているかのように。 C#について話すと、基本クラスとインターフェイスとは何か、それらを記述するために使用する必要がある構文、基本クラスが「鳥」、相続人が「ダック」と「イーグル」である例を示します。それは必要であり、クラス全体の-潜在的に複雑な-階層のどれが達成されるか、彼らは言うわけではありません。 そして、それで、まだコーンを埋める時間がなかった多くの学生が頭の中に世界の逆像を持っているのです-彼らはどのツールが彼らに与えられたのかをよく知っていますが、彼らが発明された理由と彼らが適用できるものを漠然と理解しています。

そのため、いくつかのアプローチが必要な理由を示すいくつかのケーススタディを作成しました。 確かに、私たちは慣習を受け入れなければなりません-大砲から、あるいは架空の標的にさえスズメを打ちます。 しかし、それが起こった場合、私たちは間違いなく敵の欄干に入ります。

非常に単純な質問からすぐにかなり複雑な質問(リンカなど)に移るので、最初からではなく最後まで読んでください。





最も簡単な方法で問題を解決することから始めましょう。 さて、最初のn個の素数を表示するとしましょう。 私たちは完全に簡単な方法で行動します-最適なアルゴリズムを気にしません。

static void Main() { Console.WriteLine("Input n"); var n = int.Parse(Console.ReadLine()); var primeNumbers = new List<int>(); int current = 2; while (primeNumbers.Count != n) { if (primeNumbers.All(x => current % x != 0)) { primeNumbers.Add(current); Console.WriteLine(current); } current++; } }
      
      







まあ、素晴らしい。 プログラムは動作します。 次は? さて、それを公開するとしましょう-人々が私たちのモジュールを使って素数を計算できるようにします。 私たちはゆっくりと発展し、改善し、磨きます-そして私たちは喜んで、人々は利益を得ます。

確かに、質問が発生します-キーボード入力を必要とするexe-shnikを人々に提供しますか? 通常、モジュールはライブラリで実行され、不要なものを隠し、ユーザーに必要なメソッドのみを公開します。 彼の署名を推定しましょう:



  public int GetPrimeNumber(int n) { ... }
      
      







そう言いましょう。 確かに、これは以前にやったこととはまったく異なります。 n個の数字を印刷しましたが、最後の数字のみです。 OK、別のメソッドを追加します。2つのメソッドがあります。 すぐにそれらを新しいクラスに入れてください-ただ横になってそれらにアクセスするのが便利でした。



  public class PrimeNumberCalculator { public int GetPrimeNumber(int n) { ... } public int[] GetPrimeNumbers(int n) { ... } }
      
      







ただし、モジュールモジュール、プログラムについて忘れてはなりません。 以前よりも悪くなることはありません。 作り直しましょう:



 public class PrimeNumberCalculator { public int GetPrimeNumber(int n) { return GetPrimeNumbers(n).Last(); } public int[] GetPrimeNumbers(int n) { var primeNumbers = new List<int>(); int current = 2; while (primeNumbers.Count != n) { if (primeNumbers.All(x => current % x != 0)) { primeNumbers.Add(current); Console.WriteLine(current); } current++; } return primeNumbers.ToArray(); } } class Program { static void Main() { Console.WriteLine("Input n"); var n = int.Parse(Console.ReadLine()); new PrimeNumberCalculator().GetPrimeNumbers(n); } }
      
      







すべてが良いようです。 これは、 PrimeNumberCalculatorクラスがConsoleに依存しているだけです。 一般的に言って、何に依存していますか? それはそれが使用することを意味します。 私たちのクラスは、コンソールが突然動作しなくなると突然仮定した場合、動作しません。 彼はそれを前提条件として要求しています。 Consoleは静的クラスであるため、Consoleクラスへの依存は現在のところあまり目立ちません。 newを使用して明示的にインスタンス化するのではなく、ほとんど名前空間のように使用します。 しかし、まだ依存関係がありますので、覚えておく必要があります。

強調します。 コンソール機能を呼び出すだけの新しいクラスを作成しましょうが、同時に静的ではありません。 これにはまだほとんど意味がありません-Calculatorクラスの依存関係を強調したいだけです

 public class MyConsole { // ,        ,       public void WriteLine(string value) { Console.WriteLine(value); } } public class PrimeNumberCalculator { .... public int[] GetPrimeNumbers(int n) { .... new MyConsole().WriteLine(current.ToString()); .... } }
      
      







新しいMyConsoleを記述するとき、依存関係を解決します-必要なオブジェクトを自由に使用できます。 一般的に言って、このような依存関係を解決し、目的のオブジェクトを直接作成することは悪い形と考えられています。 その理由についてすぐに話しましょう。 しかし、最初に、別の質問。



パブリッシュされたモジュール(これまで1つのクラスのみで構成されていた)を提供したユーザーが、私たちのプログラムと非常によく似たプログラムを作成したいと考えているとします。 1つの問題-彼はウェブ上で働いています。 彼にはコンソールがまったくありません。

それでは、なぜGetPrimeNumbersメソッドを使用して組み立てられた既成の配列を取り、必要な場所に印刷しないのですか? 少なくともファイル内、htmlマークアップ内でも。 できますか? 可能ですが、その後動作が変更されます-カウントされるとすぐに画面に数字が表示され、ユーザーは最初にすべての数字の計算に時間を費やしてから印刷するだけです。 同じことではありません-特に、プログラムに100千の素数を返すように要求する場合。

「どこかで、コンソールではなく」出力の具体的な実装で読者をそらすことはありません。 最も簡単なのは、ファイルに出力することです。 したがって、モジュールのユーザーがファイルに印刷したいとします。

どうする?



若干の余談があります:読者の多くはおそらくPLOに夢中になっていて、以前考えたことをほとんど覚えていないでしょう。 そして、学生が私に提供する最も頻繁で最も簡単な解決策は、「最初の素数を取り、ファイルに印刷する」などの新しいメソッドを作成することです。 さらに、ファイルへの出力のロジックとカウントのロジックが1つの方法で混在しています。



新しいステップは、「コンソールからライター」に非常によく似た「ファイルからライター」を作成することであり、彼の人生の目的は、出力ロジックを素数の計算アルゴリズムから切り離すことです。 だから、私たちが持っていると仮定します:



 public class FileWriter { public void WriteLine(string value) { //      } }
      
      







次に、正面のソリューションは次のようになります。

 public class PrimeNumberCalculator { public int GetPrimeNumber(int n) { return GetPrimeNumbers(n).Last(); } public int[] GetPrimeNumbers(int n) { var primeNumbers = new List<int>(); int current = 2; while (primeNumbers.Count != n) { if (primeNumbers.All(x => current % x != 0)) { primeNumbers.Add(current); new MyConsole().WriteLine(current.ToString()); } current++; } return primeNumbers.ToArray(); } public int[] GetPrimeNumbersToFile(int n) { var primeNumbers = new List<int>(); int current = 2; while (primeNumbers.Count != n) { if (primeNumbers.All(x => current % x != 0)) { primeNumbers.Add(current); new FileWriter().WriteLine(current.ToString()); } current++; } return primeNumbers.ToArray(); } }
      
      







このように生きることができないことは明らかです。少なくとも2つの理由があります。 まず、コードを恥知らずに複製します。 より良いアルゴリズムを考え出す場合、2つの場所で編集する必要があります。 エラーを見つけて修正した場合、おそらく1箇所で修正し、2箇所目では忘れます。 つまり、コードを複製することは職業です。 2番目の理由は、ユーザーがコンソールとファイルにのみ出力できるようにすることです。 そして、彼が他のどこかに行きたいなら? コードを変更し、新しいバージョンをコンパイルしてアップロードする必要があります。 すべてのユーザーの気まぐれをすべて満たし、どこでも印刷できる200のメソッドを持つクラスを作成しますか? ナンセンス、ナンセンス。 これは不可能です。

問題が発生します-何をすべきか。 しかし、一般的に-私たちは何が欲しいですか? ライターを適切なタイミングで引き出す計算アルゴリズムを作成します。 そして。 何でも。 少なくともファイルへのライター、少なくともコンソールへのライター、少なくともKGBのインフォーマー

さて-乾杯! -解決策があります。 少数(基本クラス、抽象クラス、インターフェース)でも。 そのような場合、私はインターフェースを好み、それを契約として理解します。 インターフェースを実装するクラスは、契約(契約)の条件を満たしていることに同意します。作業が行われた場合に限り、契約を完全に履行するユーザーであるユーザーにとっては重要ではありません。

それでは、行きましょう:



 public interface IWriter { void WriteLine(string value); } public class ConsoleWriter : IWriter { public void WriteLine(string value) { Console.WriteLine(value); } } public class FileWriter : IWriter { public void WriteLine(string value) { //      } } public class PrimeNumberCalculator { public int GetPrimeNumber(int n, IWriter writer) { return GetPrimeNumbers(n, writer).Last(); } public int[] GetPrimeNumbers(int n, IWriter writer) { var primeNumbers = new List<int>(); int current = 2; while (primeNumbers.Count != n) { if (primeNumbers.All(x => current % x != 0)) { primeNumbers.Add(current); writer.WriteLine(current.ToString()); } current++; } return primeNumbers.ToArray(); } } class Program { static void Main() { Console.WriteLine("Input n"); var n = int.Parse(Console.ReadLine()); var writer = new ConsoleWriter(); new PrimeNumberCalculator().GetPrimeNumbers(n, writer); } }
      
      







まあ何? ほぼ目標を達成しました。 ユーザーは、提供された一連のライターから選択するか、「ライター」インターフェイスを実行するように指定して新しいライターを作成できます。 ところで、ライターへの依存関係を解決する方法に注目してください。オブジェクトをメソッドに渡します。 たとえば、クラスコンストラクターに渡すことができますが、これは好みと便利さの問題です。



ここに別の質問があります。 複数のライターの機能を組み合わせたいとします。 「コンソールとファイル内のライター」が必要だとしましょう。 または「ファイルとWebの両方のコンソール上のライター(それが何を意味するにせよ)」 どうする

最初の衝動は明らかです。この新しいライターを作成し、3つのソースすべてに書き込みを許可します。 1つの問題-コードを再度複製します。 つまり、すでに3つすべてを個別に記述しました。次に、ロジックを混在させます。 さて、これは次のように解決されます。



 public class WriterToConsoleAndFile : IWriter { public void WriteLine(string value) { new ConsoleWriter(value).WriteLine(value); new FileWriter(value).WriteLine(value); } }
      
      







すべてが機能しますが、欠点があります。 オブジェクトを作成するだけで依存関係を解決するのは悪い形式だと既に述べました。 それに何が問題なのですか?

はい、見て。 「コンソールへの新しいライター」を書いたとき、どこかではなく、正確にコンソールにあるものを指定しました。 コードを変更することなく、他の人には気付かなかったため、コンソールのライターに正確に依存していました。 それだけでなく、ライターからこの方法で作成されたコンソールまで。 同じクラスを作成したいが、設定を変更したい場合があります-デフォルトの青い背景と黄色の文字を作成します。 つまり、ポリモーフィズムも使用しません(つまり、インターフェイス/基本クラスを介してライターを渡しません)が、それでも選択の自由度が増し、ユーザーに選択肢が増えます。 彼にオブジェクトを作成させ、彼の裁量でそれを構成させます。 私の仕事は、受け取ったツールを使用することです。 だから、言葉から行動まで。



 public class WriterToConsoleAndFile : IWriter { private ConsoleWriter _consoleWriter; private FileWriter _fileWriter; public WriterToConsoleAndFile( ConsoleWriter consoleWriter, FileWriter fileWriter) { _consoleWriter = consoleWriter; _fileWriter = fileWriter; } public void WriteLine(string value) { _consoleWriter.WriteLine(value); _fileWriter.WriteLine(value); } }
      
      







「コンソールからライターへ」または「ファイルからライターへ」という特別なプロパティをまったく使用していないことに注意してください。 ここでファイルへのパスを変更する必要はなく、一般的にその存在を知っています。 コンソールのフォントの色を変更したくない-これは私たちがしたことではなく、 ConsoleWriterインスタンスを作成したものです 。 それは、 IWriterで十分だということです。 必要以上のものは求めません。 ここに:

 public class WriterToConsoleAndFile : IWriter { private IWriter _consoleWriter; private IWriter _fileWriter; public WriterToConsoleAndFile( IWriter consoleWriter, IWriter fileWriter) { _consoleWriter = consoleWriter; _fileWriter = fileWriter; } public void WriteLine(string value) { _consoleWriter.WriteLine(value); _fileWriter.WriteLine(value); } }
      
      







しかし、結局のところ、コンストラクターでまったく異なるライターを私たちに転送することができます。 「ファイルへ」および「コンソールへ」ではなく、何でも。 まあ、私たちの目的のためにこれは良いです。 任意の2人のライターを結合できるクラスを作成しました。 RSPはどこにありますか!

これで、1つのステップだけが残ります。2から-任意の数までです。 ここにあります:



 public class ManyWriters : IWriter { private IWriter[] _writers; public ManyWriters(IWriter[] writers) { _writers = writers; } public void WriteLine(string value) { foreach (var writer in _writers) { writer.WriteLine(value); } } }
      
      







ほら、リンカーを手に入れた。

他に何? まだ足りない? はい、悪くはありませんが、一つだけです。 今では、作家を集めるのはそれほど簡単ではないかもしれません。 1つを作成して構成し、もう1つを作成して構成し、リンカーを使用してそれらを結合します...難しいでしょう? 書くための設定を使用して演習を作成できます。どのライターを使用するかを選択します。

そして、新しいクラスは、設定ファイルを読み取り、必要なライターを作成し、設定を1つのManyWritersに設定し、IWriterインターフェイスを閉じて、使用するライターの残りの部分を他の誰も知らないようにします。彼の出口...そしてそれは工場になります。



All Articles