FLINTERS Engineer's Blog

FLINTERSのエンジニアによる技術ブログ

【Scala】StrategyパターンでTwitter投稿を実装してみた

こんにちは。@kimutyamです。
新卒入社3年目のアプリケーションエンジニアです。
今はつぶやきGANMA!というグループ会社のサービスを開発・運用しております。

弊社はScalaカンパニーになりつつあり、Scalaを利用したサービスが増えてきた次第であります。
つぶやきGANMA!もバックエンドはScalaで実装されております。

では、本題へ。
TwitterAPIを利用してTwitterに投稿する機能を作って欲しいという要望があったので、Scalaで実現してみました。

後々、コンテキストは変えずにヨコ展開して欲しいといった(例えばFacebookで投稿したいといった)要望が出てくると思われるので、今回はStrategyパターンを利用しました。

StrategyパターンってのはGoFデザインパターンの1つですな。

設計に関して

■やり取り
ディレクター「〜の条件で、Twitterに投稿する機能を実装してほしい」
私「なるほど。では、今後、〜の条件でFacebookに投稿するといったことも考えられますか?」
ディレクター「そうしてくれたら嬉しい。まずTwitterに投稿することを実現してほしい」

■Strategyパターンを選んだ経緯
「〜の条件で」(Context)ってのは固定らしい。
今回はTwitterに投稿するという戦略(Strategy)を実装すればよいが、
別の戦略(予測できる戦略は「Facebookに投稿する」という戦略)に切り替えられ、
また併用できるような設計にした方が望ましいだろう。
戦略はTwitterFacebookといったソーシャルメディアに投稿する戦略。
抽象化するならソーシャルメディアに限定するのもよろしくないので、インターフェースを「外部メディア」としよう。

クラス図はこんな感じだろう。
f:id:septeni-original:20150722164120p:plain

では、実装に移ろう。(※業務ロジックは省いてます)


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"
)