クライアントサイドアーキテクチャー

Laravelを採用したシステム開発では同時にクライアントサイドのJavaScriptのアーキテクチャーも見直しました。

React, Vue.js

まずは、クライアントサイドのフレームワークではReactVue.jsなどが知られていますのでこれを研究しました。

React のチュートリアルを進めていくと、激しくリファクタリングを行う必要があり、開発の負担が重そうに感じました。

Vue.js はあまり複雑ではなく開発しやすそうに思いました。しかしながら、Model と View が分離しているようで分離していないので、シングルモデル - マルチビューが実現しにくいと思いました。

独自フレームワークの作成

そこでレイヤードアーキテクチャーによって Model と View を分離するフレームワークを新たに作成しました。View(アプリケーション層を含む)+ドメイン層+データアクセス層に分けています。React や Vue.js が実現している1文字入力するたびに反応する Reactive な機能は実現できていませんが、当方の開発範囲ではフィールド毎に反応する機能があればよいと考えています。

いくつか割り切った部分があります。

ECMAScript 6 を使用しています。現在では ECMAScript 6を前提としても問題ない状況になってきていると判断しました。また、jqueryの使用をやめました。

サーバーとのデータ交換は JSON で行うと限定し、FORMによる GET, POST をサポートする機能を実装しませんでした。レンダリング対象となるデータは JSON でクライアントに送信し、クライアント側で DOM を構築するようにしました。

データアクセス層のオブジェクトの取得は依存性注入を行いたかったのですが JavaScript では若干困難ですので諦めました。

バインディングによる Model と View の連係

Model と View との連係は双方向データバインディングによっています。

下記のように、HTML 上の要素に属性を記述することでバインディングを指定しています。

1
2
3
4
5
6
7
8
9
<dl>
<dt>注文番号</dt>
<dd><b>data-property="orderNo"</b> data-input-type="static"></dd>
</dl>
<dl>
<dt>顧客名</dt>
<dd><input type="text" <b>data-property="customerName"</b> data-uneditable-behavior="disabled"/>
<span class="error-message" data-property-error="customerName"></span></dd>
</dl>

上記コードでdata-nodeはプロパティの親となるエンティティや埋め込みバリューを指定し、data-propertyはプロパティを指定しています。

独自のテンプレート構文を導入するのではなく pure HTML で実装することで、デザイナーとの分業がスムーズになります。お納めしたシステムをお客様のデザイナーが更新しています。

サーバー側に Laravel を採用すれば Blade ファイルとなるのですが、JavaScript ファイル読み込みのためにタグを使っているのみなのでデザイナー様も問題なく扱っておられます。

算出プロパティ

Vue.js を調べるうちに 算出プロパティ という機能に興味を持ちました。

この機能は計算式を書いておけば必要なときに計算式が自動的に実行されるものです。

算出プロパティはangularJSで既に実現されていたようです。

以前に当方も独自に同様の機能を作成したことがあるのですが、計算式と合わせて計算のトリガーとなるプロパティ名を設定しなければならないもので、あまり便利とは言えませんでした。
一方 Vue.js の算出プロパティでは計算式さえ設定すればよく、非常に便利なものでした。仕組みを調べると、計算式を評価するたびに依存元を分析・記録するようになっていました。
パフォーマンス的な点が心配になりましたが、そもそも依存元の値に変化がなければ計算式を評価しないので問題は少ないだろうと結論付けています。

早速、同様の機能を当方の Model にも実装しました。採用してみると大変便利で依存性のことをほとんど意識しなくてよく、宣言的に計算式を書きさえすればよくなりました。また、オブジェクトの境界を越えて依存性をたどってくれる点も便利です。

算出プロパティ実装時に工夫した点はリストのようにメソッド呼び出しによって内部の値を変えるケースにも対応したことです。例えば、明細を集計した金額を表示するといったケースにも対応できます。

