オブゞェクト指向のGin Installer開発

はじめに



次の䞀連の蚘事には、いく぀かの䞻芁な目暙がありたす。

  1. 有甚な゜フトりェアを䜜成する-プログラムず曎新プログラムのむンストヌラヌ。
  2. ゜フトりェア開発に察するオブゞェクト指向アプロヌチの利点を瀺し、簡単に拡匵可胜な゜フトりェアアヌキテクチャを䜜成する方法を教えたす。


この䞀連の蚘事では、パッケヌゞを䜿甚しお䌚瀟の゜フトりェア補品をむンストヌルおよび曎新できる゜フトりェア䜜成の歎史を共有したいず思いたす。 既補の゜リュヌションの䜿甚を拒吊しお独自のむンストヌラヌを䜜成する必芁があるのは、むンストヌラヌの芁件の特異性が原因です。 䞀連の蚘事のトピックが異なるため、開発の必芁性の理論的根拠を掘り䞋げたせん。

開発䞭のアヌキテクチャの䞻な芁件は次のずおりです。

  1. トランザクションメカニズムの実装、およびトランザクションには、SQLトランザクションだけでなく、ファむルトランザクションだけでなく、レゞストリ゚ントリ、構成ファむルの倉曎など、他のOSリ゜ヌスの倉曎に関連するトランザクションも含たれたす。
  2. むンストヌラヌの操䜜ベヌスの拡匵性、぀たり、トランザクションサポヌトの有無にかかわらず、新しいタむプのコマンド操䜜の远加。




したがっお、最初の近䌌の各パッケヌゞには以䞋が含たれおいる必芁がありたす。

  1. 実行されたコマンドのシヌケンスの説明。
  2. デヌタファむルの任意のセット。


.NETプラットフォヌムには、オブゞェクトをXMLファむルにシリアラむズ/デシリアラむズするための䟿利でシンプルなクラスが含たれおいるため、コマンドのシヌケンスの説明をXMLファむルに保存したす。これにより、操䜜を詳しく調べるこずなく、アプリケヌションのメむンロゞックに集䞭するこずができたす。行で。

パッケヌゞ自䜓はTARアヌカむブの圢匏で、堎合によっおはGZIP圧瞮の察象ずなるこずもありたすが、パッケヌゞの保存方法その構造ず圧瞮方法を倉曎するこずを決定するずきに、既存のコヌドぞの最小限の介入が必芁になるように゜フトりェアを蚭蚈しようずしたす。

将来の゜フトりェアのコマンドセットを掚定したしょう。

  1. ファむル操䜜
  2. SQL操䜜
  3. IISサヌバヌ管理
  4. 倚分䜕か...


前述したように、コマンドのセットは拡匵できたす。 さらに、䞻な機胜゜フトりェアコアが別のアセンブリに保存され、実行される可胜性のあるコマンドが、プラグむンの抂念を䜿甚しおメむンモゞュヌルに接続された別のアセンブリに蚭定されるようにしたす。

゜フトりェア開発プロセス䞭に解決する必芁がある興味深いタスクの数から、すでに息を匕き取っおいたす。 これらの行を曞いおいる時点で、アプリケヌションの実際のプロトタむプが既にあり、実際にはただ宣蚀されたすべおの機胜を備えおいないため、倉曎を加えるこずは私が望むよりも少し難しいこずをすぐに予玄したす。 それで、私はただそれをリファクタリングしたす。 ファむル操䜜甚のトランザクションを䜜成する機胜は既にありたすが、単䞀のトランザクション内でファむル操䜜ずSQL操䜜を組み合わせる方法はわかりたせん。 プラグむンのサポヌトはただ実装されおいたせん。 コマンドセットに新しいコマンドを远加するず、4぀のファむルを線集する必芁がありたす。必芁なのは、新しいクラスを宣蚀するだけです。 䞀般的に、仕事は耕されおいない畑です。

最初の近䌌では、システムのクラス図は次のようになりたす。

画像

これには、アプリケヌションが実行できるすべおの操䜜コマンドの芪クラスず、そこから継承されたいく぀かのコマンドCreateFileCommandおよびDeleteFolderCommandがあり、最終的には倚くなりたす。 たた、䞀連のコマンドを含むPackageBuilderクラスもありたす。 PackageBuilderは、パッケヌゞを䜜成しおナヌザヌに提䟛できたす。 パッケヌゞは保存しお実行できたす。

その結果、これらのクラスを䜿甚する擬䌌コヌドは次のようになりたす。

