Flyweight design pattern is used to minimize the memory usage with the help of an object that shares as much data as possible with other similar objects.
This pattern is very similar to the simple factory design pattern with slight modification to cache the object in a data structure so that we can return the object which is cached in the data structure if there is a request for same kind of object instead of creating it newly.
Lets say we are processing data from a text files which is received from the EMS. And we need to apply some calibration to few metrics and store them back in some location. The calibration will be applied based on the vendor firmware version and each vendor has shared a csv file which has a mapping between the firmware and the calibration factor. As loading the mapping file for each vendor and converting it into a map is a expensive operation we plan to use flyweight pattern to reuse the object if its already loaded.
We will have a trait VendorCalibration and an separate implementation of this for each vendor like huw,brocade and cisco. The flyweight design pattern is implemented through the CalibrationFactory which stores the data in a map in memory and returns the same if there is a request.
Lets jump into the code
We will start of with VendorCalibration and its implementation . I have not added the logic into the code to make the code simpler and also to focus on the usage of design pattern
trait VendorCalibration { def loadCorrectionFactor(file: File): Map[String, Double] = { // I have not added the code to load the file and convert the data into the // map for simplicity purpose. The key of the hashmap will be the firmware // and value will be calibration to be applied return Map(); } def getCalibration(): Map[String, Double]; }
class HuwCalibration extends VendorCalibration { override def getCalibration():Map[String,Double]= { return loadCorrectionFactor(new File("Huw _calibration.csv")) } }
class BrocadeCalibration extends VendorCalibration { override def getCalibration():Map[String,Double]= { return loadCorrectionFactor(new File("Brocade _calibration.csv")) } }
class CiscoCalibration extends VendorCalibration { override def getCalibration():Map[String,Double]= { return loadCorrectionFactor(new File("Cisco _calibration.csv")) } }
Lets code the CalibrationFactory where the main logic of flyweight pattern exists
object CalibrationFactory { val cache: scala.collection.mutable.Map[String, VendorCalibration] = scala.collection.mutable.Map[String, VendorCalibration]() def apply(vendor: String): VendorCalibration = { val vendorCalibration: Option[VendorCalibration] = cache.get(vendor) val exist = vendorCalibration.getOrElse(false); if (exist == false) { if (vendor == "HUW") { val cal = new HuwCalibration cache += ("HUW" -> cal) return cal } else if (vendor == "CISCO") { val cal = new CiscoCalibration cache += ("CISCO" -> cal) return cal } else if (vendor == "BROCADE") { val cal = new BrocadeCalibration cache += ("BROCADE" -> cal) return cal } else { throw new IllegalArgumentException("Invalid argument.") } } else { return exist.asInstanceOf[VendorCalibration] } } }
Driver Code
object Test extends App { val vendor = "HUW"; val version = "22.22.33" val vendorCal: VendorCalibration = CalibrationFactory(vendor) val calibrationFactor: Option[Double] = vendorCal.getCalibration().get(version); val value: Double = calibrationFactor.getOrElse(1) }