次のように記述できます。このコードとデータバインディングを組み合わせることによって明細が入力されたら常に合計を計算できます。※コピペで使えるものではありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
export class Order extends Data.ObservableObject {
/**
* 初期化します。
*/
initialize() {
const totalAmountExpression = () => {
return <b>Enumerable.from(this.orderDetails).sum(item => item.amount)</b>
} //合計金額
this.defineProperty('totalAmount', DataType.Number, totalAmountExpression) //合計金額
this.defineInternalChangeProperty('orderDetails',
new Data.ListChangeDetector(),
new Data.ObservableList({createItem: () => new OrderDetails(this)})) //受注明細リスト
}

今回の開発とは関係がありませんが、算出プロパティを C# にも移植し、便利に使っております。

仮想 DOM

React、Vue.js では仮想 DOM によってレンダリングの負担を軽減しています。当方の開発したフレームワークでは仮想 DOM を採用していません。Model が変化すればバインディングによって同時に DOM も変化します。この点でパフォーマンスの心配がありました。

動作させてみると DOM が変更されたらすぐにブラウザーがレンダリングするのではなく、一連のスクリプトが終了してからレンダリングしているようです。

仮想DOMのパフォーマンスについて Vue・React・Angularのパフォーマンス比較検証 という興味深い記事がありました。リンク先のテストではいずれの場合も仮想DOMを使った場合パフォーマンスが落ちています。但し、ほぼすべての要素が変更される条件でのテストなので仮想DOMのメリットが出にくいです。

当方は Firefox でテストしてみたところそれでも仮想DOMの方が早かったです。

Firefox では仮想DOMが早く、Chrome では実在DOMの方が早いという結果から、Chromeでは逐次的にDOMに変更が加えられてもパフォーマンスが落ちないように対策されていると言えます。

以上のことから対象ブラウザーやスピードに求められる条件にもよりますが仮想DOMなしで実用になると考えています。

不変オブジェクト

React では仮想DOMの変更箇所を効率的に特定するために各オブジェクトを不変としています。

不変オブジェクトの場合、参照が同じであれば配下の構造は変更がないと解釈できるので構造を末端までたどる必要がないのです。

オブジェクトのプロパティを変更する場合は、該当するプロパティに変更後の値が設定された新しいオブジェクトを作る必要があります。新しいオブジェクトを作ればそのオブジェクトを所有するオブジェクトも連鎖的に作成する必要があります。オブジェクトグラフはツリー構造になっていますので、変更を末端から最上位まで連鎖的に伝える必要があります。

この作業は負担になりますので、immer など不変でない操作を不変操作に変換するライブラリを使って負担を軽減できます。

当方の仕組みでは、可変オブジェクトを使用していますのでこのような配慮は不要です。また対応する DOM 要素はバインディングによってピンポイントに結合されているので、変更箇所を特定するためのオーバーヘッドはありません。

開発したフレームワークを採用した結果

開発したフレームワークを採用した結果、下記のメリットを感じました。

  • Model, View に分離することですっきりとした構成になった。特に今回の開発で同じエンティティを2箇所以上に表示すると言った要件がたまたまあったが、これに難なく対応できた。
  • サーバーとのデータ交換を JSON のみにしたことで HTML の POST, GET に関連する複雑な仕様に悩まされることがなくなった。
  • 算出プロパティによって記述量が減った。要件変更にも対応しやすくなった。
参照

Laravel

遅まきながらLaravelを採用したシステムを開発しました。開発を通じて感じた利点・欠点を紹介します。

HTMLとデータのバインディングはクライアント側で行いました。ですので Laravel が備えているBladeについては最小限の機能しか使っていません。

次の点が優れていると思いました。

  • 情報がSymfonyに比べて豊富にある。
  • クエリービルダーおよびEloquentのモデルで記述できるクエリーがDQLに比べて書きやすい。
  • 配列をラップしたCollectionのメソッド充実している。
  • Eloquent モデルがメモリーを圧迫することに対して対策が用意されている。
  • データベーステーブルの生成、テストデータの生成機能が用意されている。これを活用することで開発中の早い段階でバグに気が付くことができた。

次のような点が不便と思いました。

