Software Architecture
Spring 2019

OOP: Encapsulation

Debrief: Code Reviews

Structure of the Day

Today is a talking theory day....

  • Design Principles

  • Encapsulation

  • Law of Demeter

  • Interfaces

  • Modules

https://slides.com/joelross/arch-sp19-encapsulation/live

Class

A Class organizes a set of data AND functionality associated with that data.

Classes act as blueprints/templates/recipes for Object instances.

A way of encoding components of the domain model

How do I organize my data and behavior into classes?

Design Principles

Design Principle

Model-in-Code Principle

"The source code should express the model"

Principle of
Separation of Concerns

"Organize software into separate components (pieces) that are as independent as possible."

Cohesion

"The degree to which elements of a module are related and belong together."

Coupling

"The degree to which software modules are inter-connected (and inter-dependent)."

Design Principle

Single Responsibility Principle

"A component should have a single responsibility"

CRC Cards

Keep these unique and not overlapping!

Class-Responsibility-Collaboration (CRC) Cards are a technique for discovering and organizing classes

Design Principle

Single Responsibility Principle

"A component should have one and only one reason to change"

"A component should have a single responsibility"

"A component should have a single responsibility"

Adapting to Change

  1. It should be easy to locate the place where a change is needed

  2. A change to a single artifact in a program should be made in just one place

  3. A change to an artifact should require no (or minimal) changes to the rest of the program

  4. An error introduced by the change should only affect the thing being changed

Design software so that it can be modified in the future!

Classes Provide
Abstraction

Abstraction is the process of generalization . It lets us work with higher-level representations rather than specific details.

Classes Provide
Encapsulation

Encapsulation

Encapsulation is the process of compart-mentalizing the elements of an abstraction. It hides the details in the abstraction.

Encapsulation Example

class Line {
   constructor(
      private startX: number,
      private startY: number,
      private endX: number,
      private endY: number
   ) {}

   move() { /*...*/ }

   stretch() { /*...*/ }
    
   changeColor() { /*...*/ }
}


//Use the class to work with the encapsulated data!
line: Line = new Line(1,2, 5,8);
line.move(); //move around
line.stretch(); //get longer

Information Hiding

Information Hiding is keeping encapsulated details inaccessible to other components.

class Location {

   //what attributes?
   private x: number; //x-coordinate
   private y: number; //y-coordinate

   private latitude: number;
   private longitude: number;

   private address: string = "1234 Main Street";

   private place: string = "Alice's Restaurant";


   //methods
   distanceFrom(other:Location): number { /*...*/ }

   areWeThereYet(): boolean { /*..*/ }
}

Information Hiding

The specific properties (data and functions) that an abstraction (class) makes available for interaction. The interface available to the public.

Public Interface

Access Modifiers

In order to keep information hidden (from the TypeScript compiler), we use access modifiers to encapsulated properties.

class Person {
   
   //private variables are only accessible from inside the class
   private password: string;

   //public variables are accessible anywhere (default access)
   public name: string;

   //readonly vars are public, but can't be changed (like const)
   readonly species: string = "Human"


   //Use functions to provide (controlled) access to private fields
   getPassword(): string { return this.password; }

   setPassWord(newPass: string) { this.password = newPass; }
}

Encapsulation Example

class Line {
  private startX:number;
  private startY:number;
  private endX:number;
  private endY:number;
  private length:number;

  constructor(x1:number, y1:number, x2:number, y2:number) {
    this.startX = x1; //etc
  }

  setStart(p:Point):void {
    this.startX = p.getX(); 
    this.startY = p.getY();
    this.length = this.calculateLength();
  }

  getEnd():Point { 
    return new Point(this.endX, this.endY); 
  }

  //...
}

Example from Pragmatic Programmer

Encapsulation Example

class Line {
  private start:Point;
  private end:Point;
  private length:number;



  constructor(x1:number, y1:number, x2:number, y2:number) {
    this.start = new Point(x1, y1); //etc
  }

  setStart(p:Point):void {
    this.start = p;
    this.length = this.calculateLength();
  }