void CreatePackage() { //     PackageBuilder builder = new PackageBuilder(); builder.AddCommand(new CreateFolderCommand() { FolderPath = @"%APPROOT%\files" }); builder.AddCommand(new CreateFileCommand() { SourcePath = @"d:\package\config.xml", //      DestPath = @"%APPROOT%\files\config.xml" //     }); Package package = builder.GetResult(); package.SaveAs(@"d:\package\output\package.pkg"); } void ExecutePackage () { //    Package package = new Package(@"d:\package\output\package.pkg"); package.Execute(); }
      
      





もちろん、クラスの構造にはただ倚くの倉容を起こす時間がありたすが、どこかから始めなければなりたせん。



基本的なコマンドシステム



むンストヌラヌコマンドの基本的なシステムを理解しおみたしょう。 ここでは、ナヌザヌが機胜を簡単に拡匵できるようにするため、むンストヌラヌのすべおの機胜をリストする぀もりはありたせん。

ここでは、残りのチヌムを結合できる基本的なワむダヌフレヌムコマンドに぀いお説明したす。 すべおのコマンドが継承される抜象クラスには、単䞀のDoメ゜ッドが含たれたす。このメ゜ッドは、コマンドによっおプログラムされたアクションを実際に実行したす。 たずえば、DeleteFileコマンドは、このDoメ゜ッド内に正確にファむルを䜜成し、パブリックプロパティ文字列FilePathを匕数削陀されたファむルのパスずしお䜿甚したす。 これがどのように芋えるかです

 public abstract class Command { public abstract void Do(); } public class DeleteFile: Command { public string FilePath { get; set; } public override void Do() { File.Delete(FilePath); } }
      
      





他のすべおのコマンドはたったく同じ方法で実装されたす。コマンドは抜象クラスCommandを継承し、継承されたクラスのパブリックプロパティを匕数ずしお䜿甚しおDoメ゜ッドを眮き換えたす。

したがっお、最初の近䌌では、PackageBuilderでチヌムが次々に远加されるず想定したしたが、このアプロヌチの柔軟性のなさをすぐに実感したした。 むンストヌラコマンドのシヌケンスが、タヌゲットマシンにむンストヌルされおいるサヌドパヌティ゜フトりェアのバヌゞョン番号に䟝存する必芁があるずしたす。 たずえば、サむトのむンストヌルアルゎリズムは、タヌゲットマシンにむンストヌルされおいるIISのバヌゞョンに倧きく䟝存しおいたす。 たたは、たずえば、むンストヌラヌはタヌゲットマシンに必芁なCOMオブゞェクトが存圚するかどうかを確認し、必芁に応じおそれらをむンストヌルする必芁がありたす。 これらのすべおの堎合においお、いく぀かのレゞストリ倀をチェックし、そこから読み取られた倀に応じおさらにアクションを遞択する必芁がありたす。 ここから、ベヌスセットに条件付き実行コマンドが必芁になりたした。それをExecuteIfず呌びたしょう。 むンタヌフェむスの説明は次のずおりです。

 public class ExecuteIf : Command { public string ArgumentName { get; set; } public Command Then { get; set; } public Command Else { get; set; } public void Do(); }
      
      





぀たり、Doメ゜ッドは、ある実行コンテキストからArgumentNameずいうブヌル倉数を読み取り、Trueの堎合はThenコマンドを実行し、そうでない堎合はElseコマンドを実行したす。 ここで、私たちは新しい抂念に盎面しおいたす-実行のコンテキスト。 実行コンテキストをExecutionContextクラスのむンスタンスずしたす。このむンスタンスは、各コマンドのDoメ゜ッドの呌び出しの匕数になりたす。 オヌバヌロヌドされたDoExecutionContextメ゜ッドで抜象クラスむンタヌフェむスを膚らたさないために、Doメ゜ッドは垞にExecutionContext匕数で呌び出されるこずに同意したすが、実行コンテキストを倉曎しないコマンドは、Doメ゜ッド匕数を単に無芖したす。



したがっお、クラスは次のようになりたす。

 public abstract class Command { public abstract void Do(ExecutionContext context); } public class DeleteFile: Command { public string FilePath { get; set; } public override void Do(ExecutionContext context) { File.Delete(FilePath); } } public class ExecuteIf : Command { public string ArgumentName { get; set; } public Command Then { get; set; } public Command Else { get; set; } public void Do(ExecutionContext context); }
      
      







コマンドの基本セットの説明から泚意をそらし、クラスExecutionContextを蚭蚈したす。



実行コンテキスト



パッケヌゞ内で実行された各チヌムは、有甚な䜜業を実行するこずに加えお、実行結果を返すこずもできたす。 たずえば、SQLコマンドは、ストアドプロシヌゞャの実行結果たたは倉曎された行数を返すこずができ、レゞストリから読み取るず、読み取った実際の倀が返されたす。 さらに、䞀般的な堎合、コマンドは1぀のValueType結果ではなく、耇数を返すこずができたす。たずえば、DataSetなどの耇雑な結果を返すこずもできたす。 他のチヌムが以前の操䜜の結果に䟝存しお正しく動䜜するためには、以前のコマンドの結果にアクセスする方法をチヌムに提䟛する必芁がありたす。 同時に、各特定のコマンドは、厳密に前のコマンドの実行結果だけでなく、䞀般に、以前に実行されたコマンドも含めお、以前のいく぀かのコマンドの実行結果に䞀床に䟝存できたす。 たずえば、SqlQueryCommandコマンドによっお以前に返された文字列の配列内のナヌザヌが入力した行のむンデックスを定矩するFindIndexCommandコマンドを䜜成できたす。 ご芧のずおり、この堎合のFindIndexCommandコマンドの結果は、SqlQueryCommandコマンドによっお返されたDataSetず、UserInputCommandコマンドを䜿甚しおナヌザヌが入力した文字列の2぀の匕数に基づいおいたす。 私はただ蚭蚈さえしおいないチヌムの名前で業務を行っおおり、チヌムの名前からその目的が明確であるこずを暗瀺しおいたす。

ナヌザヌにそのような柔軟性を䞎えるために、実行コンテキストExecutionContextを導入する予定です。 特定のキヌの䞋に倀を栌玍し、倀がある堎合はそこから倀を読み取るこずができたす。 実行された各コマンドは、Doメ゜ッドの最初の匕数ずしお受け取りたす。 したがっお、各チヌムは、以前に実行されたすべおのチヌムの結果にアクセスできたす。

ExecutionContextクラスは次のようになりたす。

 public class ExecutionContext { private Dictionary<string, object> _results = new Dictionary<string, object>(); public void SaveResult(string key, object value) { _results[key] = value; } public object GetResult(string key) { return _results[key]; } }
      
      





おそらく将来、他のメ゜ッドが远加されるかもしれたせんが、今のずころは䞊蚘のリストのような単玔な圢匏になりたす。

最初は、次のようなExecutionContextを䜿甚しおいたした。

 public class DeleteFile: Command { public string FilePath { get; set; } private ExecutionContext _context; public DeleteFile(ExecutionContext context) { _context = context; } public DeleteFile() { } public override void Do() { } }
      
      







぀たり、むンスタンスを匕数ずしおコマンドコンストラクタヌに送信し、プラむベヌト倉数に保存しおから、必芁に応じおDoメ゜ッドで䜿甚したす。 ただし、コマンドパッケヌゞは通垞、最初にシリアル化されお保存され、次に結果ファむルがデシリアラむズされお実行されるこずを思い出したした。この2番目のステップでは、デシリアラむザヌがパッケヌゞをメモリに自動的に埩元したすが、閉じた倉数_contextが初期化されたせん閉じおいるためパッケヌゞは初期化されおいないコンテキストで実行されたす。 コンテキストはダりンロヌドしたパッケヌゞの実行段階でのみ必芁であるず考え、コンテキストをDoメ゜ッドの匕数ずしお送信するこずにしたした。 これはすぐに別の利点をもたらしたした-新しいコマンドごずにこの繰り返しコヌドを蚘述する必芁がなくなりたした。

 private ExecutionContext _context; public DeleteFile(ExecutionContext context) { _context = context; } public DeleteFile () { }
      
      





結局、匕数を持぀コンストラクタヌは䞍芁になりたした。぀たり、コンパむラヌがデフォルトのコンストラクタヌを䜜成するため、デフォルトのコンストラクタヌを蚘述する必芁もありたせん。 これで、コマンドフレヌムワヌクは次のようになりたす。

 public class DeleteFile: Command { public string FilePath { get; set; } public override void Do(ExecutionContext context) { } }
      
      





぀たり、その䞭に䜙分なものはなく、本質だけがありたす。 これは、アプリケヌションをサポヌトおよび開発するプログラマヌがその本質を理解しやすくなるこずを意味したす。

これで、ExecuteIfコマンドの実装を蚘述できたす。

 public class ExecuteIf : Command { public string ArgumentName { get; set; } public Command Then { get; set; } public Command Else { get; set; } public override void Do(ExecutionContext context) { if ((bool)context.GetResult(ArgumentName)) { Then.Do(context); } else if (Else != null) { Else.Do(context); } } }
      
      







基本的なコマンドシステム。



むンストヌラヌコマンドフレヌムワヌクの䜜成に戻りたしょう。 基本抜象クラスCommand、および条件付きコマンドExecuteIfに぀いおは既に説明したした。 ExecuteIfコマンドのThenおよびElseパラメヌタヌには、1぀のコマンドだけでなく、連続したコマンドのブロック党䜓を指定できたす。 そしお、これはコマンドを蚭蚈する必芁があるこずを意味したす-挔算子括匧の類䌌物。 このコマンドをCommandSequenceず呌びたす。 そのむンタヌフェヌスず実装は簡単です

 public class CommandSequence : Command { public List Commands; public override void Do(ExecutionContext context) { foreach (Command command in Commands) { command.Do(context); } } }
      
      





その唯䞀のパラメヌタヌはコマンドのリストであり、Doメ゜ッドは単玔にそれらを順番に実行したす。



ExecuteIfコマンドは、入力コンテキストずしお実行コンテキストからブヌル倉数の名前を受け入れるこずを思い出しおください。 このブヌル倉数は、コンテキストのどこかに衚瀺されるはずです。 圌女は特定の状態をチェックした結果、そこに珟れるはずです。 そしお、量を比范するためのチヌムを蚭蚈する時が来たした。 文字列比范コマンドを開始するように蚭蚈したす。 コマンドむンタヌフェむスずその郚分的な実装は次のようになりたす。

 public class CompareStringsCommand: Command { public string FirstOperandName { get; set; } public string SecondOperandName { get; set; } public string ResultName { get; set; } public void Do(ExecutionContext context) { string operand1 = (string)(context.GetResult(FirstOperandName)); string operand2 = (string)(context.GetResult(SecondOperandName)); bool result = Compare(operand1, operand2); context.SaveResult(ResultName, result); } }
      
      





コマンドは、コンテキストから2぀の倉数をFirstOperandNameずSecondOperandNameの名前で読み取り、それらを比范しCompareメ゜ッドを䜿甚しお比范を指定したす、比范のブヌル結果をResultNameずいう名前でコンテキストに保存したす。

このコマンドは、他のすべおの文字列比范コマンドの基本抜象クラスになるための優れた候補であり、Comparestring、stringメ゜ッドは、抜象クラスメ゜ッドになり、継承者に実装するための優れた候補であるこずがわかりたす。 次に、抜象クラスのむンタヌフェヌスのみを曞き盎しDoメ゜ッドの実装は同じたた、盞続人の1人のコヌドも提䟛したす。

 public abstract class CompareStringsCommand: Command { public string FirstOperandName { get; set; } public string SecondOperandName { get; set; } public string ResultName { get; set; } protected abstract bool Compare(string operand1, string operand2); public override void Do(ExecutionContext context) { //    ,       } } public class StringStartsWith : CompareStringsCommand { protected override bool Compare(string operand1, string operand2) { return operand1.StartsWith(operand2); } }
      
      





ご芧のずおり、バむナリ2぀の匕数比范挔算子のコマンドセットは、CompareStringsCommandクラスの継承者を䜜成し、その䞭にComparestring、stringメ゜ッドを定矩するだけで、他の比范操䜜EndsWith、Contains、Equals、NotEqualsで簡単に拡匵できたす。

この段階での単項挔算たずえば、IsNullOrEmptyなどは、第2オペランドを単に無芖しお、同じ基本クラスを䜿甚しお実装するこずを提案したす。

これに泚意したしょう。 CompareStringsCommandコマンドは、実行コンテキストに栌玍されおいる2぀の匕数を比范したす。これらの匕数は、他のコマンドの結果ずしおのみ存圚したす。 しかし、たずえば、レゞ​​ストリから読み取られた行が䞀定の郚分文字列で始たるかどうかを知りたい堎合はどうすればよいですか この定数が実行コンテキストに存圚するこずを確認する必芁がありたす。 指定された名前で実行コンテキストに倀を単玔に保存するコマンドを蚭蚈したす。 ここではすべおが簡単です

 public class SaveConstantCommand: Command { public string ResultName { get; set; } public object Value { get; set; } public override void Do(ExecutionContext context) { context.SaveResult(ResultName, Value); } }
      
      





次の章では、トランザクションメカニズムの実装に぀いお説明したす。



第二郚ぞのリンク



All Articles