Wednesday, September 6, 2017

Scala : Registry design pattern or similar?

Leave a Comment

I am migrating my system from java to Scala. I have used registry pattern in my java code to get the implementation from the string. Is there any similar thing I could do with scala ? I am new to scala, can someone point to me proper references ?

My java code :

public class ItemRegistry {      private final Map<String, ItemFactory> factoryRegistry;      public ItemRegistry() {         this.factoryRegistry = new HashMap<>();     }      public ItemRegistry(List<ItemFactory> factories) {         factoryRegistry = new HashMap<>();         for (ItemFactory factory : factories) {             registerFactory(factory);         }     }      public void registerFactory(ItemFactory factory) {         Set<String> aliases = factory.getRegisteredItems();         for (String alias : aliases) {             factoryRegistry.put(alias, factory);         }     }      public Item newInstance(String itemName) throws ItemException {         ItemFactory factory = factoryRegistry.get(itemName);         if (factory == null) {             throw new ItemException("Unable to find factory containing alias " + itemName);         }         return factory.getItem(itemName);     }      public Set<String> getRegisteredAliases() {         return factoryRegistry.keySet();     } } 

My Item interface :

public interface Item {     void apply(Order Order) throws ItemException;      String getItemName(); } 

I map the string like :

public interface ItemFactory {      Item getItem(String itemName) throws ItemException;      Set<String> getRegisteredItems(); }   public abstract class AbstractItemFactory implements ItemFactory {       protected final Map<String, Supplier<Item>> factory = Maps.newHashMap();      @Override     public Item getItem(String alias) throws ItemException {         try {             final Supplier<Item> supplier = factory.get(alias);             return supplier.get();         } catch (Exception e) {             throw new ItemException("Unable to create instance of " + alias, e);         }     }      protected Supplier<Item> defaultSupplier(Class<? extends Item> itemClass) {         return () -> {             try {                 return itemClass.newInstance();             } catch (InstantiationException | IllegalAccessException e) {                 throw new RuntimeException("Unable to create instance of " + itemClass, e);             }         };     }      @Override     public Set<String> getRegisteredItems() {         return factory.keySet();     } }  public class GenericItemFactory extends AbstractItemFactory {      public GenericItemFactory() {         factory.put("reducedPriceItem",  () -> new Discount(reducedPriceItem));         factory.put("salePriceItem",  () -> new Sale(reducedPriceItem));     } } 

where Sale and Discount are implemntation of Item. I use the newInstance method in ItemRegistry to get the class based on the name. Can some one suggest me any similar thing which can allow me to do the same in scala ?

4 Answers

Answers 1

The other answers give the following options:

  • Directly translate your existing Java code to Scala.
  • Implement another version of your existing code in Scala.
  • Use Spring for dependency injection.

This answer offers an approach that is different from the "registry pattern" and that uses the compiler instead of a string, or Spring, to resolve implementations. In Scala, we can use the language constructs to inject dependencies with the cake pattern. Below is an example using simplified versions of your classes:

case class Order(id: Int)  trait Item {   // renamed to applyOrder to disambiguate it from apply(), which has special use in Scala   def applyOrder(order: Order): Unit    def name: String }  trait Sale extends Item {   override def applyOrder(order: Order): Unit = println(s"sale on order[${order.id}]")   override def name: String = "sale" }  trait Discount extends Item {   override def applyOrder(order: Order): Unit = println(s"discount on order[${order.id}]")   override def name: String = "discount" } 

Let's define a class Shopping that depends on an Item. We can express this dependency as a self type:

class Shopping { this: Item =>   def shop(order: Order): Unit = {     println(s"shopping with $name")     applyOrder(order)   } } 

Shopping has a single method, shop, that calls both the applyOrder and name methods on its Item. Let's create two instances of Shopping: one that has a Sale item and one that has a Discount item...

val sale = new Shopping with Sale val discount = new Shopping with Discount 

...and invoke their respective shop methods:

val order1 = new Order(123) sale.shop(order1) // prints: //   shopping with sale //   sale on order[123]  val order2 = new Order(456) discount.shop(order2) // prints: //   shopping with discount //   discount on order[456] 

The compiler requires us to mix in an Item implementation when creating a Shopping instance. We have compile-time enforcement of the dependencies, and we don't need third-party libraries, with this pattern.

Answers 2

You can pretty much just translate your Java classes to Scala and use the exact same pattern as you're doing in Java.

Since Scala runs on the JVM you can also use it with Spring. It may not be the "standard" way of writing services in Scala but it's definitely a viable choice.

Answers 3

This blog is a pretty good resource and answers your question in a good, idiomatic way.

Answers 4

As others have already suggested, you can translate your code directly into Scala without changing the design pattern, if that's what you want.

Here's how that might look:

import scala.collection.Set import scala.collection.mutable import scala.collection.immutable  trait Item  trait ItemFactory {   def registeredItems: Set[String]   def getItem(alias: String): Item }  class ItemRegistry(factories: List[ItemFactory]) {    final private val factoryRegistry = mutable.Map[String, ItemFactory]()    factories.foreach(this.registerFactory)    def registerFactory(factory: ItemFactory): Unit = {     factory.registeredItems.foreach(alias =>       factoryRegistry.put(alias, factory))   }    def newInstance(itemName: String): Item = {     val factory = this.factoryRegistry.get(itemName)         .getOrElse(throw new Exception("Unable to find factory containing alias " + itemName))     factory.getItem(itemName)   }    def getRegisteredAliases: Set[String] = this.factoryRegistry.keySet } 

I would suggest that this is a clunky pattern in both Java and Scala though. It may be useful from time to time. Could you give an example of what you want to achieve with this? When do you need to use a different factory based on a runtime value?

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment