1

I have an application which should produce cars and operate with them. Car object creation is a complex process so I need a factory for each type of car. Also I want users to be able to provide their own type of cars and factories which produce them. These car types and factories should be plugged to my application as jars (probably there is a better way than jars but I don't see it).

I've come to an idea of making a common CarFactory which accepts the name of the car ("mercedes", "bmw", "nissan", etc) as an argument. CarFactory has a map where each name is mapped to its own factory class. The code looks something like this (sorry I can't provide a working copy because I'm still evaluating it and don't have a version which compiles without errors)

import scala.collection.mutable.Map

class CarFactory {
  var knownCarTypes = Map[String, Class[Factory]]()

  def create(carType: String) = knownCarTypes.get(carType) match {
      case Some(factoryClass) => Some(factoryClass.getMethod("create").invoke(null).asInstanceOf[Car])
      case None => None
    }
  }
}

The knownCarTypes is mutable because I want user factories to register on this map providing what type of car they are responsible for and what is the name of the factory class. So from a user class it looks like this

class Mercedes extends Car

object MercedesFactory extends Factory {
  def register() {
    CarFactory.knownCarTypes("mercedes") = getClass
  }

  def create() = new Mercedes()
}

And here is my question. I don't know how to trigger the register() method of a user factory. Is it possible? Is there a better solution than my approach?

I thought about making common trait for factories, find all loaded classes implementing the trait and trigger method via reflection. But it looks quite complex. I hope some design pattern or OOP trick can be used here. What do you think?

Thanks!

om-nom-nom
  • 62,329
  • 13
  • 183
  • 228
Soteric
  • 3,070
  • 5
  • 24
  • 23

2 Answers2

1

If I understand your question correctly, all you have to do is call register from the object's "body":

object MercedesFactory extends Factory {
  def register() {
    CarFactory.knownCarTypes("mercedes") = getClass
  }
  register

  def create() = new Mercedes()
}
Tal Pressman
  • 7,199
  • 2
  • 30
  • 33
  • Unfortunately until I touch MercedesFactory object (in any way, by calling any of its method) it won't invoke object body :( – Soteric Mar 18 '12 at 16:33
1

Finally I got it working via reflection. I iterated over all jars on specified path, found all classes implementing my com.example.Factory trait and triggered their register() method. For jars inspection I used Clapper ClassFinder and for invoking object method I followed Thomas Jung advice. Here is the final code

import org.apache.commons.io.FileUtils
import org.clapper.classutil.{ClassFinder, ClassInfo}
import scala.collection.JavaConverters._

def triggerFactories() {
  val jars = FileUtils.iterateFiles(new File("lib"), Array[String]("jar"), true).asScala.toList
  val classes = ClassFinder(jars).getClasses
  val factories = ClassFinder.concreteSubclasses("com.example.Factory", classes)

  factories.foreach { (factory: ClassInfo) =>
    companion[Factory](factory.name).register()
  }
}

def companion[T](name: String)(implicit man: Manifest[T]): T =
  Class.forName(name).getField("MODULE$").get(man.erasure).asInstanceOf[T]

It's worked for me. It looks tricky but I hope it won't break anything in my application in future. Please post if there is better approach, I'll reaccept the answer.

Community
  • 1
  • 1
Soteric
  • 3,070
  • 5
  • 24
  • 23