君は心理学者なのか?

大学時代に心理学を専攻しなぜかプログラマになった、サイコ(心理学)プログラマかろてんの雑記。

before/afterで学ぶデザインパターン(state編:after)

前回のソースコードをstateパターンを使用して書き換えていく

f:id:karoten512:20171019233939p:plain

前回のソースコードを見てみると、

状態と、その状態の時の振る舞いが描かれていることがわかります。

図の例なら、「HAPPY」「HAPPY時の振る舞い(hello, goodbye)」です。

これを状態クラスとして書き出します。

状態クラス(状態時の振る舞いを持ったクラス)を作成

# たのしい時
class HappyState
  def self.hello
    puts 'hello!!!'
  end

  def self.goodbye
    puts 'goodbye!!!'
  end
end

他の2つの状態についても状態クラスとして書き出します。

# 通常
class NormalState
  def self.hello
    puts 'hello'
  end

  def self.goodbye
    puts 'goodbye'
  end
end


# 風邪引いた時
class ColdState
  def self.hello
    puts 'hello...'
  end

  def self.goodbye
    puts 'goodbye...'
  end
end

Humanクラスが状態クラスを持つようにする

そして、Humanクラスでは先ほどの状態クラスをstateとして保持するようにします。

class Human

  # その人心理的状態
  attr_accessor :state

  def initialize(state = NormalState)
    # 引数が渡ってきていない場合、デフォルトでNormalStateにする
    state = state
  end

  # 挨拶をさせる(stateクラスにメッセージを送る)
  def hello
    state.hello
  end

  # 挨拶をさせる(stateクラスにメッセージを送る)
  def goodbye
    state.goodbye
  end

  # 状態クラスをセットする
  def set_state(state)
    state = state
  end

end

if文による条件分岐がなくなり、スッキリしましたね。

Humanクラス使用時

human = Human.new()
human.hello
human.goodbye

human.set_state(ColdState)
human.hello
human.goodbye

human.set_state(HappyState)
human.hello
human.goodbye

結果

hello
goodbye
hello...
goodbye...
hello!!!
goodbye!!!

仕様変更

さて、これでstateパターンへの書き換えが終了しました。

ここで例の仕様変更です。

上司「恋に落ちた状態も用意してくれ」

stateパターンを使用しない場合、4行の修正が必要になりました。

stateパターンを使用する場合、既存のソースコードはどのように変更されるでしょうか。

結論

既存のソースコード(Human, ~Stateクラス)は変更されません

新規に以下のクラスが追加されるだけです。

# 恋におちた時
class FallInLoveState
  def self.hello
    puts 'hello,  love you!'
  end

  def self.goodbye
    puts "Don't leave me alone..."
  end
end

このクラスを使用するときは、先ほどと同じように

human.set_state(FallInLoveState)
human.hello
human.goodbye

とするだけです。

まとめ

stateパターンを使用することにより、オブジェクトの状態をクラスとして分離することが出来ます。

そうすると、新しい状態を追加した際に、既存のソースコード 一切変更せずに すみ、

新しい状態のクラスを追加するだけですみます。

縮めたのたった4行だけかよ。って思うかもしれません。

たしかにそうです笑

ただ、状態が大量にある場合や複雑な条件分岐が行われる場合、

stateパターンを用いるとソースコードが明快になることが多いです。

before/afterで学ぶデザインパターン(state編:before)

デザインパターンのメリットについて今一度確認したい

業務で使ったり使わなかったりするデザインパターンですが、

なかなかメリットについてつきつめて考えたことがない。

「なんとなくソースコードがきれいになった気がする。。。」で頭がとまってしまっている><

そこで、デザインパターンを利用する前/後を比較することにより

今一度メリットを確かめてみることにしました。

今回はstateパターンで。

心理的状態によっていろんなhelloを返す人間クラス

class Human

  # その人心理的状態
  attr_accessor :state

  # 普通
  STATE_NORMAL = 0
  # 嬉しい時
  STATE_HAPPY  = 1
  # 風邪を引いた時
  STATE_COLD   = 2

  def initialize(state = STATE_NORMAL)
    # 引数が渡ってきていない場合、デフォルトでNORMALにする
    @state = state
  end

  # 挨拶をさせる
  def hello
    if state == STATE_NORMAL
      puts 'hello.'
    elsif state == STATE_HAPPY
      puts 'hello!!!'
    elsif state == STATE_COLD
      puts 'hello...'
    end
  end

 # 挨拶をさせる
  def goodbye  
   if state == STATE_NORMAL
      puts 'goodbye.'
    elsif state == STATE_HAPPY
      puts 'goodbye!!!'
    elsif state == STATE_COLD
      puts 'goodbye...'
    end
  end

  # 何も起きない
  def nothing_happens
    @state = STATE_NORMAL
    puts '(特に何もなかった。)'
  end

  # 試験に通ったから嬉しい
  def pass_a_exam
    @state = STATE_HAPPY
    puts '(試験に通った!)'
  end

  # 風邪を引いて元気がない
  def catch_a_cold
    @state = STATE_COLD
    puts '(風邪を引いた。。)'
  end

