コーヒー飲みながら仕事したい

仕事で使う技術的なことの備忘録とか

Observer パターンを自分なりに整理してみる

免責

いきなり免責というのもアレですが、この記事の真偽は一切保証を致しかねます。
正直、かなり怪しいと思いますので、少なくとも情報収集されている方は、この記事はスキップされたほうが無難かと思われます。 (私のアカウントに書いてあることはもともと正しさの保証は一切できないのですが^^;)

目的

GoFデザインパターンの中で有名度トップ5には絶対入っているであろう「Observer パターン」ですが、そこに出てくる登場人物が個人的にはとってもわかりにくい!

やっていることは「何かが起きた時に、その何かを他の誰かへ通知する」というデザインパターンの中でもシンプルなほうなので、実装時にもよく使うんですが。
しかし、登場人物というか、その登場人物の呼び方がシチュエーションによってコロコロ変わるのが本当に厄介です。
例えば、GoF では Subject/Observer だったり、別名 Pub/Sub 方式と呼ばれ Publisher/Subscriber だったり、Rx では IObservable/IObserver だったり・・・

ということで、個人的な備忘録として GoF でいう Obesrver は、 Pub/Sub 方式では?Rx では?というのを書いてみます。

それぞれの比較

GoF の Observer パターン

まず、GoFデザインパターンで出てくるやつです。

「イベントを発行する人(= 発火元)」が Subject です。
「イベントを受信して処理する人」が、 Observer です。

上記の場合だと、シーケンス的には以下の感じになるでしょうか。

  1. あらかじめ、 Subject は、 Observer を登録しておく
    Subject#AddObserver の実行)
  2. Subject にて何かイベントが発生した場合は、 Observer へ通知する
    Subject#NotifyObservers の実行 ⇒ 内部では Observer#Update の実行)
  3. ObserverUpdate を受けて、何かしらのイベント処理を行う

言ってしまえば、Subject は観察対象、Observer は観察者ですよね。そのまんまですが。

Pub/Sub パターン

次に、GoF の Observer パターンはよく「別名 Pub/Sub パターンとも呼ばれる」と言われていますが、 Pub/Sub パターンでは Observer パターンの登場人物がどのように割り当てられるのか整理してみます。

と、その前にそもそも、「Observer パターン = Pub/Sub パターン」というのは厳密に言うと誤りらしいです。
基本的な考え方は同じなんですが、 Pub/Sub パターンでは、いわゆる Publisher と Subscriber の間に「Broker」なるものが介在します。この辺はこちらに詳しく書かれているので、省略します。

が、ここでは無理やり登場人物をあてはめます。

「イベントを発行する人(= 発行者)」が Publisher です。
「イベントを受信して処理する人(=購読者)」が、 Subscriber です。
「イベントをとりまとめる人(=仲介人)」が、 Broker です。とりまとめると言うとあやふやなので、「メッセージを受け取ると、購読登録者へ配達する人」みたいな感じでしょうか。

シーケンス的には以下になると思います。

  1. あらかじめ Subscriber は購読したいイベントの topic(イベント名みたいなもの)を、Broker に登録しておく
    Broker#Subscribe の実行)
  2. Publisher は何らかのイベントが発生した場合は、 topic と共に Broker へ通知する
    Broker#Publish の実行)
  3. Broker は、あらかじめ登録されている Subscriber に対してイベントを通知する
  4. Subscriber はイベントを受け取ると、何かしらのイベント処理を行う

つまり、Pub/Sub パターンを Observer パターンに当てはめた場合、以下のような対応になります。
イベント発生側:Publisher = Subjectイベント受信側:Subscriber = Observer

※ ただし、やはり Observer パターンとは異なり、PublisherSubscriber は基本的に依存関係にありません(つまり疎結合)。Publisher は自身が通知したイベントがどの Subscriber が購読しているか知りませんし、 Subscriber もイベントの発信元がどの Publisher なのかを知りません。

Rx (Reactive Extensions)

はっきりいって、 Rx はまったくの初心者というか勉強中の身なので偉そうなことは書けませんが、今自分が把握していること(というか理解したつもりでいること)を書きます。

