- 投稿日:2019-03-01T15:11:12+09:00
デザインパターン ~Abstract Factory~
1. はじめに
GoFのデザインパターンにおける、Abstract Factoryパターンについてまとめます。
2. Abstract Factoryパターンとは
- Abstract Factoryは、抽象的な工場という意味になります。
- 抽象的とは、具体的にどのように実装されているかについては考えず、インターフェースだけに注目している状態のことです。
- Abstract Factoryパターンは、部品の具体的な実装には注目せず、インターフェースに注目します。そしてそのインターフェースだけを使って、部品を組み立て、製品にまとめる方式です。
- GoFのデザインパターンでは、生成に関するデザインパターンに分類されます。
3. サンプルクラス図
4. サンプルコード
4-1. Factoryクラス
抽象的な工場を表すを行うクラスです。Link、Tray、Pageを作成します。
Factory.javapackage factory; public abstract class Factory { public abstract Link createLink(String caption, String url); public abstract Tray createTray(String caption); public abstract Page createPage(String title); public static Factory getFactory(String classname) { Factory factory = null; try { factory = (Factory) Class.forName(classname).newInstance(); } catch (ClassNotFoundException e) { System.err.println("クラス " + classname + " が見つかりません。"); } catch (Exception e) { e.printStackTrace(); } return factory; } }4-2. Itemクラス
LinkとTrayを統一的に扱うクラスです。
Item.javapackage factory; public abstract class Item { protected String caption; public Item(String caption) { this.caption = caption; } public abstract String makeHTML(); }4-3. Linkクラス
抽象的な部品:HTMLリンクを表すクラスです。
Link.javapackage factory; public abstract class Link extends Item { protected String url; public Link(String caption, String url) { super(caption); this.url = url; } }4-4. Trayクラス
抽象的な部品:LinkやTrayを集めたクラスです。
Tray.javapackage factory; import java.util.ArrayList; public abstract class Tray extends Item { protected ArrayList tray = new ArrayList(); public Tray(String caption) { super(caption); } public void add(Item item) { tray.add(item); } }4-5. Pageクラス
抽象的な部品:HTMLページを表すクラスです。
Page.javapackage factory; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; public abstract class Page { protected String title; protected ArrayList content = new ArrayList(); public Page(String title) { this.title = title; } public void add(Item item) { content.add(item); } public void output() { try { String filename = title + ".html"; Writer writer = new FileWriter(filename); writer.write(this.makeHTML()); writer.close(); System.out.println(filename + " を作成しました。"); } catch (IOException e) { e.printStackTrace(); } } public abstract String makeHTML(); }4-6. ListFactoryクラス
具体的な工場を表すクラスです。ListLink、ListTray、ListPageを作成します。
ListFactory.javapackage listfactory; import factory.Factory; import factory.Link; import factory.Page; import factory.Tray; public class ListFactory extends Factory { public Link createLink(String caption, String url) { return new ListLink(caption, url); } public Tray createTray(String caption) { return new ListTray(caption); } public Page createPage(String title) { return new ListPage(title); } }4-7. ListLinkクラス
具体的な部品:HTMLリンクを表すクラスです。
ListLink.javapackage listfactory; import factory.Link; public class ListLink extends Link { public ListLink(String caption, String url) { super(caption, url); } public String makeHTML() { return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n"; } }4-8. ListTrayクラス
具体的な部品:LinkやTrayを集めたクラスです。
ListTray.javapackage listfactory; import java.util.Iterator; import factory.Item; import factory.Tray; public class ListTray extends Tray { public ListTray(String caption) { super(caption); } public String makeHTML() { StringBuffer buffer = new StringBuffer(); buffer.append("<li>\n"); buffer.append(caption + "\n"); buffer.append("<ul>\n"); Iterator it = tray.iterator(); while (it.hasNext()) { Item item = (Item) it.next(); buffer.append(item.makeHTML()); } buffer.append("</ul>\n"); buffer.append("</li>\n"); return buffer.toString(); } }4-9. ListPageクラス
具体的な部品:HTMLページを表すクラスです。
ListPage.javapackage listfactory; import java.util.Iterator; import factory.Item; import factory.Page; public class ListPage extends Page { public ListPage(String title) { super(title); } public String makeHTML() { StringBuffer buffer = new StringBuffer(); buffer.append("<html><head><title>" + title + "</title></head>\n"); buffer.append("<body>\n"); buffer.append("<h1>" + title + "</h1>\n"); buffer.append("<ul>\n"); Iterator it = content.iterator(); while (it.hasNext()) { Item item = (Item) it.next(); buffer.append(item.makeHTML()); } buffer.append("</ul>\n"); buffer.append("</body></html>\n"); return buffer.toString(); } }4-10. Mainクラス
メイン処理を行うクラスです。
Main.javaimport factory.Factory; import factory.Link; import factory.Page; import factory.Tray; public class Main { public static void main(String[] args) { Factory factory = Factory.getFactory("listfactory.ListFactory"); Link qiita = factory.createLink("Qiita", "https://qiita.com//"); Link dot = factory.createLink("ドットインストール", "https://dotinstall.com/"); Link yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/"); Link excite = factory.createLink("Excite", "http://www.excite.com/"); Link google = factory.createLink("Google", "http://www.google.com/"); Tray pgTray = factory.createTray("プログラミング"); pgTray.add(qiita); pgTray.add(dot); Tray searchTray = factory.createTray("検索サイト"); searchTray.add(yahoo); searchTray.add(excite); searchTray.add(google); Page page = factory.createPage("お気に入り"); page.add(pgTray); page.add(searchTray); page.output(); } }4-11. 実行結果
5. メリット
例えば、サンプルプログラムに新たな具体的な工場を追加する場合、Factory、Link、Tray、Pageのサブクラス作り、それぞれの抽象メソッドを実装することになります。つまり、factoryパッケージのクラスが持っている抽象的な部分を具体化していくだけになります。このとき、いくら具体的な工場を追加しても、抽象的な工場を修正する必要がありません。
6. 参考
- 投稿日:2019-03-01T15:00:04+09:00
grailsでrun-appしたらbootRunでfailedした
はじめに
こんにちは、grailsで色々なものを作っているエンジニアです。
できごと
ある日、ある依存関係を追加して grailsを起動したら、こんな風にブートすらしなくなってしまいました。
load application.groovy | Resolving Dependencies. Please wait... CONFIGURE SUCCESSFUL | Running application... FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':bootRun'. > A problem occurred starting process 'command 'C:\Program Files\Java\jdk1.8.0_201\bin\java.exe'' * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. | Error Failed to start server (Use --stacktrace to see the full trace)--stacktraceを追加して再度起動してみると、
Caused by: java.io.IOException: Cannot run program "C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" (in directory "D:\git_repo\puredash"): CreateProcess error=206, フ ァイル名または拡張子が長すぎます。 at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25) ... 5 more Caused by: java.io.IOException: CreateProcess error=206, ファイル名または拡張子が長すぎます。 ... 6 more | Error Failed to start serverという、香ばしいエラーが。"D:\git_repo\puredash" のどこが長いんじゃ!と思いましたが、どうやら依存関係をたくさん書いているので、classpathが長すぎてコマンドラインが長くなりすぎてるのが原因のようですね。そんな時は、build.gradle にこう書き足します。
grails { pathingJar = true }最初、gradle界隈で同様の事案があるようで(あたりまえか)、いろいろ書いてたらpathingJarタスクはもうあるけど?みたいなエラーが出たので、恐る恐る上記だけにしてみました。ちなみに grails-3.3.9です。それでは皆さん、またがんばりましょう。
参考
- 投稿日:2019-03-01T15:00:04+09:00
grailsでrun-appしたらブートしなくなった
はじめに
こんにちは、grailsで色々なものを作っているエンジニアです。
できごと
ある日、ある依存関係を追加して grailsを起動したら、こんな風にブートすらしなくなってしまいました。
load application.groovy | Resolving Dependencies. Please wait... CONFIGURE SUCCESSFUL | Running application... FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':bootRun'. > A problem occurred starting process 'command 'C:\Program Files\Java\jdk1.8.0_201\bin\java.exe'' * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. | Error Failed to start server (Use --stacktrace to see the full trace)--stacktraceを追加して再度起動してみると、
Caused by: java.io.IOException: Cannot run program "C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" (in directory "D:\git_repo\puredash"): CreateProcess error=206, フ ァイル名または拡張子が長すぎます。 at net.rubygrapefruit.platform.internal.DefaultProcessLauncher.start(DefaultProcessLauncher.java:25) ... 5 more Caused by: java.io.IOException: CreateProcess error=206, ファイル名または拡張子が長すぎます。 ... 6 more | Error Failed to start serverという、香ばしいエラーが。"D:\git_repo\puredash" のどこが長いんじゃ!と思いましたが、どうやら依存関係をたくさん書いているので、classpathが長すぎてコマンドラインが長くなりすぎてるのが原因のようですね。そんな時は、build.gradle にこう書き足します。
grails { pathingJar = true }最初、gradle界隈で同様の事案があるようで(あたりまえか)、いろいろ書いてたらpathingJarタスクはもうあるけど?みたいなエラーが出たので、恐る恐る上記だけにしてみました。ちなみに grails-3.3.9です。それでは皆さん、またがんばりましょう。
参考
- 投稿日:2019-03-01T12:37:00+09:00
WindowsにCorretto8をインストールする
WindowsにCorretto8をインストールする
OracleJDK から Amazon Correttoに移行するかもしれないので、Windows10 に Corretto 8をインストールした際の手順を残します。おそらくWindows7でも同じ手順でインストールできると思います。
Macの場合は、こちらの記事が参考になりました。
[Java]Amazon Corretto8のインストールCorretto とは?
Amazonが実装したJDK(Javaの開発に必要なツールのキット)です。Javaコミュニティのお墨付きを得ており、Java SE(Java標準仕様)との互換性が保証されています。
Amazon Linux 2, Windows, Macでの利用が可能であり、今後、対応OSは更に増える予定です。
Corretto 8 とは?
Amazon Corretto 8 は Java 8 用のJDKです。少なくとも2023年6月まで無償のサポートがあります。
インストーラーのダウンロード
こちらからCorretto 8のインストーラーがダウンロードできます。
https://docs.aws.amazon.com/ja_jp/corretto/latest/corretto-8-ug/downloads-list.html
私の環境はWindows 64bitなので、 amazon-corretto-8.202.08.2-windows-x64.msi をダウンロードします。
Windows 32bitなら、お隣の Windows x86 の方からダウンロードすればよいと思います。ちなみに、JREというのがありますが、こちらにはコンパイラが入っていません。開発用途ならJDK版が必要です。(JRE版でもJVMやAPIは入っています)
インストーラーの実行
インストーラーをダブルクリックすると、ウィザードが起動します。画面に従って進めていきます。
ここまでくればインストール完了です。
デフォルトのままなら、以下のパスにインストールされます。
C:\Program Files\Amazon Corretto\jdk1.8.0_202環境変数を設定する
シェルがコマンドプロンプトの場合
まず、
JAVA_HOMEを確認します。上記のインストール作業によって、自動的にシステム環境変数に
JAVA_HOMEが追加されるはずです。過去に他のJDKを利用していて、もともとJAVA_HOMEが存在する場合は、値が上書きされます。正しく設定されたかどうか、以下のコマンドで確認しましょう。
echo %JAVA_HOME%
C:\Program Files\Amazon Corretto\jdk1.8.0_202と表示されれば成功です。次に、システム環境変数
Pathの先頭に以下を追加してください。
%JAVA_HOME%\bin過去に他のJDKを利用していて、もともと追加済みの場合は、この作業は不要です。
シェルがGit Bashの場合
コマンドプロンプトだけでなく、シェルとしてGit Bashも利用している場合、そちらの環境変数は手動で設定する必要がありますので、その手順を書きます。
まず、
%USERPROFILE%\.bash_profileをテキストエディタで開きます。なければ作ります。最終行の後に以下を追加し、保存します。既に
JAVA_HOMEがある場合は=の右辺を書き換えます。# Java export JAVA_HOME='/c/Program Files/Amazon Corretto/jdk1.8.0_202' export PATH=$PATH:${JAVA_HOME}/binGit Bashを再起動して、以下のコマンドで正しく設定されたかどうか確認しましょう。
echo $JAVA_HOME
/c/Program Files/Amazon Corretto/jdk1.8.0_202と表示されれば成功です。Javaのバージョンを確認する
コマンドプロンプト(またはGit Bash)でJavaのバージョンを確認します。
java -version以下のように表示されれば、インストール成功です。おつかれさまでした。
openjdk version "1.8.0_202" OpenJDK Runtime Environment Corretto-8.202.08.2 (build 1.8.0_202-b08) OpenJDK 64-Bit Server VM Corretto-8.202.08.2 (build 25.202-b08, mixed mode)他のJDKに切り替える
システム環境変数の
JAVA_HOMEの値を、他のJDKのインストールパスに変更するだけで、切り替えはできるはずです。例えば、OracleJDKが
C:\Program Files\Java\jdk1.8.0_172にインストールされているのであれば、JAVA_HOMEにそのパスを指定すれば、CorrettoではなくOracleJDKが有効になります。さいごに
結構、簡単に導入できました。
既存のJavaプロジェクトで動作確認しましたが、特に問題はありませんでした。
- 投稿日:2019-03-01T12:23:54+09:00
Lombokお試し
これも超今さら。読み方は「ロンボク」。
準備
Mavenで入れるのが一番ラクでした。
要は、lombok.jarにクラスパスさえ通れば何でもいいです。pom.xml<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.16</version> <scope>compile</scope> </dependency>getter/setterの自動生成
↓これが
Product.java(@Data導入前)public class Product implements Serializable { private String code; private String name; private BigDecimal price; /* ごちゃごちゃ */ public Product() {} public String getCode() { return this.code; } public String getName() { return this.name; } public BigDecimal getPrice() { return this.price; } public void setCode(String code) { this.code = code; } public void setName(String name) { this.name = name; } public void setPrice(BigDecimal price) { this.price = price; } }↓こうなる。
Product.java(@Data導入後)@Data public class Product implements Serializable { private String code; private String name; private BigDecimal price; /* スッキリ! */ }呼び出し元は、getter/setterがあるものと考えて普通に呼び出せばOK。
Product product = new Product(); product.setCode("0001"); product.setName("商品01"); product.setPrice(BigDecimal.valueOf(10000L)); product.getCode(); // => 0001 product.getName(); // => 商品01 product.getPrice(); // => 10,000可読性が向上していいと思います。
Builderパターンの自動生成
前述のProduct.javaに
@Builderアノテーションを付けると、呼び出し元で以下のようなこともできる。Product product = Product.builder() .code("0001") .name("商品01") .price(BigDecimal.valueOf(10000L)) .build();なんか今風でカッコいい。
ちなみにJavaBeansとBuilderパターンは矛盾しないと考えてOK?
(JavaBeansの仕様書には「getter/setterを持つこと」(※getter/setter以外のメソッドを持つなとは言っていない)と記載してあった)検査例外を非検査例外のように扱う
検査例外を呼び出し元でcatchしなくてもコンパイルエラーにならなくなります。
すなわち、↓これが@SneakyThrows導入前private String encrypt(String src) { MessageDigest md; try { md = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } md.update(src.getBytes()); return md.digest(); }↓こうなる。
@SneakyThrows導入後@SneakyThrows private String encrypt(String src) { MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(src.getBytes()); return md.digest(); }ただ、詳しく調べきれていないのですがこれは使わない方がいいような気がします。どういう原理かイマイチ理解できていませんが、検査例外を
RuntimeExceptionで包んでいるのではなく、検査例外がコンパイルエラーにならないようにコンパイラを騙しているだけのようなのです。前述のソースで意図的にNoSuchAlgorithmExceptionを発生させると、これが呼び出し元にそのまま飛んできます。これはこれで思わぬ副作用があるかもしれません。
前述のソースのように絶対に例外が発生しないとわかりきっている(SHA-256は必ず入っている)ならいいかもしれませんが。あと細かい話ですが、SneakyThrowsアノテーションがついているとJacoco(0.8.2)のカバレッジが100%になりませんでした。カバレッジ100%をテスト計画でうたっていたりすると、後々面倒なことになるかもしれません。
余談
getter/setterが自動生成できるのはまあいいとして、そもそもJavaBeansのsetter自体がカプセル化の考え方に反しているのではないかという、本質的な矛盾に直面中。「getter/setter無くして、フィールドをpublicにするだけじゃダメなんですか?」と聞かれたときに満足のいく回答ができる自信がない。
- 投稿日:2019-03-01T01:01:16+09:00
Android で Watson Assistance(旧Conversation)
gradleの設定
gradel(App.module)へ下記を追加implementation 'com.ibm.watson.developer_cloud:android-sdk:0.5.0'
implementation 'com.ibm.watson.developer_cloud:java-sdk:6.14.0'[IAM Key]
認証が少しややこしいです。
以前はusername, passwordで認証していたようですが、現在は仕様が変わりIAMキーにて認証を行います。
watsonの設定はIBM Cloudのダッシュボードから行え、対象サービス(Assistance)の設定ページでIAMキーを確認できます。
このキーを使って認証しますので、他人に使われると、あなたのアカウントに課金される可能性があります。[EndPoint]
IAMキーとセットで使われるのが、EndPointです。
アクセスするURIなのですがAssitanceの場合は複数 候補があります。
分からなくなったら、設定ページで確認できます。
サーバーがワシントンDCの場合
https://gateway-wdc.watsonplatform.net/assistant/api
となります。[WorkSpace]
これがどこをどう探しても見つからず苦労しました。
IBMのハンズオンでは設定ページに記載があることになっていますが、現在のページにはその欄は無くなっており、確認できません。
Create a skill -> 対象のインスタンス と進むと、Assistanceの会話を構成するツールが起動します。
その時のブラウザ上のURIの中にWorkSpaceIDが潜んでいます。
~workspaces/ ここ /build~ にあります。IBMのwatsonで提供される各機能は各種言語向けのサンプルコードを伴った解説(英語)が用意されています。
APIレファレンス
https://console.bluemix.net/apidocs/assistant
https://www.ibm.com/cloud/watson-assistant/
IBMのチュートリアル(日本語)
https://cloud.ibm.com/docs/services/assistant?topic=assistant-getting-started#getting-started
- 投稿日:2019-03-01T01:01:16+09:00
Android で Watson Assistant(旧Conversation)
gradleの設定
android studioの場合
gradel(App.module)へ下記を追加implementation 'com.ibm.watson.developer_cloud:android-sdk:0.5.0' implementation 'com.ibm.watson.developer_cloud:java-sdk:6.14.0'通信がありますので、AndroidManifest.xml へ下記を追記してください。
<uses-permission android:name="android.permission.INTERNET" />Watson Assistant にはv1とv2があります。
それぞれ使用するAPIが違います。まずどちらのバージョンを使用するか設定画面で確認してください。
以下v1で運用する場合の説明となります。[API Key]
認証が少しややこしいです。
以前はusername, passwordで認証していたようですが、現在はIAMに仕様が変わりAPIキーにて認証を行います。
watsonの設定はIBM Cloudのダッシュボードから行え、対象サービス(Assistant)の設定ページでIAMキーを確認できます。
このキーを使って認証しますので、他人に使われると、あなたのアカウントに課金される可能性があります。[EndPoint]
IAMキーとセットで使われるのが、EndPointです。
アクセスするURIなのですがAssitantの場合は複数 候補があります。
分からなくなったら、設定ページで確認できます。
サーバーがワシントンDCの場合
https://gateway-wdc.watsonplatform.net/assistant/api
となります。[WorkSpace]
これがどこをどう探しても見つからず苦労しました。
IBMのハンズオンでは設定ページに記載があることになっていますが、現在のページにはその欄は無くなっており、確認できません。
Create a skill -> 対象のインスタンス と進むと、Assistantの会話を構成するツールが起動します。
その時のブラウザ上のURIの中にWorkSpaceIDが潜んでいます。
~workspaces/ ここ /build~ にあります。
追記: 作ったskillを選ぶ画面の各スキルの枠の右上 3点ドット -> View API Detail で確認できました。もっとも簡単なサンプル
SampleAssistant.javaprivate static final String WORKSPACE_ID = ""; private static final String IAM_Key = ""; private static final String URI = ""; ~中略~ String sendMessage = "こんにちわ"; //ワトソンへ送信する文字列 IamOptions iamOptions = new IamOptions.Builder().apiKey(IAM_Key).build(); Assistant service = new Assistant("2018-09-20", iamOptions); service.setEndPoint(URI); InputData input = new InputData.Builder(sendMessage).build(); MessageOptions options = new MessageOptions.Builder(WORKSPACE_ID) .input(input) .build(); MessageResponse response = service.message(options).execute(); System.out.println(response);このままでは、常にダイアログ ルートノードでの反応となります。
セッションを継続するためには、いくつか方法があるようですが、watson からの返信で受け取った context を次のリクエストに渡すやり方が、一番簡単だと思われます。Context watsonContext = response.getContext(); //と受け取っておきSampleAssistant2.javaMessageOptions options; if(watsonContext != null) { options = new MessageOptions.Builder(WORKSPACE_ID) .input(input) .context(watsonContext) //次のリクエストに渡す .build(); }else{ options = new MessageOptions.Builder(WORKSPACE_ID) .input(input) .build(); } MessageResponse response = watsonAssistant.message(options).execute(); watsonContext = response.getContext();IBMのwatsonで提供される各機能は各種言語向けのサンプルコードを伴った解説(英語)が用意されています。
APIリファレンスV1
https://console.bluemix.net/apidocs/assistant
APIリファレンスV2
https://cloud.ibm.com/apidocs/assistant-v2
https://www.ibm.com/cloud/watson-assistant/IBMのチュートリアル(日本語)
https://cloud.ibm.com/docs/services/assistant?topic=assistant-getting-started#getting-startedwatsonはクレジットカードの登録無しでも、一定範囲内の利用は無料です。
AIサービス利用の感じをつかむため、ちょっと触ってみてはどうでしょう。
特に、Assistantは概念的にもわかりやすく、様々なサイトへ応用可能ですのでお勧めです。
Wordpress用のモジュールもあるようですよ。