  • Eloquent が ActiveRecord であるためドメイン層とデータアクセス層の分離ができない。データアクセス関係のメソッドが静的メソッドになっているのでコンストラクタインジェクションにより生成したDIオブジェクトを利用できない。
  • 複合主キーがサポートされない。例えば伝票に紐づく伝票明細テーブルは伝票番号・伝票明細番号の組み合わせで表されるサロゲートキーの他に単独カラムで一意となるサロゲートキーを付けなければならない。
  • データベース・ドメイン間の変換をカスタマイズしにくい。埋め込みバリュー(値オブジェクト)に対するサポートが中途半端。(toArrayメソッドの対応)
参照

分散アプリケーション

このところ立て続けに ASP.NET Web Service(asmx) を使った分散アプリケーションに携わりました。

クライアントサーバー型アプリケーションの形態で、サーバーに置いたデータベースにクライアントプログラムからアクセスするものがあります。それに対して、分散アプリケーションでは、サーバーでもプログラムを実行しクライアントプログラムからの要求に対してサーバー側プログラムが応答を返します。
分散アプリケーションでは、複数のデータベース操作を1つの要求にまとめられます。例えば明細付き伝票の保存、月次更新などをそれぞれ1回の要求にまとめられます。このためクライアントサーバー間の通信量・通信頻度を減らせます。

当方は今まで上記の内分散でないアプリケーションだけを作成してきました。今回は ASP.NET Web Service ですでに構築されているシステムへの機能追加でした。

今回、取り扱った感想としては構成が複雑になるということです。

その原因の一つに、プロクシタイプの自動生成があります。サーバー側プログラムで提供するサービス(いわゆるメソッドの集合)を作成してから、クライアントプログラムで Web参照の更新 を実行すると、サービスを呼び出すメソッドが生成されます。
このとき、引数や戻り値に対応する型がプロクシタイプとして作られるのですが、ここでつくられた型はサーバープログラムで定義されたものとは異なります。
サーバーでエンティティ類を定義してセッター・ゲッターを記述しても、クライアントには反映されず、サーバーと同等のロジックが処理されません。

後継の WCF では同じ型を参照していればプロクシタイプが作成されず、参照された型が直接使われます。

WCF はさらに gRPC にとって変わられようとしています。.proto ファイルから自動生成すると、ASP.NET Web Service と同様プロクシタイプが生成されますので、これを防ぐためには下記のような工夫が必要になりそうです。

コードファースト ASP.NET Core gRPC

null許容参照型の注意点

NetFramework では、null代入可能な値型をNullable<>型によって表します。T?と略記することができます

一方、参照型ではもともとnull代入可能なので、逆にnullが代入できないことを表す型がなく、実行時にNullReferenceExceptionが発生して初めてミスに気付くことが度々あります。

これを解決するためC# 8.0ではnull許容参照型が導入されました。

記法はnull許容値型と同じく、型名の後に?を付けます。

但し、新たに型が追加されたわけではなく、同じ型についてnullが許容されるかどうかを判別する仕組みで動作します。NullReferenceExceptionが発生する可能性があると思われる場所には、コンパイル時に警告が出るようになっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace NullableReferenceTypeTest
{
class Test { }

class Container<T>
{
public T? Value { get; set; }

public Container(T? value)
{
Value = value;
}
}

class Client
{
public T? GetValue<T>(Container<T?> container)
{
return container.Value;
}

public void GetValueTest()
{
var c1 = new Container<Test>(new Test());
var v1 = GetValue(c1); //警告発生
var c2 = new Container<Test?>(null);
var v2 = GetValue(c2); //警告が発生しない
}
}
}

C# 9.0 では制約がない型引数に ? をつけれるようになりました。

ジェネリックで制約がない型引数を使ってnull許容かどうかにかかわらず使えるクラスを作成する場合、型引数に ? をつけると問題が発生します。

右のコードはこのような場合に余分な警告が発生することを示すものです。
Container はnull許容かどうかにかかわらず使うことを意図したクラスです。
GetValueTest では c1 にnull許容しない型Testを実引数Tに設定しています。
それに対してc2にはnull許容する型Test?を実引数Tに設定しています。
それぞれGetValueで値を取得しているのですが、null許容しない型c1を引数に指定した場合は警告が出ます。

#nullable enableは、以降でnull許容参照型を有効にすることを示しています。
同様に#nullable disableで無効になり、#nullable restoreで以前の設定に戻ります。

対策としては ! をつけて警告を無視させることも考えられます。

しかし、null許容かどうかにかかわらず使えるクラスの型引数にnull許容参照型を指定することは、上記のあいまいさがあるので、当面これに該当する部分はnull許容参照型を無効のままにしようと考えています。

ジェネリックでない型や、ジェネリックの場合でもnullを許容するかどうか用途が決まっている場合には、null許容参照型を使っていこうと思います。

ASP MVC5

ASP.NET MVC5を採用したシステムを手がけています。言語は C# を使用しています。

前回手がけた有名どころのフレームワークSymfonyとの比較にも触れます。

コンパイル

PHPでは実行時にコンパイルが実行されるので大きなライブラリーを使用するのにためらいがあります。
ASP.NET(C#)ではコンパイル済みになっているのでパフォーマンスがよいと思われます。

メモリ

Symfony でバッチ処理した場合、メモリ食いが激しく、数万件の処理でも問題があったので、Doctrine管理外のプレーンオブジェクトを使う、クエリで最低限必要なカラムのみ読み込むなどの工夫が必要でした。経験上C#で数万件の処理はオンメモリで問題なく扱えます。

テンプレートエンジン

Razerが標準で使えます。インテリセンスも効いて Typo を防げます。
但し、デザイナーとの分業を考えたとき、Razerは開発者向けなので継続的にデザイナーに編集を依頼するのは難しいと思います。
この点はSymfonyが採用しているtwigでも事情はあまり変わりません。

フレームワークの柔軟性

テンプレートエンジンはRazer、O/RマッピングはEntity Frameworkが標準で使えるようになっていますが、これを差し替えることができます。
テンプレートエンジンはデザイナーと協業できるテンプレートに差し替え、O/Rマッピングは普段使用しているフレームワークに差し替えました。
差し替え時、ASP.NETが持っているバリデーションの機能を使えるようにするため工夫が必要でした。差し替えのためにはフレームワーク内部の動作を理解する必要があります。

ASP.NET MVC5採用決定時に、ASP.NET Coreについても検討したのですが、利用予定のActiveReportsが対応していなかったので不採用となりました。

参照

Symfony(PHP フレームワーク比較)

phpでSymfonyを採用したシステムを開発しました。開発を通じて感じた利点・欠点を紹介します。

次の点が優れていると思いました。

  • DoctrineにO/Rマッパーの機能が一通り揃っている。

  • 標準的なフォーム部品に相当するクラスが用意され、しかもカスタマイズしやすい。

  • データベース・ドメイン間、ドメイン・ビュー間の変換をカスタマイズしやすい。

  • フォームが階層構造に対応しているため、マスター・ディテールフォーム・部品表などのツリー構造を持つデータに容易に対応できる。

    次のような点が不便と思いました。

  • 学習コストが大きい。

  • DQLが使いにくい。

  • エンティティで関連を操作する際、参照が必要になるので回りくどい処理が必要になる場合がある。

  • エンティティがDIで生成されるので大量の行を操作すると処理が重い。

  • データベースからエンティティを生成する機能があるが、必ずしも適切なエンティティを生成しない。

  • 既定ではサブエンティティ(明細行)を保持する配列のキーとしてカラムの値が使われ、それがINPUT要素の名前の一部に使われ、文字によってはエラーになるので、これを回避しなければならない。

他のフレームワークと比較しました。Symfony のほか Laravel, ASP.NET5 の経験はありますが。 開発経験がない他のフレームワークはドキュメント等や各種Webサイトの情報を元に判断しました。

項目 Symfony 2.8
Laravel 9.6 CakePHP 3 ASP.NET Core 備考
O/Rマッピング コンポーネント Doctrine Eloquent 一体 EntityFramework
問い合わせ言語
DQL(外部DSL)
QueryBuilder(内部DSL)
QueryBuilder(内部DSL) QueryBuilder(内部DSL) Linq(内部DSL)
関連エンティティの暗黙的ロード
  • 遅延
  • 特殊遅延(行毎読み込み・Count関数発行)
  • 即時
  • 遅延
× ×
関連エンティティの明示的ロード クエリーで指定(Join) クエリーで指定(with) クエリーで指定(contain) クエリーで指定(Include)
埋め込みバリュー × ○(ComplexType)
トラッキング × ×
トラッキング指定 エンティティ毎 ロード毎 ロード毎に指定できた方がパフォーマンスチューニングしやすい。
POPO/POCO × × 特定クラスを継承しなくても良いかどうか。
アイデンティティ・マッピング × 不明
ドメインの構造 構造
  • Entity
  • Repository
    (Finder+Updater)
  • EntityManager
    (Updater)
  • ActiveRecord
    (Entity+Finder+Updater)
  • ActiveRecord
    (Entity+Updater)
  • Table
    (Finder+Updater)
バリデーション指定 アノテーションまたは設定ファイル 内部DSL 内部DSL 属性
ビュー 標準テンプレート twig Blade PHP Razor
データ型変換 不明
マスター・ディテールフォーム × × × 標準的な枠組みが用意されているかどうか。
参照

イベントハンドラのフォームからの分離

対話型システムで Windows Forms をはじめイベント駆動で処理を記述するプラットフォームでは、ドメインロジックからキーの処理まで雑多な処理が集中しがちになるといった反省から、レイヤーアーキテクチャーを採用しています。書籍を読んだりインターネットの情報を吸収してそれなりの効果を上げていると考えています。

雑多な処理を分離するもう一つの方法があります。それはイベントハンドラをフォームから分離してしまうものです。下記コードはWindows Forms に対応するもので、エンターキーでフォーカス移動したいとの要望にこたえるものです(但し当方のライブラリの呼び出しがありますのでそのまま使えるものではありません)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// <summary>
/// Enter キーが押されたら Tab キーに変換します。
/// </summary>
public class EnterToTabHandler
{
Form Form;

void FormKeyDown(object sender, KeyEventArgs e)
{
if (e.Handled) return;
if (e.KeyCode == Keys.Enter && (e.Modifiers & (Keys.Alt | Keys.Control)) == 0) {
var control = ViewUtility.GetCurrentControl(Form);
if (control is TextBoxBase && !(control as TextBoxBase).Multiline ||
control is ComboBoxBase && !(control as ComboBoxBase).DroppedDown ||
control is CheckBox || control is RadioButton) {
SendKeys.Send("{TAB}");
e.SuppressKeyPress = true;
}
}
}

/// <summary>
/// <see cref="EnterToTabHandler"/> 型のインスタンスを作成します。
/// </summary>
/// <param name="form">処理対象となるフォーム</param>
public EnterToTabHandler(Form form)
{
Form = form;
form.KeyDown += FormKeyDown;
}
}

利用するフォーム側ではEnterToTabHandlerのコンストラクタ呼び出しを1行記述だけです。

他にもボタンを押したらモデルのコマンドを実行するもの、ファンクションキーを押したら特定のボタンをクリックしたことにするものなど、多くの処理をフォームから追い出すことができます。

この方法は次のようなメリットがあります。

  • 継承を気にしないで使うことができるので機能間の分離が良い
  • 継承とは異なり、多重度に縛られない(モデルのコマンドを実行する例ではボタンとコマンドの組み合わせをいくつ持っても良い)

そしてこの方法はAccessVBAでも使えて当方では長く実績があったものです。

現在は Windows Forms でレイヤーアーキテクチャーと組み合わせて使っています。従来はビューとモデルがパラレル継承階層となっていて硬直していたのですが、ビューの機能継承に縛られなくなったので、ビューはもっぱらデザインの継承階層、モデルは編集、一覧など機能での継承階層と全く別のベクトルで継承するようにしました。

アイデンティティ・マッピングと弱参照

データベースアプリケーションのテクニックの1つにアイデンティティ・マッピングがあります。これはデータベース上の行とメモリー上のオブジェクトを1対1に対応させようというものです。すべての行をメモリーに読み込むという意味ではなく、1つの行に対応するオブジェクトを複数作成しない点に主眼が置かれています。一意マッピング・恒等写像などと訳すことができますが、ここではアイデンティティ・マッピングと呼んでおきます。

1つの行に対応するオブジェクトがメモリー上に複数ある状況というのは、複数画面で同じ行を参照している場合に起こりやすいです。例えば一覧画面でロードしたいずれかの行が別画面で表示されていたりロジック実行で必要となったりといった状況です。

1つの行に対応するオブジェクトがメモリー上に複数あると、行を更新したときに同じ行に対応する別のオブジェクトも更新する必要があります。これを怠ると更新前のオブジェクトが表示されたり、更新前のオブジェクトを使ってロジックが実行されるといった不具合が発生します。アイデンティティ・マッピングではオブジェクト更新時に同じ行を表す別のオブジェクトがありませんのでこのような配慮が不要となります。

アイデンティティ・マッピングを実現するためにテーブルの主キーをキーに持ち、行に対応するオブジェクトを値に持つディクショナリーを用意することが考えられます。データベースからテーブルを読み取ったときは、このディクショナリと突き合わせ処理して、同一キーの要素があればオブジェクトのプロパティをデータベースから読み取った値に更新します。同一キーの要素がなければディクショナリーに追加します。リポジトリーにこのような仕組みを持たせておけば、すべての読み取り処理でアイデンティティ・マッピングを施すことができます。

この仕組みで問題となるのが行に対応するオブジェクトを保持するために設けたディクショナリーは要素を除外することができず、プログラム起動後に1度でも読み取った行が居座り続けるためメモリーを圧迫することです。NetFrameworkのガーベジコレクターはどこからも参照されなくなったオブジェクトを破棄対象とみなしますが、この場合ディクショナリーが参照を持っているので破棄対象になりません。

そこでこのディクショナリーの要素を弱参照するのです。 どこかの画面から参照されていれば要素は破棄されませんがどの画面からも参照されなくなりディクショナリーが弱参照するのみとなればガーベージコレクターによる破棄対象となります。

下記は要素を弱参照で持つディクショナリの例です(C# 6.0文法を使用しています)。インデクサーは読み取り専用にしています。新しい要素は内部で保持するItemCreatorによって作成します。RemoveDeadItems()によって破棄済みの要素を除外します。このようなディクショナリーを使って読み取ったデータを保持します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/// <summary>
/// 弱い参照によって、キーに対応する値を保持します。
/// </summary>
/// <typeparam name="TKey">キーの型</typeparam>
/// <typeparam name="TValue">値の型</typeparam>
/// <remarks>
/// このオブジェクト以外から参照されなくなった値はガーベジコレクションの対象となります。
/// ガーベジコレクションによって破棄された値を参照したときは、再作成します。
/// 全ての値を保持するとヒープメモリが不足する恐れがある場合に使用します。
/// </remarks>
public class WeakReferenceDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>
where TValue : class
{
IDictionary<TKey, WeakReference<TValue> UnderlyingDictionary = new Dictionary<TKey, WeakReference<TValue>();

/// <summary>
/// キーに対応する値を作成する関数を取得します。
/// </summary>
public Func<TKey, TValue> ItemCreator { get; private set; }

/// <summary>
/// 破棄されているキーのシーケンスを取得します。
/// </summary>
IEnumerable<TKey> GetDeadKeys()
{
foreach (var kv in UnderlyingDictionary) {
TValue target;
if (!kv.Value.TryGetTarget(out target)) yield return kv.Key;
}
}

/// <summary>
/// 破棄された値を持つ要素を除外します。
/// </summary>
public void RemoveDeadItems()
{
lock (this) {
foreach (var key in GetDeadKeys().ToArray()) {
UnderlyingDictionary.Remove(key);
}
}
}

/// <summary>
/// キーに対応する値を取得します。
/// </summary>
/// <param name="key">キー</param>
/// <returns>キーに対応する値</returns>
/// <remarks>
/// キーに対応する値がないかまたは既に破棄されている場合は <see cref="ItemCreator"/> によって作成します。
/// </remarks>
public TValue this[TKey key]
{
get
{
lock (this) {
WeakReference<TValue> value;
TValue target;
if (UnderlyingDictionary.TryGetValue(key, out value) && value.TryGetTarget(out target)) return target;
target = ItemCreator(key);
UnderlyingDictionary[key] = new WeakReference<TValue>(target);
return target;
}
}
}

/// <summary>
/// 格納されている要素の数を取得します。
/// </summary>
public int Count => UnderlyingDictionary.Count;

/// <summary>
/// 格納されているキーのシーケンスを取得します。
/// </summary>
/// <remarks>
/// キーに対応する値は破棄されている場合があります。
/// </remarks>
public IEnumerable<TKey> Keys => UnderlyingDictionary.Keys;

/// <summary>
/// 格納されている値のシーケンスを取得します。
/// </summary>
public IEnumerable<TValue> Values
{
get
{
foreach (var kv in UnderlyingDictionary) {
TValue target;
if (kv.Value.TryGetTarget(out target)) yield return target;
}
}
}

/// <summary>
/// コレクションを反復処理する列挙子を返します。
/// </summary>
/// <returns>コレクションを反復処理するために使用できる <see cref="IEnumerator{T}"/></returns>
public IEnumerator<KeyValuePair<TKey, TValue> GetEnumerator()
{
foreach (var kv in UnderlyingDictionary) {
TValue target;
if (kv.Value.TryGetTarget(out target)) yield return new KeyValuePair<TKey, TValue>(kv.Key, target);
}
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();

/// <summary>
/// <see cref="WeakReferenceDictionary{TKey, TValue}"/> 型のインスタンスを作成します。
/// </summary>
/// <param name="itemCreator"> キーに対応する値を作成する関数</param>
public WeakReferenceDictionary(Func<TKey, TValue> itemCreator)
{
ItemCreator = itemCreator;
}
}

PhpStorm

PHPを使った開発では、しばらくNetBeansを使っていました。

PHPについては正確に補完してくれているのですが、JavaScriptの補完はあまり当てになりません。

情報を探しているとPhpStormというIDEが優れているとの書き込みをよく見かけるので使ってみました。
その結果、JavaScriptについても正確に補完してくれることがわかりました。NetBeansに比べて優れている点としては、

  • JavaScriptの補完が正確
  • 関数の順序変更が簡単
  • Ctrl-Wによる選択領域の拡大が多段階にできる
  • ソースコードフォーマットが細かく設定できる
  • 箱形選択時のカーソル移動が合理的(各行で異なる長さの単語があってもCtrl-Wで選択してくれるなど)

などです。NetBeansとは違って有償なのですがそれだけの価値はあったと判断しています。

参照

PHPのトレイトでダイヤモンド継承

PHPのトレイトを使うと多重継承に相当する処理を記述できることを3種類の帳票の例で紹介しました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
trait A
{
public function a(){}
}
trait B
{
use A;
}
trait C
{
use A;
}
class D
{
use B, C;
} //メソッド競合エラー

$d = new D();

トレイトを使い込む内に1つの制限が見えてきました。それはダイヤモンド継承ができないことです(現在ではこの問題は解消されています。恐らくPHP7.3で解消されたものと思われます)。

右のコードで、クラスDは、トレイトBとトレイトCを取り込んでいます。トレイトB、トレイトC共にトレイトAを取り込んでいます。トレイトAはメソッドaを持っています。この場合、PHPではメソッドaが衝突しているという意味のエラーが発生します。トレイトBとトレイトCのそれぞれにメソッドaがあるとみなすようです。

私は共通のトレイトAの実体は1つなのでエラーにならないことを期待しました。includeinclude_onceに例えれば、includeではなくinclude_onceに相当する動作を期待したのですが実際にはincludeに相当する動作をします。

この回避方法としてはトレイトB、トレイトCでトレイトAを取り込まないで、クラスDでトレイトABCを取り込むことが考えられます。

但しトレイトB、トレイトCがトレイトAを取り込むことがコード上で明示されないというデメリットがあります。これについてはコメントで注釈しています。

トレイトB、トレイトCをコーディングするとき、トレイトAに関するメンバーをIDEが補完してくれない可能性があります。PHPStormではトレイトをuseしているクラス、この場合はクラスDを通じてAのメンバーを候補に表示してくれます。Eclipseは調査していません。

参照