  getEnd():Point { 
    return this.end; 
  }
  //...
}

Example from Pragmatic Programmer

public interface is the same--no change needed!

duplicate information

Encapsulation Example

class Line {
  private start:Point;
  private end:Point;

  constructor(x1:number, y1:number, x2:number, y2:number) {
    this.start = new Point(x1, y1); //etc
  }

  setStart(p:Point):void {
    this.start = p;
  }


  getEnd():Point { 
    return this.end; 
  }

  getLength():number {
    return this.calculateLength();
  }

}

Example from Pragmatic Programmer

Encapsulation enforces Abstraction

Design Principle

Encapsulate what varies

"Hide code that changes a lot behind a public interface that doesn't."

Design Principle

Principle of Least Knowledge

(Law of Demeter)

"A component should not know the internal details of another component"

"Talk only to your immediate friends,
not to friends of friends."

An Example: Payment

class Customer { //responsible for buying
  private name: string;
  private wallet: Wallet;

  getName(): string { return this.name; }
  getWallet(): Wallet { return this.wallet; }
}

class Wallet { //responsible for tracking money
  private value: number;

  getTotalMoney() :number { return this.value; }

  subtractMoney(debit: number) { this.value -= debit; }
}
class Merchant { //responsible for selling
  sellTo(myCustomer: Customer) {
    let cost = 5.00;
    let theWallet: Wallet = myCustomer.getWallet();
    if(theWallet.getTotalMoney() >= cost){
      theWallet.subtractMoney(cost);
      this.revenue += cost;
    } 
    else { /* do not sell */ } 
  }
}

An Example: Payment

class Customer { //responsible for buying
  private wallet: Wallet;

  getPayment(bill: number): number { 
    if(this.wallet.getTotalMoney() >= bill) {
      this.wallet.subtractMoney(bill);
      return bill;
    } 
    else { return 0; } //treat as error code ?
  }
}

class Wallet { //responsible for tracking money
  private value: number;
  getTotalMoney(): number { return this.value; }
  subtractMoney(debit: number) { this.value -= debit; }
}
class Merchant { //responsible for selling
  sellTo(myCustomer: Customer) {
    let cost = 5.00;
    let paidAmount = myCustomer.getPayment(cost);
    if(paidAmount > 0) {
      this.revenue += paidAmount;
    }
    else { /* do not sell */ } 
  }
}

Law of Demeter (Methods)

A method of an object should only call methods on:

   (a) the object itself

   (b) the method's parameters

   (c) any objects the method instantiates

   (d) any direct component objects

class Dog {
  private mFlea: Flea; //instance variable
  bark() { } //member function

  //Inside this method we can call functions on:
  fetch(aBall: Ball) {
    this.bark();     //1. the object itself

    aBall.squeeze(); //2. method parameters

    let wag = new TailWag();
    wag.show();      //3. instantiated objects

    mFlea.scratch(); //4. direct component objects
  }
}

Law of Demeter (Methods)

Law of Demeter Practice

exercise from Pragmatic Programmer

Does this code obey the Law of Demeter?

function showBalance(account: BankAccount):void { 
   let amount: Money = account.getBalance(); 
   console.log(amount.printFormat());
}
function processTransaction(account: BankAccount, value: number) { 
   Money amount = new Money();
   amount.setValue(123.45); 
   account.setBalance(amount);
   let who: Person = account.getOwner(); 
   console.log("Updated account for " + who.getName());
}
class Colada { 
  private blender: Blender;
  private stuff: IngredientList;
  
  constructor() {
    this.blender = new Blender();
    this.stuff = new IngredientList();
  }

  mix() {
    this.blender.addIngredients(this.stuff.getIngredients());
    this.blender.blend();
  }
}

Design Principles

  • Single Responsibility Principle

    • This leads to good cohesion

       
  • Principle of Least Knowledge (Law of Demeter)

    • This leads to good coupling

Class Relationships

Classes have different relationships:

  • Dependency: class A uses a class B


     

  • Composition: class A has a class B


     

  • Realization: class A is a class B

