佐々木屋

技術的なことから趣味まで色々書きます

Strategyパターン

デザインパターンの中でもよく見るものですが、オブジェクト指向が分からないとチンプンカンプンになります。

「戦略」という意味で、メソッドなどにちりばめられた静的なアルゴリズムを、ifやswitch(VB.NETはSelect Case)などの条件分岐で動的にアルゴリズムごと変更してしまう手法です。

このパターンによってアルゴリズムに変更が生じても、条件分岐で意図しない影響が出ないようになります。

まずインターフェースを定義します。

Public interface IStrategy {
    void Work();
}
Public Interface IStrategy
    Sub Walk()
End Interface

次にそれらを実装したクラスを定義します。

public class Class1 : IStrategy {
    public void Walk() {
        Console.WriteLine("テクテク歩く");
    }
}

public class Class2 : IStrategy {
    public void Walk() {
        Console.WriteLine("ノソノソ歩く");
    }
}

public class Class3 : IStrategy {
    public void Walk() {
        Console.WriteLine("サクサク歩く");
    }
}
Public Class Class1
    Implements IStrategy

    Public Sub Walk() Implements IStrategy.Work
        Console.WriteLine("テクテク歩く")
    End Sub
End Class

Public Class Class2
    Implements IStrategy

    Public Sub Walk() Implements IStrategy.Work
        Console.WriteLine("ノソノソ歩く")
    End Sub
End Class

Public Class Class3
    Implements IStrategy

    Public Sub Walk() Implements IStrategy.Work
        Console.WriteLine("サクサク歩く")
    End Sub
End Class

最後に、インターフェースを通じて必要な処理を返す(委譲させるための)クラスを定義します。

public class Context {
    public IStrategy Strategy { set; get; }

    public void Execute() {
        Strategy.Walk();
    }
}
Public Class Context
    Public Property Strategy As IStrategy

    Public Sub Execute()
        Strategy.Walk()
    End Sub
End Class

こうすることで、メインコードからアルゴリズムを切り離してカプセル化することにより、不用意な操作やアルゴリズムの変更に対応できるようになります。

var cls = new Context();

cls.Strategy = new Class1();
cls.Execute();

cls.Strategy = new Class2();
cls.Execute();

cls.Strategy = new Class3();
cls.Execute();
Dim cls As New Context()

cls.Strategy = New Class1()
cls.Execute()

cls.Strategy = New Class2()
cls.Execute()

cls.Strategy = New Class3()
cls.Execute()

オブジェクト指向の導入

課題

手続き型でよく見る配列に情報を持つ場合(カンマ区切りなども含めて)があります。一番の問題は何番目に何のデータを保持しているのかが分かりにくいということです。これは非常に危険なデータの持ち方でもあります。

string[] person = new string[2];
person[0] = "山田太郎";
person[1] = "21";
person[2] = "090********";
Dim person(2) As String
person(0) = "山田太郎"
person(1) = "21"
person(2) = "090********"


リファクタリング

オブジェクト指向を導入することで一気に解決します。Personクラスをオブジェクトとして構築し、そこで管理させればよいのです。

public class Person {
    public string KanjiName { get; set; }
    public int Age { get; set; }
    public string Tell { get; set; }
}

Person p = new Person();
p.KanjiName = "山田太郎";
p.Age = 21;
p.Tell = "090********";
Public Class Person
    Public Property KanjiName As String
    Public Property Age As Integer
    Public Property Tell As String
End Class

Dim p As New Person()
p.KanjiName = "山田太郎"
p.Age = 21
p.Tell = "090********"

クラス化することでプロパティで桁数チェックや体裁チェックなども可能となります。その他にも、複数のインスタンスを作成してジェネリックコレクションにすれば、配列によるインデックス管理も行わなくてよくなります。

Sys.Extended.UI.ModalPopupBehaviorがnullになる件

ASP.NET Ajaxネタですが、ネットで同じように困った方が見つからず、海外サイトでようやく回避策を見つけたので備忘として載せておきます。


というか、これだけ日本語のサイトが出てこないと、正直私が何か間違っているのか???と不安になります・・・。

もし誰か、何かお気づきあれば、教えて下さい・・・。


マスターページにToolkitScriptManagerを配置したコンテンツページ上でModalPopupExtenderを配置したサイトです。

ModalPopupExtenderのプロパティは以下のように設定しています。