end

クラスの利用側

human = Human.new

human.catch_a_cold
human.hello
human.goodbye

human.pass_a_exam
human.hello
human.goodbye

human.nothing_happens
human.hello
human.goodbye

結果

(風邪を引いた。。)
hello...
goodbye...
(試験に通った!)
hello!!!
goodbye!!!
(特に何もなかった。)
hello.
goodbye.

仕様変更

上司「このクラスに、”恋に落ちた状態”を追加してよ」

こんな仕様変更があったときの変更箇所について考えてみる。

class Human

  # その人心理的状態
  attr_accessor :state

  # 普通
  STATE_NORMAL = 0
  # 嬉しい時
  STATE_HAPPY  = 1
  # 風邪を引いた時
  STATE_COLD   = 2
  ############### 1. 状態の追加 ###############

  def initialize(state = STATE_NORMAL)
    # 引数が渡ってきていない場合、デフォルトでNORMALにする
    @state = state
  end

  # 挨拶をさせる
  def hello
    if state == STATE_NORMAL
      puts 'hello.'
    elsif state == STATE_HAPPY
      puts 'hello!!!'
    elsif state == STATE_COLD
      puts 'hello...'
    end
    ############### 2. 分岐の追加 ###############
  end

 # 挨拶をさせる
  def goodbye  
   if state == STATE_NORMAL
      puts 'goodbye.'
    elsif state == STATE_HAPPY
      puts 'goodbye!!!'
    elsif state == STATE_COLD
      puts 'goodbye...'
    end
    ############### 3. 分岐の追加 ###############
  end

  # 何も起きない
  def nothing_happens
    @state = STATE_NORMAL
    puts '(特に何もなかった。)'
  end

  # 試験に通ったから嬉しい
  def pass_a_exam
    @state = STATE_HAPPY
    puts '(試験に通った!)'
  end

  # 風邪を引いて元気がない
  def catch_a_cold
    @state = STATE_COLD
    puts '(風邪を引いた。。)'
  end

   ############### 4. 状態変化の追加 ###############

end

4箇所変更の必要がある。

基本的に既存のソースコードを変更は少ないほど良い。

(規模によっては既存ソースコードの編集はリスクになるから)

stateパターン

このように、クラスが状態をいくつか持つときはstateパターンが有効。

本当に有効なのか?ということで次回、stateパターンを適用してみます。

Angular2/4で使えるdatatable「ngx-datatable」を使ってみた

Angular2/4にてテーブルのソートを簡単に行ったりpagerを簡単に作ったりしたい

pagerだけ作るのであれば以下のmoduleでも良い。

github.com

テーブルのソートなども行いたかったので、ngx-datatableを利用することにした。

github.com

star数も1800超えで、かなり活発に開発されているみたい。

特徴

仮想DOMを使っているので、かなり大きなデータ数を扱える

http://swimlane.github.io/ngx-datatable/#virtual-scroll

10,000件でもスイスイ動く。

jQueryのDatatableを使っていたときは1,000件超えたあたりから

モッサリ動いていた気がする。ngx-datatableすごい。

ページネーションとソートは、クライアント/サーバ両方に対応

今回はデータ件数が多いので、

ページングする毎にAjax通信をしてデータを受け取ろうと思っていたのでこれはありがたかった。

デモページを見るとかなりいろいろなことができそう。

ngx-datatable - Angular2 and beyond component for presenting large and complex data

ngx-datatableを使おうとしたらコンパイル時に「In ambient enum declarations member initializer must be constant expression」が発生

問題

Angular4で作っているアプリケーションにてdatatableを使おうとしたところ、

コンパイル時に以下のエラーが発生

In ambient enum declarations member initializer must be constant expression

解決方法

typescriptのバージョンを上げればよいらしい。

ERROR in E:/works/ferrari-update/ferrari/Grand/node_modules/@swimlane/ngx-datatable/release/types/column-mode.type.d.ts (2,16): In ambient enum declarations member initializer must be constant expression. · Issue #927 · swimlane/ngx-datatable · GitHub

プロジェクトディレクトリにて、

npm install typescript@latest --save-dev

を実行して、再コンパイルするとなおった。

ただangular-cliとtypescriptの相性等があるので、typescriptのバージョンアップには注意。

使っているnode moduleのバージョンを確認したい

バージョンを確認する

現在使っているnode moduleのバージョンを確認する必要がありました。

アプリケーションディレクトリにて、

