Flyweightパターンの雑感とJavaおよびcoffeescriptでの実装
復習がてら殴り書きしつつ、Javaとcoffeescriptでの実装を貼っておく
メモ
- GoFが定義したデザインパターンの1つ。同一のインスタンスを複数箇所で利用するときに、1つのインスタンスを再利用することで省リソース化することをねらう
- 共有したいインスタンスFlyweightと、キャッシュを保持しつつ必要に応じてFlyweightインスタンスを生成するFlayweightFactoryが想定される
- Factoryはシングルトンで実装される場面が多く、それによりキャッシュ機構が複数存在してしまうことを防ぐ
- Factoryの振舞いは次の通り
- 利用シーン
- イミュータブルなクラスを複数生成したい場合など
- 注意点
- 一度 FlyweightFactory に保存されたインスタンスは、たとえ不要になった場合でもガベージコレクションされることがないため、場合によっては明示的に FlyweightFactory から削除する必要がある
- Factoryの裏でシングルトンをいくつも存在させたいという場面で、自然と思いついていそうなパターンである。
- プリミティブでないものをキャッシュ機構に載せるということは、参照を保持するということなので、もし参照先で変更が生じた場合、それは参照元にも影響を与える。こう考えるとよくあるバグを生みそうなパターンである。
Javaによる実装
- 素直
package com.sisidovski.flyweight; public class HumanLetter { private String letter; public HumanLetter(String character) { this.letter = character; } public void display() { System.out.println(letter); } } package com.sisidovski.flyweight; import java.util.HashMap; import java.util.Map; public class HumanLetterFactory { private Map<String, HumanLetter> map = new HashMap<String, HumanLetter>(); public static HumanLetterFactory singleton = new HumanLetterFactory(); private HumanLetterFactory(){} public HumanLetterFactory getInstance() { return singleton; } public int countInstance() { return this.map.size(); } public synchronized HumanLetter getHumanLetter(String letter) { HumanLetter humanLetter = map.get(letter); if (humanLetter == null) { humanLetter = new HumanLetter(letter); map.put(letter, humanLetter); } return humanLetter; } } package com.sisidovski.flyweight; public class Test { public static void main(String args[]) { HumanLetterFactory factory = HumanLetterFactory.singleton; factory.getHumanLetter("a").display(); factory.getHumanLetter("b").display(); factory.getHumanLetter("c").display(); factory.getHumanLetter("d").display(); factory.getHumanLetter("e").display(); factory.getHumanLetter("a").display(); factory.getHumanLetter("b").display(); factory.getHumanLetter("c").display(); factory.getHumanLetter("d").display(); factory.getHumanLetter("e").display(); System.out.println(factory.countInstance()); } }
coffeescriptによる実装
- どちらかと言うとsingletonの実装に躓いた
- 生jsでの実装は結構辛そうだが、時間があるときにやってみたい
class FlyWeight constructor: (string) -> @string = string display: -> console.log @string class Factory map = {} instance = null class Private getFlyWeight: (string)-> flyweight = map[string] unless flyweight flyweight = new FlyWeight(string) map[string] = flyweight return flyweight count: -> Object.keys(map).length @get: -> instance ?= new Private() # test factory = Factory.get(); factory.getFlyWeight('a').display() factory.getFlyWeight('b').display() factory.getFlyWeight('c').display() factory.getFlyWeight('d').display() factory.getFlyWeight('e').display() factory.getFlyWeight('a').display() factory.getFlyWeight('b').display() factory.getFlyWeight('c').display() factory.getFlyWeight('d').display() factory.getFlyWeight('e').display() count = factory.count() console.log count
追記
coffeescriptの例でinstance変数の設定が間違っていたので修正した
Fuelのテストがコケる件
いきなりテスト落ちてるんだが
FuelPHPを使ってみようと公式に書いてある通りにダウンロードしてコードを生成し、まずはテストを走らせてみようと思ったらこんな感じ。
$ php oil test Tests Running...This may take a few moments. PHPUnit 3.7.29 by Sebastian Bergmann. Configuration read from /home/fuelSample/fuel/core/phpunit.xml FFFFFFFF.....FFFF.F.F.....F.................................... 63 / 361 ( 17%) ............................................................... 126 / 361 ( 34%) ............................................................... 189 / 361 ( 52%) ..............................................F................ 252 / 361 ( 69%) ............................................................... 315 / 361 ( 87%) .............................................. Time: 6.89 minutes, Memory: 18.00Mb There were 16 failures: 1) Fuel\Core\Test_Agent::test_browser Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'Firefox' +'unknown' /home/fuelSample/fuel/core/tests/agent.php:47 2) Fuel\Core\Test_Agent::test_platform Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'Linux' +'unknown' /home/fuelSample/fuel/core/tests/agent.php:59 3) Fuel\Core\Test_Agent::test_version Failed asserting that 0 is of type "float". /home/fuelSample/fuel/core/tests/agent.php:71 4) Fuel\Core\Test_Agent::test_property with data set #0 ('Browser', 'Firefox') Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'Firefox' +'unknown' /home/fuelSample/fuel/core/tests/agent.php:165 5) Fuel\Core\Test_Agent::test_property with data set #1 ('Version', 3.6) Failed asserting that 0 matches expected 3.6. /home/fuelSample/fuel/core/tests/agent.php:165 6) Fuel\Core\Test_Agent::test_property with data set #2 ('MajorVer', 3) Failed asserting that 0 matches expected 3. /home/fuelSample/fuel/core/tests/agent.php:165 7) Fuel\Core\Test_Agent::test_property with data set #3 ('MinorVer', 6) Failed asserting that 0 matches expected 6. /home/fuelSample/fuel/core/tests/agent.php:165 8) Fuel\Core\Test_Agent::test_property with data set #4 ('Platform', 'Linux') Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'Linux' +'unknown' /home/fuelSample/fuel/core/tests/agent.php:165 9) Fuel\Core\Test_Agent::test_property with data set #10 ('Frames', true) Failed asserting that false matches expected true. /home/fuelSample/fuel/core/tests/agent.php:165 10) Fuel\Core\Test_Agent::test_property with data set #11 ('IFrames', true) Failed asserting that false matches expected true. /home/fuelSample/fuel/core/tests/agent.php:165 11) Fuel\Core\Test_Agent::test_property with data set #12 ('Tables', true) Failed asserting that false matches expected true. /home/fuelSample/fuel/core/tests/agent.php:165 12) Fuel\Core\Test_Agent::test_property with data set #13 ('Cookies', true) Failed asserting that false matches expected true. /home/fuelSample/fuel/core/tests/agent.php:165 13) Fuel\Core\Test_Agent::test_property with data set #15 ('JavaScript', true) Failed asserting that false matches expected true. /home/fuelSample/fuel/core/tests/agent.php:165 14) Fuel\Core\Test_Agent::test_property with data set #17 ('JavaApplets', true) Failed asserting that false matches expected true. /home/fuelSample/fuel/core/tests/agent.php:165 15) Fuel\Core\Test_Agent::test_property with data set #23 ('CssVersion', 3) Failed asserting that 0 matches expected 3. /home/fuelSample/fuel/core/tests/agent.php:165 16) Fuel\Core\Test_Model_Crud::test_get_connection Failed asserting that null matches expected 'read'. /home/fuelSample/fuel/core/tests/model/crud.php:35 FAILURES! Tests: 361, Assertions: 411, Failures: 16.
記録
- fuelのバージョンは1.7.1です
- これからFuel使うぞって方は、1.8入れましょう
- Agentクラスが機能しかないと記載があった。まとめwiki作成者の方々に感謝しかない
- 関係ないけど素の状態でテスト実行7分近くかかるんですね
- 私の打ったコマンド(なんと3行)
$ curl get.fuelphp.com/oil | sh
$ oil create fuelSample
$ php oil test
- 上がってたissue
- https://github.com/fuel/core/issues/1671
まとめ
issuesにも上げられてるけど、「1.8でやってみ?」とか書いてあるし、まあ一言で言うとウケるとしか言いようがない。
興味のあること
ちょっと学びたいことを整理する。
言語とかライブラリ
- Go言語
- Socket.IO 1.0
- swift
- javascript
- もっと高みを目指したい
- 最近触ってないので、定期的に触ったりしライブラリを試したりしてキャッチアップしておく
- fuel PHP
- 新卒のコード読むために最低限は
人間として
最近のフロントエンド
最近のフロントエンドでみんなが知りたいことってなんだろう。
ChefDevelopmentKit(ChefDK)入れてみた
chefを使ってみたいのだが
最近chefの勉強を始めたのですが、cookbook
だとかrecipe
だとか言う以前に環境構築するのに手間取って、意外と時間がかかってしまった。特にknife-solo
とberkshelf
は依存ライブラリやrubyのバージョン関連で上手く行かず、一度環境を作り直すことになったりした。非rubyistでrbenvだとかgemだとかなんとなく適当に入ってる、みたいな人は同じような経験をした人が多いと思う。
Chef Development Kit (ChefDK)
インストールがまさに成功したときに見つけたのがこれ、もう1日早く知りたかった。。。
http://www.getchef.com/downloads/chef-dk
chef-dkの名の通り主要なものがだいたい入ったパッケージで、普通にインストールするだけでchefだのknifeだのberkshelfだのがまとめて入る。
Installation
上記ページからダウンロードしてインストールするだけ。macなら/opt/chefdk
に一式追加されている。homebrew-caskも使えるっぽい。デフォルトだとsudo権限が必要なので、必要に応じてPermissionは変更する。chef
コマンドやknife
コマンドなどは/usr/bin/
にシンボリックリンクが貼られている。
Settings
ChefDKのシステムを利用する場合はchef
コマンドを利用する。knife-solo
のインストールは以下。
$ chef gem install knife-solo
$ ~/.chefdk/gem/ruby/2.1.0/bin
上手くいかない場合、PATHを通す必要があるかも。
githubが設定は詳しい。 https://github.com/opscode/chef-dk
ChefDKの環境をデフォルトで利用する場合、以下のPATHを追加する。既存のruby環境の上に来る。
$ /opt/chefdk/embedded/bin $ ~/.chefdk/gem/ruby/2.1.0/bin
まとめ
初学者が悩むことなく簡単に実行環境を構築できる、という点では非常に優れているし、アップデートで毎回心配することがないのも素晴らしい。「chefの環境構築はここからスタート」でも個人的にはよいと思う。
※残念ながらwindowsはcomming soon。最も待望していると思われるwindows勢の方は首を長くして待つとよさそうです。
ディレクトリ内の特定ファイルをソートして先頭のn件取得したい
詰まったのでメモ。
特定ファイルを削除
. +-- hoge +-- foo3.txt +-- foo2.txt +-- foo1.tx +-- bar3.sh +-- bar2.sh +-- bar1.sh
こんな感じのディレクトリを想定。 ここから
- .shファイルのみソートして
- フルパスで
- 先頭の2件のみ取得して
- 削除したい
以下のようなコマンドで実現できる。
DIR=`pwd` /bin/ls -A ${DIR}/*sh | awk '{print("'${DIR}'"$1)}' | sort | head - 2 | xargs rm
重要なのは以下の事柄。
- rcファイルでコマンドにオプション付きのaliasを設定している場合にデフォルトだと競合してしまうことがある
- フルパスでコマンドを記述するか、
\ls
のようにバッククォートを使う - lsの-Aオプションで
.
や..
のディレクトリを除外できる - awkコマンドでシェル変数を展開したい場合は、シングルクォートの外に記述する(少々泥臭いのでfind使ったほうが健全かもしれない。また、パスを環境変数に入れるのが少々駄目っぽいのでやはりfindがよさそう)
おまけ
末尾からの件数を出力する場合は、tailコマンドの-n
オプションが便利だ。tail -n 5
で末尾から5行分出力してくれる。また、+10
といったオプションで先頭10行目から末尾まで出力するすることもできる。例えば、10件中先頭2件を残して削除したい場合は、以下のようにする。
LENGTH=`\ls -A | wc -l` # 10 \ls -A | tail +3 | xargs rm #3行目から出力しxargsに渡す
この手のシェルスクリプトは手を動かして覚えておくべき。
複数行ある文字列に重複がないかチェックする
テキスト操作のメモ。とあるファイルが以下のようであったとする。
foo bar baz
これに重複がないかチェックしたい。 以下のコマンドでチェックできる。
cat foo.txt | sort | wc -l cat foo.txt | sort | uniq | wc -l
結果
3 3
上記コマンドで出力が同じであれば重複なしと判断できる。
テキストを変更する。
foo bar baz bar
結果
4 3
重複があることが分かる。