こんにちは。@kimutyamです。
新卒入社3年目のアプリケーションエンジニアです。
今はつぶやきGANMA!というグループ会社のサービスを開発・運用しております。
弊社はScalaカンパニーになりつつあり、Scalaを利用したサービスが増えてきた次第であります。
つぶやきGANMA!もバックエンドはScalaで実装されております。
では、本題へ。
TwitterAPIを利用してTwitterに投稿する機能を作って欲しいという要望があったので、Scalaで実現してみました。
後々、コンテキストは変えずにヨコ展開して欲しいといった(例えばFacebookで投稿したいといった)要望が出てくると思われるので、今回はStrategyパターンを利用しました。
StrategyパターンってのはGoFデザインパターンの1つですな。
設計に関して
■やり取り
ディレクター「〜の条件で、Twitterに投稿する機能を実装してほしい」
私「なるほど。では、今後、〜の条件でFacebookに投稿するといったことも考えられますか?」
ディレクター「そうしてくれたら嬉しい。まずTwitterに投稿することを実現してほしい」
■Strategyパターンを選んだ経緯
「〜の条件で」(Context)ってのは固定らしい。
今回はTwitterに投稿するという戦略(Strategy)を実装すればよいが、
別の戦略(予測できる戦略は「Facebookに投稿する」という戦略)に切り替えられ、
また併用できるような設計にした方が望ましいだろう。
戦略はTwitterやFacebookといったソーシャルメディアに投稿する戦略。
抽象化するならソーシャルメディアに限定するのもよろしくないので、インターフェースを「外部メディア」としよう。
クラス図はこんな感じだろう。
では、実装に移ろう。(※業務ロジックは省いてます)
1.Strategy(戦略インターフェース)
trait ExternalMediaStrategy { def post(message: String) }
2. ConcreteStrategy(具体的な戦略役:Twitter)
class TwitterStrategy( val consumerKey: String, val consumerSecret: String, val accessToken: String, val accessTokenSecret: String, val debug: Boolean = true ) extends ExternalMediaStrategy { private lazy val cb = new twitter4j.conf.ConfigurationBuilder() cb.setDebugEnabled(debug) .setOAuthConsumerKey(consumerKey) .setOAuthConsumerSecret(consumerSecret) .setOAuthAccessToken(accessToken) .setOAuthAccessTokenSecret(accessTokenSecret) private lazy val twitter: twitter4j.Twitter = new twitter4j.TwitterFactory(cb.build()).getInstance() def post(message: String) = { twitter.updateStatus(message) } }
3. Context(コンテキスト:状況判断)
class ExternalMediaContext { def contribute()(implicit externalMediaStrategy : ExternalMediaStrategy) = { val message = "チョコちょーだい" externalMediaStrategy.post(message) } }
4.Client(利用者)
object MyApp extends App{ .... def shareTwitter() = { implicit val strategy = new TwitterStrategy( consumerKey, consumerSecret, accessToken, accessTokenSecret, debug ) val context = new ExternalMediaContext() context.contribute() } }
ちなみに具体的な戦略役を増やす場合はこんな感じ。
class FacebookStrategy(....) extends ExternalMediaStrategy { def post(message: String) = { ... } }
そんでもってClient(利用者)でこんな感じで呼び出してあげる
object MyApp extends App{ def shareFacebook() = { implicit val strategy = new FacebookStrategy(....) val context = new ExternalMediaContext() context.contribute() } }
これなら、Facebook対応する場合もConcreteStrategy(具体的な戦略役)を追加するだけで対応できるでしょう。
今回はTwitterのみ具体的な実装を書きました。
※Twitter4Jを利用してます。
build.sbt載せておきます。
libraryDependencies ++= Seq( "org.twitter4j" % "twitter4j-core" % "4.0.2" )