3. Case Study 1: Eliminating a Class Hierarchy

Simple class hierarchies can be eliminated by applying the Class Hierarchy Merge design pattern. The starting point for our case study is the simple class hierarchy shown in the information design model of Figure 15.1 above, representing a disjoint (but incomplete) segmentation of Book into Textbook and Biography. This model is first simplified by applying the Class Hierarchy Merge design pattern, resultng in the model shown in Figure 15.5.

Figure 15.5.  The simplified class model obtained by applying the Class Hierarchy Merge design pattern


3.1. Encode the Factory Book

Create a factory frame for Book in file js/modelsPackage/BooksModel.js, which also inject $http service from Angular:

plModels.factory( 'Book', [ '$http', function ( $http) {
  ...
  return Book;
}]);

All following code will be filled into this factory.

3.1.1. Encode the enumeration type BookCategories

Encode the enumeration type, which contains an enumeration literal for each segment subclass, into a corresponding constant array, then create a function to get the list of category:

const BookCategories = [
  "Textbook",
  "Biography"
];

Book.getAllCategories = function () {
  return BookCategories;
};

As a post scriptum, by using Angular ngOptions in views, a value for each item in this array will be set automatically. For instance, for the first enumeration literal "Textbook" we get a value "1".

3.1.2. Encode the model class Book

We encode the model class Book in the form of a constructor function where the category attribute as well as the segment attributes subjectArea and about are optional:

function Book () {
  this.isbn = "";     // String
  this.title = "";    // String
  this.year = 0;      // Number (PositiveInteger)
  /* optional properties
    this.category       // String {from BookCategories}
    this.subjectArea    // String
    this.about          // String
  */
}

The main job of our Book factory is to interact with the RESTful service from Parse cloud storage and all the book records will be saved in one class/table Book in Parse.com:

var urlBook = 'https://api.parse.com/1/classes/Book/';

Book.loadAll = function () {
  return $http( {
    method: 'GET', url: urlBook
  });
};

Book.add = function ( slots) {
  return $http( {
    method: 'POST', url: urlBook,
    data: slots
  });
};

Book.update = function ( slots) {
  return $http( {
    method: 'PUT', url: urlBook + slots.objectId,
    data: slots
  });
};

Book.destroy = function ( slots) {
  return $http( {
    method: 'DELETE', url: urlBook + slots.objectId
  });
};

These functions return a promise, which will be resolved by the controller using the success() and error() functions.

3.2. Write the View and Controller Code

The user interface (UI) consists of a start page that allows navigating to the data management pages. Such a data management page contains 5 sections: manage books, list books, create book, update book and delete book.

3.2.1. Adding a segment information column in List all books Use Case

Exept the nomal columns "ISBN", "Title" and "Year", we add a "Special type" column to the display table of the "List all books" use case in showAllBooks.html. By using ngSwitch Angular can check the value of book.category, a book without a special category will have an empty table cell in this column, while for all other books their category will be shown in this column, along with other segment-specific information:

<div ng-init="getAllBooks()">
<h1>List all books</h1>
<table>
  <thead><tr>
    <th>ISBN</th><th>Title</th>
    <th>Year</th><th>Special Type</th>
  </tr></thead>
  <tbody><tr ng-repeat="book in books">
    <td>{{book.isbn}}</td>
    <td>{{book.title}}</td>
    <td>{{book.year}}</td>
    <td>
      <span ng-switch="book.category">
        <span ng-switch-when="Textbook">
          {{book.subjectArea}} textbook
        </span>
        <span ng-switch-when="Biography">
          Biography about {{book.about}}
        </span>
        <span ng-switch-default="ng-switch-default"></span>
      </span>
    </td>
  </tr></tbody>
</table>
</div>

By loading this HTML file, the controller BooksController will also be invoked at the same time, which is defined in the js/controllersPackage/BooksController.js file:

plControllers.controller('BooksController',
['$scope', 'Book', function ( $scope, Book) {
  ...
  $scope.getAllBooks = function () {
    Book.loadAll()
      .success( function ( data) {
        $scope.books = data.results;
      })
      .error( function ( data) {
        console.log( data);
      });
  };
  ...
}]);

The BooksController now relies on the injected Book, which is defined by the factory in BooksModel.js. Once the request is succeed, the $scope.book will be updated by resolved data, otherwise show error message.

3.2.2. Adding a "Special type" select control in "Create book" and "Update book"

In both use cases, we need to allow selecting a special category of book ('Textbook' or 'Biography') with the help of several Angular directives: ngOptions, ngDisabled and ngSwitch, as shown in the following HTML fragment:

<div ng-init="prepareBook()">
<form ng-submit="addBook()"> <!-- or "updateBook()" -->
  ...
  <div>
    <label>Special type
      <select name="subtype" ng-model="book.category"
              ng-options="bookCategory for
                          bookCategory in bookCategories"
              ng-disabled="isDisabled">
        <option value="">...</option>
      </select>
    </label>
  </div>
  <div ng-switch="book.category">
    <div ng-switch-when="Textbook">
      <label>Subject area
        <input type="text" name="subjectArea"
               ng-model="book.subjectArea" />
      </label>
    </div>
    <div ng-switch-when="Biography">
      <label>About
        <input type="text" name="about"
               ng-model="book.about" />
      </label>
    </div>
    <div ng-switch-default="ng-switch-default"></div>
  </div>
  ...
</form>
</div>

  • ngOptions: sets each item in an array of bookCategories as an option.

  • ngDisabled: as an attribute of select element. In "update book" use case its value isDisabled will be true when the category of a book has been defined, while in "create book" use case its value will be false.

  • ngSwitch: takes care of which part of HTML should be shown. It's dynamically depending on the value of book.category.

The function prepareBook() in BooksController.js will do some woks as following:

$scope.prepareBook = function () {
  $scope.bookCategories = Book.getAllCategories();
  $scope.isDisabled = false;
  if ( $scope.book) {  // "update book" use case
    if ( $scope.book.category) {
      // if the category is defined, 
      // set the select element to be disabled
      $scope.isDisabled = true;
      var idx = 0;
      while ( !angular.equals( $scope.bookCategories[idx],
                               $scope.book.category)) {
        idx++;
      }
      $scope.book.category = $scope.bookCategories[idx];
    }
  } else {  // "create book" use case
    $scope.book = new Book();
  }
};

  • get all book's categories according to the book factory.

  • in "update book" use case, check whether a category of this book has been defined or not. Only when it is defined, let this category option to be selected and set the select element to be disabled.

  • in "create book" use case, construct a new book object.

For handling the create/update-event, we also invoke the functions from book factory:

// submit-function in "create book" use case
$scope.addBook = function () {
  Book.add( $scope.book)
    .error( function ( data) { console.log( data);});
};

// submit-function in "update book" use case
$scope.updateBook = function () {
  Book.update( $scope.book)
    .error( function ( data) { console.log( data);});
};