Represent a person who has different roles for different offices

People have different types of roles depending on the office they are working at. For example, a person has two roles (cashier,cleaner) at OfficeMax and five roles(manager,security,etc) at OfficeLess. Typescript is used.

interface Person {
name : string,
roles : OfficeRoles
}

interface OfficeRoles {
roles : OfficeRole[],
office : Office
}

interface OfficeRole {
getType() : RoleType
}

interface Office {
name : string
}

enum RoleType {
CLERK,CUSTODIAN,SECURITY;
}


The following Person object would represent John, who is a Clerk and Security at OfficeMax:

let office : Office = { name : "OfficeMax" };
let roles : Role[] = [ new ClerkRole( "John" ) , new SecurityRole(1) ];
let person : Person = { name : "John" , roles : roles };

class ClerkRole implements Role {
private name : string;
constructor( name : string ) {
this.name = name;
}
getType() { return RoleType.CLERK; }
}

class SecurityRole implements Role {
private id : number;
constructor( id : number ) {
this.id = id;
}
getType() { return RoleType.SECURITY; }
}


Is the above a good representation of the requirements? Specifically, I had trouble with the OfficeRole interface as the different office roles do not share any commonality.

2 Answers

Here's my comments on your code.

interface Person {
name : string,
/**
* Because you can have an employee working at multiple offices, they cannot have only one OfficeRoles object.
* The roles relationship should reflect the potential for multiple offices.
*/
roles : OfficeRoles
}

interface OfficeRoles {
roles : OfficeRole[]
office : Office
}

interface OfficeRole {
/**
* This doesn't need to be a function, you could implement a readonly property in the class that's assigned
* when the OfficeRole is created.
*/
getType() : RoleType
}

/**
* The Office object does not include the roles it has, or the employees who currently hold those roles.
*/
interface Office {
name : string
}

/**
* I'd recommend against going with an Enum if the type of roles could be office specific.
*/
enum RoleType {
CLERK,CUSTODIAN,SECURITY
}

let office : Office = { name : "OfficeMax" };
// Does not compile, there is no Role class or interface.
let roles : Role[] = [ new ClerkRole( "John" ) , new SecurityRole(1) ];
let person : Person = { name : "John" , roles : roles };

/**
* No Role interface exists.
* Also, you shouldn't subclass OfficeRole just to provide a different value for {@link getType}, that can be done
* in a constructor.
*/
class ClerkRole implements Role {
private name : string;
constructor( name : string ) {
this.name = name;
}
getType() { return RoleType.CLERK; }
}

class SecurityRole implements Role {
private id : number;
constructor( id : number ) {
this.id = id;
}
getType() { return RoleType.SECURITY; }
}


Here's the direction I'd take it in. This does not include any methods you'd normally have to manage the relationships between employees and roles, nor does it include the classes (they're straightforward). It also doesn't include any methods you'd need to maintain the integrity of your object graph.

/**
* Represents an employee.
* An employee can work at different offices and does not have a relationship with an office beyond having a
* role there, therefore office is not modeled on the employee directly.
*/
interface IEmployee {
name : string
roleAssignments: IAssignedRole[];
}

/**
* An employee can work at multiple offices with different roles, which means a N:N relationship between
* {@link IEmployee} and {@link IRole}.
*/
interface IAssignedRole {
employee: IEmployee;
role: IRole;
}

/**
* Assumes there can be any number of roles for a given office. Each role needs a name and exists for only one office.
*/
interface IRole {
name: string;
office: IOffice;
}

/**
* Represents an office.
*/
interface IOffice {
name: string
/**
* Defines roles permitted for this office.
*/
roles : IRole[];
/**
* Captures the roles assignments for this office.
*/
roleAssignments : IAssignedRole[];
/**
* Provides list of employees by iterating through the role assignments.
*/
readonly employees: ReadonlyArray<IEmployee>;
}


Let me know if you have any questions :)

let office : Office = { name : "OfficeMax" };
let roles : Role[] = [ new ClerkRole( "John" ) , new SecurityRole(1) ];
let person : Person = { name : "John" , roles : roles };


This snippet contains "John" twice, which I think is strange. You already have the person's name stored in the person, so why does a ClerkRole have a name? The security role having an id is more plausible, but I don't know what you're going to use it for. There's no mention of it in the requirements, so I don't know why you put it there.

• The "John" contained in ClerkRole is simply used as a dummy parameter to show that different roles can have different parameters. Sorry I did not point that out. – klangnomad Feb 8 '17 at 2:24