もうまんまなのですが、 GoF の Observer パターンでいうと、IObserveable = Subject で、 IObserber = Observer ですね。(この時点で、なぜ名前を統一しないのか!GoF の定義をそのまま使ってくれ!と思っちゃいます1

そして、さらに恨み節ですが、「IObservableSubscribe メソッドを持つ」というのが個人的にわかりにくい。こんがらがる原因は Observer パターンが Pub/Sub パターンと関連しているという前提認識があるからなのだと思いますが、本来 IObserber が購読者であるはずなのに、 IObservableSubscribe するというのが混乱の元になってます。2

また、もっとやっかいなことに Rx では Subject という「 IObservableIObserver の両方を実装した、一人二役する」クラスがいます。
「おーい、IObservable = Subject やなかったんかーい」って、もうこの段階でパニックです。

まとめ

ということで、 Rx やるうえで、以下のことを覚えておきます。

パターン名 イベントの発行元(= 発行者)の呼び方 イベントの受信先(= 購読者)の呼び方 備考
Observer パターン Subject Observer
Pub/Sub パターン Publisher Subscriber
Rx IObservable IObserver ※ただし、IObservableIObserver の両方を実装する Subject というクラスがある

うーん、ややこしい・・・


  1. 一応補足しておくと、たぶん IEnumerableIEnumerator の絡みという理由があるのだろうと思ってます。

  2. Pub/Sub パターンでも、もし Broker が介在しなければ、 Publisher が Subscribe メソッドを持つことになるので、よく考えたらおかしくないのですが。

Visual Studio 2017 で欠かせないプラグイン 4+1つ

個人的にこれは欠かせないっていう Visual Studioプラグインを備忘録として残しておきます。

Format document on Save

Ctrl + S で保存時、自動整形を実行してから保存してくれるプラグインです。
Visual Studio 自体にもプラグインの機能があるのですが、わざわざ Ctrl + K, Ctrl + D と2回も実行しないといけないし、明示的な操作をしないとソースファイルに反映されないので、このプラグインを使うことで使い勝手が格段に向上します。

marketplace.visualstudio.com

Git Diff Margin

かなり有名なプラグインですが、やはりこれは便利です。
git 管理が前提となりますが、ソースファイルを開いていたらプラグインが自動で git diff を実行してソース上にコミット前との変更箇所が視覚的に表示されます。
なんといっても、変更前と変更後のソースを直感的に確認できるのは便利です。

marketplace.visualstudio.com

VSColorOutput

出力ウィンドウ(ビルド実行時とかにビルドログとかが表示されるところ)の文字に色を付けてくれます。
エラーだったら赤色とか、正常ビルドできたら緑色とか、地味に便利です。

marketplace.visualstudio.com

Shrink Empty Lines

このプラグインを導入すると、文字や数値を含まない行の縦幅が 25% 縮小されます。ちょっとだけですが、見通しが良くなります。

marketplace.visualstudio.com

ForceUTF8 (with BOM)

ちょっとマニアックですが、保存時の文字コードを UTF8 に自動変換してくれます。
Visual Studio のデフォルト文字コードが UTF8 なので、基本的には恩恵を受けることはないのですが、私の環境で以前 Visual Studio が自動生成したコードが Shift-JIS だったことがありました。そのせいでコンパイルが通らなくなり、その理由もなかなか判明せずにハマったので、それ以降保険として適用しています。

marketplace.visualstudio.com

Visual Studio でビルド時に「のプロジェクト情報が見つかりません。これは、プロジェクト参照がないことを示している可能性があります。」でエラーになったときの対処法

(備考:もしかしたら .NET Standard のみの事象かもしれません)

事象

主題の通り、
Visual Studio でビルド実行時に、以下のようなエラーになりました。
xxx.csproj はソリューション内で参照しているプロジェクト)

'xxx.csproj' のプロジェクト情報が見つかりません。これは、プロジェクト参照がないことを示している可能性があります。

また、出力ウィンドウには以下のようなエラーが表示されました。

4>C:\Program Files (x86)\dotnet\sdk\2.0.0\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.targets(114,5): error : 'xxx.csproj' のプロジェクト情報が見つかりません。これは、プロジェクト参照がないことを示している可能性があります。
4>プロジェクト "zzz.csproj" のビルドが終了しました -- 失敗。

対策

どうやら、参照しているプロジェクトが本当は小文字なのに、sln ファイル内では大文字として記述してあったのが原因のようでした。
上記の場合だと、 xxx.csproj のファイル名を、 XXX.csproj (つまり大文字)に修正したら治りました。

Windows は大文字と小文字を区別しないので、適当に変更したことがダメだったようです。

日本語でググってもなかなかエラーを特定できなかったので備忘録として残しておきます。

英語でググったら出てきた解決策が以下です。

VS 2017 RC error: Cannot find project info for... This can indicate a missing project reference · Issue #5144 · dotnet/cli · GitHub

単体テスト<C#> async な moq をセットアップするときにハマったこと

C#単体テストで moq を使用した場合のお話です。
備忘録として残しておきます。

以下のようなインターフェイスがあったとします。

public interface IHoge
{
    Task SomethingAsync(int x);
}

これを moq でセットアップする場合、以下のように書く必要があります。

Mock<IHoge> hogeMock= new Mock<IHoge>();

hogeMock.Setup(m => m.SomethingAsync(It.IsAny<int>())).Callback<int>((value) =>
    {
         // Assert.Equal 等の何かしらの処理をここに記述
    }).Returns(Task.CompletedTask);

重要なのが .Returns(Task.CompletedTask) の部分で、 Task async で実装されるメソッドにはこれをしないとセットアップする必要があります。
でないと、メソッドをコールしても NullReferenceException の例外が投げられてしまいます。
なんとも不親切な例外なので、かなりハマりました・・・