Types

A value's type indicates what that value is.

// age IS A number
let age: number = 23

// firstName IS A string
let firstName: string = "Ada"
//an interface for a `Person` type
interface Person {
  firstName: string,
  age: number
}

//ada IS A Person
let ada: Person = {firstName: "Ada", age: 33};

We use interfaces to specify custom types

The specific properties (data and functions) that an abstraction makes available for interaction.

Defines an explicit annotation (contract)

 

 

Programming Interfaces

TypeScript lets us make provided properties explicit, and will warn us if the contract is violated.

interface Computer {
  add(x: number, y: number): number; //method signature
  print(text: string): void;          //no body
}

let hal: Computer = { /*...*/ };

hal.add(2, 3); //no problem!

hal.openThePodBayDoors(); //Error!

Programming Interfaces

Classes Specify Interfaces

An object's class specifies its type, and thus its interface (an is a relationship)!

class Dog {
  constructor(private name: string) {}

  bark() { /*...*/ }
  beg() { /*...*/ }
}

//fido IS A Dog
let fido: Dog = new Dog("Fido");

fido.bark();
fido.beg();
fido.rollOver(); //Error! Not in the interface!

Defining a class also defines an interface!

Polymorphism

An object can simultaneously have multiple different types (from the Greek: many forms).

interface VisaAcceptor {
   payWithVisa(cardNum: number): Receipt;
}

//explicit: elements of this class fulfill the interface
//a CoffeeShop IS A VisaAcceptor
class CoffeeShop implements VisaAcceptor {
   payWithVisa(cardNum: number): Receipt { /* ... */ }
}

//object IS A CoffeeShop
let shop: CoffeeShop = new CoffeeShop();

//object also IS A VisaAcceptor
let payee: VisaAcceptor = shop; //assign same object

(The arrow points at the interface)

<<interface>>
Barker

Dog
 

Realization (dotted line, hollow arrow) represents a "is a" relationship--a class fulfills a contract or interface

UML Class Relationships

Why is this useful?

Polymorphism

Interacting with classes in terms of a shared interface lets us work at a higher level of abstraction.

interface VisaAcceptor {
   //takes in the card number, returns Receipt object
   payWithVisa(cardNum: number): Receipt;
}

class CoffeeShop implements VisaAcceptor { /* ... */ }
class CharityDrive implements VisaAcceptor { /* ... */ }

let shop: CoffeeShop      = new CoffeeShop();
let charity: CharityDrive = new CharityDrive();

shop.payWithVisa();
charity.payWithVisa();
interface VisaAcceptor {
   //takes in the card number, returns Receipt object
   payWithVisa(cardNum: number): Receipt;
}

class CoffeeShop implements VisaAcceptor { /* ... */ }
class CharityDrive implements VisaAcceptor { /* ... */ }

let shop: VisaAcceptor    = new CoffeeShop();
let charity: VisaAcceptor = new CharityDrive();

shop.payWithVisa();
charity.payWithVisa();

VisaAcceptor box contains
Shop object

interface VisaAcceptor {
   //takes in the card number, returns Receipt object
   payWithVisa(cardNum: number): Receipt;
}

class CoffeeShop implements VisaAcceptor { /* ... */ }
class CharityDrive implements VisaAcceptor { /* ... */ }

let shop: VisaAcceptor    = new CoffeeShop();
let charity: VisaAcceptor = new CharityDrive();

//put them in an array together (same type!)
let placesToPay: VisaAcceptor[] = [shop, charity, ...];