npm ls

直下のmoduleのみ確認したいときは、

npm ls --depth=0

npm installでinstallされるパッケージのバージョンを固定する(その1)

確認した後は、他の環境でも同じバージョンがインストールされるよう、

package.jsonにバージョンを記載しておいたほうが良いと思われます。

^ や ~ を用いたバージョンだとバージョンに幅が出てしまうので。。

npm installでinstallされるパッケージのバージョンを固定する(その2)

バージョンを固定する方法はもう一つあります。

npm shrinkwrap

を実行すると

npm-shrinkwrap.json

というファイルが生成されます。

新しい環境で

npm install

した際はこちらのjsonファイルに記載されたバージョンに従って、

node moduleのinstallが行われます。

docs.npmjs.com

Angular Materialを導入しようとしたら"has no exported member 'MdCheckboxModule'"と怒られた話

Googleが提唱している「MaterialDesign」を、Angular2/4上で簡単に使うことができる

「Anglar Material」を導入しようとしたら、moduleの読み込みに失敗しました。

 has no exported member 'MdButtonModule'.

CHANGELOG.mdを見てみると

どうやら2017-10-05の変更で、

md-と記載していた部分をmat-と記載するようになったようです。

/** 今まで */
import {
  MdButtonModule
} 
from '@angular/material';

/** これから */
import {
  MatButtonModule
} 
from '@angular/material';
<!-- 今まで -->
<md-checkbox>Click me!</md-checkbox>

<!-- 2.0.0-beta.12以降 -->
<mat-checkbox>Click me!</mat-checkbox>

github.com

ただこの変更によって、幾つかバグが生じているようです(2017-10-17 現在)。

僕もチェックボックスのデザインが崩れる現象に遭遇しました。

安定するまでは前のバージョン使ったほうが良さげ。

ピザ◯ラから100枚のピザが届くまで 〜 CSRF対策とは(その2)

CSRF対策とは

  • その1「CSRF攻撃」について

  • その2「CSRF対策」について

  • その3「RailsにおけるCSRF対策」について

の「その2」です。

おさらい

CSRFとは

  • 悪い人が使うwebサイト攻撃方法
  • これをされるとすごく困る
  • 具体的には「ピザ◯ラから100枚のピザが届いたり」する

前回は、どうやって「ピザ◯ラから100枚のピザが届く」のかシミュレーションをしました。

問題点

f:id:karoten512:20171015194503g:plain

リクエストが正しいページから送られてきているのか

をサーバが判別することが出来ていないのが問題です。

今回はそれを解決します。

解決方法(抽象)

「今来たリクエスト、

 さっき俺(サーバ)が発行したページから来た正しいリクエストなのか?

 それともぜんぜん関係ないページから来たヤバいリクエストなのか?」

というサーバさんの悩みを解決すればよいということになります。

解決方法(具体)

サーバからページを発行する際に、「指紋(token)」をつけておき、

次回のリクエスト時にtokenを持っているか確認します。

以下、具体的にどうやっているのか説明していきます。

正常な場合

f:id:karoten512:20171015204114g:plain

ブラウザからページをリクエストされたサーバは、

token(特定の法則に従って作られた文字列)を作成しそれを保持します。

またそれをhtmlに含めて返します。

htmlのform要素(hidden)に含ませることが多いです。

f:id:karoten512:20171015202047g:plain

さて、購入ボタンが押されると、

data、cookieそして

hidden要素に含まれるtokenがサーバに送信されます。

サーバは送られてきたtokenとサーバ上に保持しておいたtokenを照合して、

「正しいページ」から情報が送られてきたことを確認します。

その後、ユーザ特定を行い、購入処理を行います。

CSRF攻撃された場合

f:id:karoten512:20171015202950g:plain

第3者が発行したページにはtokenが含まれないか、

間違ったtokenが含まれているので、以上のように購入処理が弾かれます。

まとめ

セキュリティを高めるには、

  • 正しい人からのリクエストであること(cookie

に加えて、

  • 正しいページからのリクエストであること(CSRF対策)

が重要ということがよくわかりました。

補足

ワンタイムトーク

なお、攻撃者がtokenを手に入れてしまうと悪いことをされてしまうので、

基本的にtokenは1回のリクエスト毎に新しくなります。

(ワンタイムトークンといいます)

tokenを保持する場所

今回hidden要素に含ませる方法を紹介しましたが、

http headerに含ませる方法もあります。

token照合の方法

今回簡単のために、

クライアント側で保持するtokenとサーバ側で保持するtokenを同一のものとして扱いました。

実際は文字列自体が異なっていることが多いらしいです。

クライアントから受け取ったtokenを、サーバ側でゴニョゴニョして照合するそう。

これについてはその3で触れられたらなあと思っております。