佐々木屋

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

オブジェクト指向の導入

課題

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

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インターフェースを実装すれば良いことになります。

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

デザインパターン

以下に一つでも当てはまる方は、プログラミング設計を少し勉強した方が良いかもしれません。

・最初は問題ないが、だんだん読みづらいコードにある
・スパゲティコードになってしまう
オブジェクト指向の恩恵を生かしきれていない
・コメントが無いとさっぱり分からない

デザインパターン

「事例」みたいなものなのですが、オブジェクト指向型開発において、現場でよく使われる設計を先人たちがパターン化してくれたものが「デザインパターン」です。

発端はGoFGang of Four)と呼ばれるエーリヒ・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディースの4人のことで、1995年に発刊された「オブジェクト指向における再利用のためのデザインパターン」で一躍世に広まることになります。

C言語にクラス概念が追加されたC++と、Java、最後にC#と、これらオブジェクト指向の代表言語でよく使われる設計を網羅したものとなります。

デザインパターンを意識することで、プログラムがオブジェクト指向の概念に沿った、「再利用」・「拡張性」・「独立性」が自然と設計されるようになります。

メリット

過去の偉大な先人たちの失敗から洗練された設計なので、当然オブジェクト指向の恩恵を受けることが出来ることは言うまでもありません。

それ以外に、

  • 設計の変更に強い
  • 不具合に強い(発見しやすい)
  • 汎用性が高い

などが挙げられます。

この後、少しずつですが例をとって紹介していければと思っています。

LINQ同様、デザインパターンは1冊の書籍で成り立つほどの分量です。多種多様な言語のものが多く出回っていますので、一度手にとってみてはいかがでしょうか。

但し、当然ですがオブジェクト指向の概念を100%理解した状態でなければ正直意味がありませんので、怪しい場合はよく復習しておいて下さい。

GroupByメソッド③

GroupByメソッドはクラスコレクション以外、通常のarrayコレクションでも使用可能です。

List<string[]> lst = new List<string[]> {
                    new string[]{ "1", "25", "5", "3000"},
                    new string[]{ "1", "31", "10", "4000"},
                    new string[]{ "2", "31", "98", "12800"},
                    new string[]{ "1", "25", "11", "250"}
};

var res = lst.GroupBy(x =>Tuple.Create(x[0], x[1] ));
Dim lst As New List(Of String()) From {
                    New String() {"1", "25", "5", "3000"},
                    New String() {"1", "31", "10", "4000"},
                    New String() {"2", "31", "98", "12800"},
                    New String() {"1", "25", "11", "250"}}

Dim res = lst.GroupBy(Function(x) Tuple.Create(x(0), x(1)))



GroupByメソッドにより、集約したコレクションの結果を使って集計することが簡単に出来ます。

var res = lst.GroupBy((x) => Tuple.Create(x[0],x[1]))
                                  .Select((y) => new {
                                      KaisyaCD = y.Key.Item1,
                                      SyainCD = y.Key.Item2,
                                      SumValue = y.Sum((s) => int.Parse(s[2]))
                                  }
                                  );

foreach r In res
    Console.WriteLine(r.KaisyaCD + ":" + r.SyainCD + " " + r.SumValue);
Next
Dim res = lst .GroupBy(Function(s) Tuple.Create(s(0), s(1))).Select(
                    Function(y) New With {
                            .KaisyaCD = y.Key.Item1,
                            .SyainCD = y.Key.Item2,
                            .SumValue = y.Sum(Function(s) Integer.Parse(s(2)))
                        })

For Each r In res
    Console.WriteLine(r.KaisyaCD & ":" & r.SyainCD & " " & r.SumValue)
Next
1:25 16
1:31 10
2:31 98