C言語の JSON シリアライザー parson について

parson の紹介

C言語JSONシリアライズすることができないか探していると、 parson なるものを見つけました。

速度はわかりませんが、特徴としては以下のようです。

下記の参考ページに、サンプルなどが載っているので参考になります。

↓文字列を使用することなくパースする、実践的なやり方が公開されています。
かなり参考になります。

kikd.hatenablog.com

↓サンプルを公開されているサイト

mattn.kaoriya.net

↓公式サイトです。
シリアライズとデシリアライズのサンプルがあります。

kgabis.github.io

<おまけ>言語別 JSON parser 一覧

ちなみに、C言語JSONシリアライズ/デシリアライズするのは結構あるみたいです。(こちら参照)

特に気になるのを列挙しておきます。

cJSON

mattn.kaoriya.net

jansson

mattn.kaoriya.net

npm で fatal: unable to connect to github.com: と怒られるとき

npm コマンドを使ってインストールを行うとき、fatal: unable to connect to github.com: と怒られました。
その解決方法です。

条件

ファイアウォールの問題らしいです。
恐らく社内等のネットワーク環境とかが考えられます。

対策

以下を実行します。

git config --global url."https://".insteadOf git://

要は、git://~ というプロトコルがダメらしく、それならば http://~ でアクセスしてやる、というやり方みたいです。

参照

github.com


今回、 gitbook のインストールを試していて、gitbbok-plugin-uml をインストールしようとしたら上記症状が発生しました。

ProtocolBuffers の C# 版の Timestamp について

前に ProtocolBuffers の C# 版の導入をやってみたのですが、

tassi-yuzukko.hatenablog.com

このときに proto ファイル import google/protobuf/timestamp.proto が読込めないせいで以下のようにクラスファイル変換のコンパイルに失敗していました。(このときはこの行自体をコメントアウトしてやり過ごしていましたが・・・)

> protoc -I=./ --csharp_out=./ ./addressbook.proto
google/protobuf/timestamp.proto: File not found.
addressbook.proto: Import "google/protobuf/timestamp.proto" was not found or had errors.
addressbook.proto:49:3: "google.protobuf.Timestamp" is not defined.

今回、ちょっとこの Timestamp という型を使ってみたくなったので、解決策を模索しました。

コンパイル時にパスを指定する

結論から言うと、以下の issue に書かれている通りです。

github.com

以下のように --proto_path オプションを使用してコンパイルします。 (ここでは $(NuGetPackageRoot)C:\Users\<ユーザー名>\.nuget\packages とします)

> protoc -I=./  --proto_path=`$(NuGetPackageRoot)/google.protobuf.tools/3.5.1/tools` --csharp_out=./ ./addressbook.proto

$(NuGetPackageRoot)/google.protobuf.tools/3.5.1/tools 内に、くだんの google/protobuf/timestamp.proto が存在するからです。
要は、 proto ファイルでの import は、コンパイル時に外部の定義先を指定することが必須のようですね。

Timestamp 型とはそもそも何モノ?

proto ファイル上では、以下のように定義されています。

message Timestamp {

  // Represents seconds of UTC time since Unix epoch
  // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
  // 9999-12-31T23:59:59Z inclusive.
  int64 seconds = 1;

  // Non-negative fractions of a second at nanosecond resolution. Negative
  // second values with fractions must still have non-negative nanos values
  // that count forward in time. Must be from 0 to 999,999,999
  // inclusive.
  int32 nanos = 2;
  • seconds フィールド
    • UNIX 時間」とか「 POSIX 時間」とか呼ばれている、1970年1月1日0時0分0秒(UTC)からの経過秒数
    • C言語でいう time() 関数の戻り値である time_t 型のそれ(参考)
  • nonos フィールド
    • XXXミリ秒YYYマイクロ秒ZZZナノ秒を、 XXX,YYY,ZZZ の9桁の整数として表すもの

つまり、なんてことない、結構プリミティブ型に近いような拡張型ですね。

C# での使い方( DateTime 型との互換)

C# での時間は DateTime 型が良く使われると思います。
この互換については、Timestamp クラスに静的メソッドとして以下が用意されているので、双方簡単に変換ができます。

  • Timestamp型を返す FromDateTime(DateTime dateTime)
  • DateTime型を返す ToDateTime()

詳細は、以下を参照してください。
Google.Protobuf.WellKnownTypes.Timestamp Class Reference  |  Protocol Buffers  |  Google Developers

ハマったこと・・・

ProtocolBuffers はバイナリにシリアライズする規格です。しかし、誤って文字列にシリアライズしてしまっていました。
たまたまかもしれませんが、プリミティブ型を文字列にシリアライズしても、正常にデシリアライズできていました。そこで Timestamp 型を文字列にシリアライズすると例外も出ずに失敗してしまい、「なぜ Timestamp 型を使用するとシリアライズできなくなるのか・・・」とハマってしまったので備忘録として残しておきます・・・。