When implementing some custom directives in Spray to handle pagination and sorting, it occured to me that the handling of the pagination and sorting might be applied nicely to a Chain of Responsibility design pattern as this would allow me to encapsulate different operations and allow them to be used together or in isolation, or with other, newer operations.

So I put together a prototype of the code—without the actual Spray cruft. The Spray code handles extracting the URL parameters and results in two case classes; Sort and Paginate which holds these parameters. This code is then the next stage; applying those parameters to a collection. This code has the obvious caveat that the collection is held in memory. Still, for a nominal number of items and when you need to separate the pagination and sorting; it’s a clean and elegant solution.

I’m using an abstract class to represent my parent command as that allows me to handle the successor matching in one place, thus making the code DRY. However, the calls to the superclass handleData could be refactored out to make it a little cleaner to implement a command.

Chain of Responsibility Page and Sorting UML Diagram

The code:

/**
 * To run, execute: Main.main(Array())
 * 
 * Example implementation of the Chain of Responsibility design pattern to 
 * handle paging and sorting. Using this design pattern you can separate out 
 * the responsibilities of pagination and sorting.
 */
case class Sort(field: Option[String], isAscending: Boolean = true)
case class Paginate (offset: Int, limit: Int)
case class Item (key: Int, value: String)

/**
  * Represents a Chain-of-Responsibility 'command'
  */
abstract class Command {
  /**
   * The next command in the chain
   */
  val successor: Option[Command]

  /**
   * Receives the data from the previous command in the chain and processes 
   * it according to its own implementation, then passes that data to the next 
   * command in the chain, or returns the data if no further successors.
   */
  def handleData(data: List[Item]): List[Item] = {
    successor match {
      case Some(x: Command) => x.handleData(data)
      case None => data
    }
  }
}

/**
  * Represents a sorting command and encapsulates the parameters for sorting 
  * and the sorting algorithm itself.
  *
  * @param sort The case class which holds the sorting parameters
  * @param successor The next in the chain
  */
class SortCommand(val sort: Sort, val successor: Option[Command]) 
    extends Command {

 /**
  * Sorts data by 'key' or 'value' either ascending or descending order
  *
  * @param data
  * @return
  */
  override def handleData(data: List[Item]): List[Item] = {
    val sorted = sort.field.getOrElse("none") match {
      case "key" => data.sortWith(_.key > _.key)
      case "value" => data.sortWith(_.value > _.value)
      case "none" => data.sortWith(_.key > _.key)
    }

    val ordered = sort.isAscending match {
      case true => sorted.reverse
      case false => sorted
    }

    super.handleData(ordered)
  }
}

/**
  * Represents a pagination command and encapsulates the parameters for 
  * pagination and the pagination algorithm itself.
  *
  * @param page The case class which holds the pagination parameters
  * @param successor The next in the chain
  */
class PaginateCommand(val page: Paginate, val successor: Option[Command]) 
    extends Command {

  /**
   * Return a page the size of the 'limit' from the 'offset' position, 
   * in the enclosed Paginate params.
   */
  override def handleData(data: List[Item]): List[Item] = {
    val paged = data
      .drop(page.offset)
      .dropRight(data.size - (page.offset + page.limit))

    super.handleData(paged)
  }
}

/**
 * Main entry point
 */
object Main {
  def main(args: Array[String]): Unit = {

    // the parameters to sort and page; will return items 6, 7, and 8 
    // sorted by key, descending
    val page = Paginate(offset = 5, limit = 3)
    val sort = Sort(field = Some("key"))

    // the commands are defined in reverse order as we want the 
    // sorting to take place first
    val pagination = new PaginateCommand(page, None)
    val sorting = new SortCommand(sort, Some(pagination))

    // some data to sort and page
    val data = List(
      Item(3, "foo"),
      Item(8, "bar"),
      Item(10, "baz"),
      Item(2, "qux"),
      Item(1, "quux"),
      Item(11, "corge"),
      Item(4, "grault"),
      Item(6, "garply"),
      Item(13, "waldo"),
      Item(7, "fred"),
      Item(5, "plugh"),
      Item(9, "xyzzy"),
      Item(12, "thud")
    )

    val processed = sorting.handleData(data)
    println("page: " + processed)
  }
}