asp:ModalPopupExtender

プロパティ
ID MordalCalendar
Drag True
Enabled True
TargetControlID btnCalendar
PopupControlID pnlCalendar1
PopupDragHandleControlID pnlCalendar2
BehaviorID MordalCalendar
パネルは単純に重ねて、ボタンクリックでカレンダーが表示されるようなHTMLです。

<asp:Panel ID="pnlCalendar1" runat="server">
    <asp:Panel ID="pnlCalendar2" runat="server">
        <iframe id="iCalendar" runat="server" class="iCalendar"></iframe>
    </asp:Panel>
</asp:Panel>



で、実行すると、以下のエラーが表示されて正しく画面が表示されません(ajaxがおかしくなる)。

AjaxControlToolkit.ModalPopupExtender missing required PopupControlID property value
sys.extended.ui.modalpopupbehavior undefined

つまり、Sys.Extended.UI.ModalPopupBehaviorがnullになっているわけです。
なぜそのような状況になるのかは今をもって不明ですが、以下で回避することができます。

ToolkitScriptManagerコントロールのCombineScriptsプロパティを「True」から「False」へ変更する

CombineScriptsプロパティはAjaxに関わる重複するJavaScriptファイルをダウンロードしない機能ですが、それが有効になっているとJavaScriptをまとめることが出来るので、通信の効率化になるようです。ただ、作成しているWEBアプリケーションではほとんど影響ないようでしたので、今回はこれを無効化することで回避しました。

Facadeパターン

Facadeパターンは現在でもしばしば使われるくらい、結構メジャーなパターンです。

Facadeは「玄関」や「窓口」と言われ、その名の通りプログラムの各所から利用される一つのクラスを仲介するパターンです。利用されるクラスが将来変更される可能性がある場合や、メソッドの順番などに制約があり、各所に面倒な「縛り」があった状態で乱用されることを防ぐことができます。

例えば、OtherLibraryという将来変更の可能性があるクラスがあり、メソッドの使用順番に制約があるとします。そのままクラスを利用されると後からの修正が面倒なので、Facadeクラスを用意します。

//変更される可能性のあるクラス
public class OtherLibrary {
    public void Do1() { }
    public void Do2() { }
    public void Do3() { }
}

//Facadeクラス
public class Facade {
    public void DoLibrary() {
        OtherLibrary olib = new OtherLibrary();
        olib.Do1();
        olib.Do2();
        olib.Do3();
    }
}
'変更される可能性のあるクラス
Public Class OtherLibrary
    Public Sub Do1()
    End Sub
    Public Sub Do2()
    End Sub
    Public Sub Do3()
    End Sub
End Class

'Facadeクラス
Public Class Facade
    Public Sub DoLibrary()
        Dim olib As New OtherLibrary
        olib.Do1()
        olib.Do2()
        olib.Do3()
    End Sub
End Class

あとは使用する側は、Facadeクラスを介してOtherLibraryクラスを利用します。OtherLibraryクラスに変更が生じても修正はFacadeクラスのみとなる為、全体の修正量を大幅に抑えることとなります。

Facade doLib = new Facade();
doLib.DoLibrary();
Dim doLib As New Facade
doLib.DoLibrary()

静的クラスとシングルトン

インスタンスを複数作成しないで、どのクラスからも同じインスタンスを見に行くといった場合は静的クラスを利用します。VB.NETの場合はモジュールがほぼ同じような役割(全く一緒ではない)です。

public static class StaticClass {
    public static string UserName { get; set; }
    public static string UserID { get; set; }
}
Public Module StaticClass
    Public Property UserName As String
    Public Property UserID As String
End Module



静的クラスの場合は継承が使用できないことや、インスタンスを特定場面で入れ替えたい場合に対応できない問題があります。そこでシングルトンが必要になります。

シングルトンはクラスのインスタンスが一つであることを保証する設計です。基本的には、外部ファイルのような特定リソースへの場所を一つにしたい場合や、固定値を定義する場合などに使用します。

シングルトン設計は以下の要領で行います。

  1. コンストラクタをprivateにしてインスタンス作成を禁止する
  2. インスタンスをプライベートな静的メンバ変数に保持する
  3. インスタンスの生成を静的コンストラクタまたは静的メンバ変数の初期化で行う
  4. インスタンスを公開する静的なメソッドかプロパティを追加する
public class SingletonClass : Class1 {
    private static SingletonClass obj = new SingletonClass();
        
    private SingletonClass() { }

    public static SingletonClass Instance {
        get {
            return obj;
        }
    }

    public string UserName { get; set; }
    public string UserID { get; set; }
}
Public Class SingletonClass
    Inherits Class1

    Private Shared obj As New SingletonClass()

    Private Sub New()
    End Sub

    Public Shared ReadOnly Property Instance() As SingletonClass
        Get
            Return obj
        End Get
    End Property

    Public Property UserName As String
    Public Property UserID As String
End Class

コンストラクタをprivateにすることで、クラス外からのインスタンス化を禁止しています。唯一のインスタンスはクラスが初めて使用される時にインスタンス保持メンバー変数宣言時に生成されます。インスタンスの受け渡しは静的プロパティを介して行われます。これは静的メソッドで実装しても構いません。

別の手法として、インスタンス生成を静的コンストラクタで行うことも可能です。インスタンスの受け渡しも静的メソッドで実現してみましょう。

public class SingletonClass : Class1 {
    private static SingletonClass obj;

    private SingletonClass() { }
    static SingletonClass() {
        obj = new SingletonClass();
    }

    public static SingletonClass Instance() {
        return obj;
    }

    public string UserName { get; set; }
    public string UserID { get; set; }
}
Public Class SingletonClass
    Inherits Class1

    Private Shared obj As SingletonClass

    Private Sub New()
    End Sub
    Shared Sub New()
        obj = New SingletonClass()
    End Sub

    Public Shared Function Instance() As SingletonClass
        Return obj
    End Function

    Public Property UserName As String
    Public Property UserID As String
End Class



なお、C#VB.NET)は.NET Frameworkによって静的な初期化が確実に実行されますので、JavaC++のような動的な初期化を行う必要はありません。なお、シングルトンはスレッドセーフです。

また、.NET Framework上では、シングルトンのインスタンス破棄はアプリケーション終了時に自動的に行われます。破棄すべきリソースを保持しており、明示的な破棄が必要な場合は、通常クラス同様にIDisposableインターフェースを実装すれば良いことになります。

最近ちょっと忙しい

最近ちょっと忙しい。
というか、1日24時間では足りなくなってきた。平日は仕事しているし、休日は何かと時間を取られる。


元々要領がいい方ではないので、時間を作るのが下手なのです。


このブログ開設のきっかけを作ってくれた二人のSさんへの忘れ物をほぼ届けられたと(一方的に)思うので、しばらくブログ更新をゆっくりにしようと思います。新しい言語の勉強もしたいしね。

ここがダメだよ!VB.NET⑤(静的クラスが無いのよ)

そうなんです。VB.NETには静的クラスが無いんです。

「モジュールがあるやん?」

と思った方。
VB.NETのモジュールがC#の静的クラスとよく混同されますが、残念ながら100%同じというわけではないのです。

呼び出し方

静的クラスの場合、メンバーの呼び出しは同一名前空間にあってもクラス名が必要です。

public static class TestStaticClass {
    public static void StaticMethod() {
        Console.WriteLine("hoge");
    }
}

public static void Main() {
    TestStaticClass.StaticMethod();
}

この為、名前衝突が起こりにくいですし、明示的にどこから呼ばれているのかも分かります。


モジュールの場合、メンバーの呼び出しはクラス名をそのまま呼び出せてしまいます。

Public Module TestModule
    Public Sub StaticMethod()
        Console.WriteLine("hoge")
    End Sub
End Module

Public Sub Main()
    StaticMethod()
End Sub

これは結構致命的で、どこのクラスから呼び出されたものなのかも不明です。また名前衝突も発生しやすくなります。

初期化タイミング

少し分かりにくい&影響が見えない部分ですが、静的クラスとモジュールではbeforefieldinit フラグの設定有無により初期化タイミングが異なります。

静的クラスの場合、通常beforefieldinitフラグが設定されますので、静的クラスへアクセスする前に初期化されることが保証されています。その為、プログラム実行中は静的クラスが初期化されたかどうかのチェックは内部的に実行されることはありません。

モジュールの場合、beforefieldinitフラグは設定されていませんので、モジュールへアクセスする時に初めて初期化されます。よって、プログラム実行中にモジュールが初期化されたかどうかのチェックが行われます。この処理は場合によってはアプリケーション自体のパフォーマンスに影響を及ぼします。