//Abstract away their differences and loop through them all!
placesToPay.forEach((place: VisaAcceptor) => {
  place.payWithVisa();
}

Variables vs. Values

An object can be stored in a variable for any type it is, but the value still retains it's original type(s) and code.

interface Barker {
  bark(): void;
}

class Dog implements Barker {
  bark() {
    console.log("Bark!");
  }
  
  wagTail() {
    console.log("Wag tail!");
  }
}

let animal: Barker = new Dog();
animal.bark(); //works!
animal.wagTail(); //TS compiler error, but will run
                  //Will not compile in Java

//explicitly assert `animal` is a `Dog` (typecast)
(animal as Dog).wagTail(); //compiles

Interface Inference

Because JavaScript is dynamically typed, any class that fulfills an interface is treated as implementing that interface.

Interfaces define a set of "minimal" properties

interface Barker {
  bark(): void;
}

class Dog implements Barker {
  bark() {
    console.log("Bark!");
  }
}

//Dog fulfills the interface, 
//so IS a Barker!
let animal: Barker = new Dog();
animal.bark(); //works!
interface Barker {
  bark(): void;
}

class Dog {
  bark() {
    console.log("Bark!");
  }
}

//Dog fulfills the interface, 
//so IS a Barker!
let animal: Barker = new Dog();
animal.bark(); //works!
interface Barker {
  bark(): void;
}

class Dog {
  bark() {
    console.log("Bark!");
  }
}

//Dog fulfills the interface, 
//so IS a Barker!
let animal: Barker = new Dog();
animal.bark(); //works!

//does not apply to object literals (excess property checking)
let pet: Barker = { bark:() => {}, yip:() => {} } //Error

Duck Typing

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.

Interface Practice!

Define a class HourlyEmployee who has a name, an hourly wage, and a number of hours worked this week.
Also define a class SalariedEmployee who has a name and an annual (52-week) salary. Both classes should include methods that return their pay for that week.

Define an interface that both of these classes fulfill.

Use that interface to create an array that includes two HourlyEmployee objects and two SalariedEmployee objects. Use a loop to print out the salaries of all of the employees.

How do interfaces help with abstraction?


How do interfaces help with encapsulation?

Design Principle

Program to interfaces, not to implementations

Modules

In TypeScript (as well as in Node and ES6), each file is treated as a separate module of code.

 

Values (including functions) are only available within their module, but can be explicitly exported (shared) and imported (loaded)

TypeScript Modules

Modules provide additional abstraction and encapsulation!

Importing Modules

It is possible to import external libraries and make them available in your script.

CommonJS (used by Node.js)

ES 6 Modules (used by Browsers and TypeScript )

const util = require('util');  //can only import a specific value
import { Random, Widget } from 'util'; //import specific values
import random from 'util';     //import "default" value
import * as util from 'util';  //import all values into single obj
                               //(would use `util.Random()`)

Exporting Variables

To make a file into a module, export variables, functions, and classes to be available for import.

/*** my-module.js ***/
export function foo() { return 'foo'; } //named export

function bar() { return 'bar'; }
export bar; //export previously defined variable

//will not be available (a "private" function)
function baz() { return 'baz'; }
/*** index.js ***/
import {foo, barFunction} from './my-module'; //import multiple
foo() //=> 'foo'
barFunction() //=> 'bar'

//import everything that was exported
//loads as a single object with values as properties
import * as theModule from './my-module'; //import everything 
                                          
theModule.foo(); //=> 'foo'
theModule.barFunction(); //=> 'bar'
theModule.baz(); //Error [private function]

encapsulation!

Default Exports

Each module can have a single default export, which provides a shortcut when importing.

/*** my-module.js ***/
export default function sayHello() {
    return 'Hello world!';
}
/*** index.js ***/

//default import can assign own alias without needing name!
import greet from './my-module';

greet(); //=> "Hello world!"

Align Modules and Components

Make the modules (parts of the code: files, classes, etc) correspond to components (parts of the model/system) 

  • Organize module files by component
     
  • Put related code in related modules (cohesion!)

Packages in UML

It is also possible to indicate modules (called packages) in UML diagrams, drawn as a "folder" containing the relevant classes.

Summary

New Design Principles

  • Single Responsibility Principle

  • Encapsulate what varies

  • Principle of Least Knowledge (Law of Demeter)

  • Program to interfaces, not to implementations

ACTION ITEMS!

For Next Time

  1. Start on Homework 2 (Due week from Monday)

    • Ask for help if you have questions or get stuck!
  2. Reading: TBD

    • ​Will send out an email once I've decided on a reading (if any)