I have abstract Class AbstractEmployee and two concrete sub-classes FixedSalaryEmployee and PerHourSalaryEmployee which inherit from AbstractEmployee and override its abstract getSalary method with correct implementation for given employye type.
First implementation FixedSalaryEmployee - Employee with fixed salary. Where average monthly salary is equal to employee the value of salary in JSON data.
Second implementation PerHourSalaryEmployee- Employee with per-hour salary. Where hour rate is equal to the value of salary in JSON data, working day has 8 hours and month has 20.88 working days in average.
And create Collection class which is able to work with employees of different types.
The main question is how to сreate EmployeeCollection class which represents collection of Employees:
Constructor should accept data from JSON file and create instances of corresponding classes based on type field.
id should be generated in a format id<number> e.g. (id0, id1 etc. for each item in collection)
Items in collection should be sorted by the next rules:
Sort all employees in descending order of average monthly salary.
If average monthly salary of employees is equal use employee name instead.
Need use ES5!
//AbstractEmployee.js
var AbstractEmployee = function(id, name, salary) {
if (this.constructor === AbstractEmployee) {
throw new Error("Can't instantiate abstract class!");
}
this.id = id;
this.name = name;
this.salary = salary;
if(typeof(object.id) !== 'string' || typeof(object.name) !== 'string' || typeof(object.salary) !== 'number'){
throw new Error("Wrong param passed!");
}
};
AbstractEmployee.prototype.getSalary = function() {
throw new Error('Method getSalary() must be implemented');
}
//PerHourSalaryEmployee.js
var AbstractEmployee = require('./AbstractEmployee.js')
var PerHourSalaryEmployee = function(id, name, salary) {
AbstractEmployee.apply(this, arguments)
this.id = 'id' + id;
this.name = name;
this.salary = salary * 20.88 * 8;
};
PerHourSalaryEmployee.prototype.getSalary = function() {
return this.salary;
}
module.exports = PerHourSalaryEmployee
//FixedSalaryEmployee.js
var AbstractEmployee = require('./AbstractEmployee.js')
var FixedSalaryEmployee = function(id, name, salary) {
AbstractEmployee.apply(this, arguments);
this.id = 'id' + id;
this.name = name;
this.salary = salary;
};
FixedSalaryEmployee.prototype.getSalary = function() {
return this.salary;
}
module.exports = FixedSalaryEmployee
employees-collection.json
[{
"type": "per-hour",
"salary": 10,
"name": "Anna"
},
{
"type": "per-hour",
"salary": 8,
"name": "Bob"
},
{
"type": "fixed",
"salary": 8000,
"name": "Dany"
},
{
"type": "fixed",
"salary": 8000,
"name": "Clara"
},
{
"type": "fixed",
"salary": 1000,
"name": "Egor"
}]
As was already commented, a half baked AbstractEmployee function used merely as a function based mixin does not make real sense.
A pure old school (it was asked for / limited to ES5-syntax) inheritance approach is much better suitable.
And one actually does not even need a BaseEmployee constructor since the features of a FixedSalaryEmployee type are totally equal to the ones of a BaseEmployee type, and the PerHourSalaryEmployee type differs only in the internal/initial computation of its salary property(, but one never knows what the future still might bring) ...
function orderBySalaryDescendingAndNameAscending(a, b) {
return (b.salary - a.salary) || a.name.localeCompare(b.name);
}
// employee factory.
function createTypeDependedEmployeeVariant(rawData, idx) {
const { type, name, salary } = rawData;
const employee = (type === 'per-hour')
? new PerHourSalaryEmployee(String(idx), name, salary)
: new FixedSalaryEmployee(String(idx), name, salary)
// employee.type = type;
return employee;
}
// employee list factory.
function createOrderedListOfVariousEmployeeInstances(arr) {
return arr
.map(createTypeDependedEmployeeVariant)
.sort(orderBySalaryDescendingAndNameAscending);
}
const jsonDataList = [{
"type": "per-hour",
"salary": 10,
"name": "Anna"
}, {
"type": "per-hour",
"salary": 8,
"name": "Bob"
}, {
"type": "fixed",
"salary": 8000,
"name": "Dany"
}, {
"type": "fixed",
"salary": 8000,
"name": "Clara"
}, {
"type": "fixed",
"salary": 1000,
"name": "Egor"
}];
console.log(
createOrderedListOfVariousEmployeeInstances(jsonDataList)
.map(({ id, name, salary }) => ({ id, name, salary }))
);
console.log(
createOrderedListOfVariousEmployeeInstances(jsonDataList)
.map(item => item.getSalary())
);
console.log(
createOrderedListOfVariousEmployeeInstances(jsonDataList)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
<script>
function BaseEmployee(id, name, salary) {
if (
(typeof id !== 'string') ||
(typeof name !== 'string') ||
(typeof salary !== 'number') ||
!Number.isFinite(salary)
) {
throw new TypeError('Wrong parameter(s) passed!');
}
this.id = 'id' + id;
this.name = name;
this.salary = salary;
}
BaseEmployee.prototype.getSalary = function() {
return this.salary;
}
</script>
<script>
function PerHourSalaryEmployee (id, name, salary) {
// super call.
BaseEmployee.apply(this, arguments);
this.salary = (salary * 20.88 * 8);
};
// extend superclass.
PerHourSalaryEmployee.prototype = Object.create(BaseEmployee.prototype);
// prevent super constructor from being the sub-classed constructor.
PerHourSalaryEmployee.prototype.constructor = PerHourSalaryEmployee;
</script>
<script>
function FixedSalaryEmployee(id, name, salary) {
// super call.
BaseEmployee.apply(this, arguments);
};
// extend superclass.
FixedSalaryEmployee.prototype = Object.create(BaseEmployee.prototype);
// prevent super constructor from being the sub-classed constructor.
FixedSalaryEmployee.prototype.constructor = FixedSalaryEmployee;
</script>
The most optimized Employee-class code-base would/could look like the below provided code. There is no difference in its usage with the above provided factories in comparison to the above code base which features the additional BaseEmployee ...
function checkEmployeeArguments(id, name, salary) {
if (
(typeof id !== 'string') ||
(typeof name !== 'string') ||
(typeof salary !== 'number') ||
!Number.isFinite(salary)
) {
throw new TypeError('Wrong parameter(s) passed!');
}
}
function FixedSalaryEmployee(id, name, salary) {
checkEmployeeArguments(id, name, salary);
this.id = 'id' + id;
this.name = name;
this.salary = salary;
}
FixedSalaryEmployee.prototype.getSalary = function() {
return this.salary;
}
function PerHourSalaryEmployee (id, name, salary) {
// super call.
FixedSalaryEmployee.apply(this, arguments);
this.salary = (salary * 20.88 * 8);
};
// extend superclass.
PerHourSalaryEmployee.prototype = Object.create(FixedSalaryEmployee.prototype);
// prevent super constructor from being the sub-classed prototype constructor.
PerHourSalaryEmployee.prototype.constructor = PerHourSalaryEmployee;
Related
const dogFactory = (name, breed, weight) => {
return {
_name: name,
_breed: breed,
_weight: weight,
get name() {
return this._name;
},
set name(newName) {
this._name = newName;
},
get breed() {
return this._breed;
},
set breed(newBreed) {
this._breed = newBreed;
},
get weight() {
return this._weight;
},
set weight(newWeight) {
this._weight = newWeight;
}
}
}
The other setters work perfectly fine:
dogFactory.breed = 'Pitbull';
console.log(dogFactory.breed) // returns Pitbull
dogFactory.weight = 15;
console.log(dogFactory.weight) // returns 15
But the name setter instead, returns function's name which is 'dogFactory':
dogFactory.name = 'Roger';
console.log(dogFactory.name) // returns dogFactory
To modelize a family structure, I used the simple Person class, and I would be able to save a structure as text, and later go in the other way. But my class has different methods useful and indispensable to my code, I found the way for the first object, but how to continue in depth?
Depth here is 1, but it can go up to 6
let json = {"name":"SON","date":"2000-01-01T05:00:00.000Z","sex":"H",
"dad":{"name":"DAD","date":"2000-01-02T05:00:00.000Z","sex":"H","dad":null,"mom":null},
"mom":{"name":"MOM","date":"2000-01-03T05:00:00.000Z","sex":"F","dad":null,"mom":null}
};
class Person {
constructor(name, date, sexe) {
this.name = name;
this.date = date;
this.sexe = sexe;
this.dad = null;
this.mom = null;
}
doStuff(){
console.log(this.name);
}
}
let obj = Object.assign(new Person,json);
//OK
obj.doStuff();
//NOK 'obj.dad.doStuff is not a function' as 'dad' is not associated to Person
obj.dad.doStuff();
You can do it recursively, calling the same function that performs Object.assign on dad and mom if they are not null:
let json = {
"name": "SON", "date": "2000-01-01","sex": "H",
"dad": {
"name": "DAD","date": "2000-01-02","sex": "H", "dad": null,"mom": null
},
"mom": {
"name": "MOM","date": "2000-01-03","sex": "F","dad": null,
"mom" : { "name": "GRAMDA","date": "2000-01-02","sex": "F","dad": null,"mom": null }
}
};
class Person {
constructor(name, date, sexe) {
this.name = name;
this.date = date;
this.sexe = sexe;
this.dad = null;
this.mom = null;
}
doStuff() {
console.log(this.name);
}
}
function buildPersons(root) {
let obj = Object.assign(new Person, root);
if (obj.dad) obj.dad = buildPersons(obj.dad);
if (obj.mom) obj.mom = buildPersons(obj.mom);
return obj;
}
let obj = buildPersons(json);
obj.doStuff();
obj.dad.doStuff();
obj.mom.mom.doStuff();
i have defined an object containing an array of objects and functions and called the methods as below:
var Person = {
people: [{
name: "Max",
age: 41,
},
{
name: "John",
age: 25,
},
{
name: "Doe",
age: 67,
},
],
greeting: function() {
return "Hello, my name is" + " " + this.name;
}
};
function searchByName(namePerson) {
var result = Person.people.find(Obj => Obj.name === namePerson);
return result;
}
var max = searchByName('Max');
max.greeting();
Is something wrong with my function definition? On running it says "greeting" is not a function.
You could change your Person into an actual class that you could instantiate with new Person()
//make the person class
function Person ( person ) {
this.name = person.name;
this.age = person.age;
}
//add the greeting method to the person class
Person.prototype.greeting = function () {
return "Hello, my name is" + " " + this.name;
};
//build your people
var people = [
new Person( { name: "Max", age: 41 } ),
new Person( { name: "John", age: 25 } ),
new Person( { name: "Doe", age: 67 } )
];
function searchByName(namePerson) {
var result = people.find(person => person.name === namePerson);
return result;
}
var max = searchByName('Max');
console.log( max.greeting() );
Your code doesn't make much sense, your greeting function is on the outer Person object but you are using it as if it were a property of each person inside the array.
You need a constructor for persons so you can instantiate three Persons with a greeting method
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greeting = function() {
return "Hello, my name is " + this.name + " and I am " + this.age + " years old";
}
const people = [
new Person("Max", 41),
new Person("JOhn", 25),
new Person("Doe", 67),
];
function searchByName(namePerson) {
var result = people.find(obj => obj.name === namePerson);
return result;
}
var max = searchByName('Max');
console.log(max.greeting());
You're returning an object, which does not have a greeting function.
A potential different solution with a little bit of a different structure is this.
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.greeting = function() {
return "Hello, my name is " + this.name;
}
function People(personArr) {
this.people = personArr;
}
People.prototype.searchByName = function(name) {
return this.people.find( person => person.name === name);
}
var people = new People([new Person("Max", 41), new Person("John", 25), new Person("Doe", 67)]);
var max = people.searchByName("Max");
console.log(max.greeting());
Sample code how do I want the instance to work:
var brother = new Person("James");
console.log(brother.status);
// "James, current location: home, health condition: saber"
brother.status = {
location: "pub",
"health condition": "drunk as a skunk"
};
console.log(brother.status);
// "James, current location: pub, health condition: drunk as a skunk"
Question is how to program object Person, its prototype and how to use Object.defineProperty to achieve this (or similar) behavior the most elegant way.
I did some solution so far but I would like to not to explain it here to bother your mind with it.
The key answer should contain Object with methods and properties that are not copied for each instance and Object must be easy to inherit.
// Constructor
function Person(name){
// In prototype, Can be in herited
this._name = name;
this._status;
// Not available in prototype, hence cannot be inherited
age = 20;
function incAge(){
age++;
}
// Available in instances, Privilege.
this.getAge = function(){
incAge(); // Accessing private members
return age;
}
}
Person.prototype.constructor = Person;
Object.defineProperty(Person.prototype, "status", {
configurable : true, // If you want it to be changed in derived class
enumerable : false,
get : function(){
this._status = this._status || {location : "home", "health condition" : "saber"};
return this._name + ", current location: "+ this._status.location+", health condition: "+this._status["health condition"];
},
set : function(status){this._status = status}
});
//----DERIVED CLASS---
function SuperPerson(name){
Person.call(this, name);
}
SuperPerson.prototype = Object.create(Person.prototype);
SuperPerson.prototype.constructor = SuperPerson;
Object.defineProperty(Person.prototype, "status", {
enumerable : false,
get : function(){
this._status = this._status || {location : "BatCave", "health condition" : "I dont have parents to take care of me"};
return this._name + ", current location: "+ this._status.location+", health condition: "+this._status["health condition"];
}
});
This post beautifully explained the descriptors of the Object.defineProperty method, if you are interested.
You could define a common private defaultStatus for all people, and then for each person create a private status which inherits from defaultStatus. Then, expose a public status which, on setting, assigns the new data to the private status (I use ES6 Object.assign, can be polyfilled). On getting, it builds the desired string.
var Person = (function() {
var defaultStatus = {
location: 'home',
health: 'saber'
};
return function Person(name) {
var status = Object.create(defaultStatus);
Object.defineProperty(this, 'status', {
enumerable: true,
get: function() {
return [
name,
"current location: " + status.location,
"health condition: " + status.health
].join(', ');
},
set: Object.assign.bind(void 0, status)
});
};
})();
I am new to MongoDB and I have a mongodb document like this
{
"_id": 1,
"title": "abc123",
"author": {
"last": "zzz",
"first": "aaa",
"address": {
"street": "stvalue",
"city": "NewYork"
}
}
}
I am using MongoDB Java client and trying to convert the above document as
{
"_id": 1,
"title": "abc123",
"author_last": "zzz",
"author_first": "aaa",
"author_address_street": "stvalue",
"author_address_city": "New York"
}
Is this possible using MongoDB Java client using MongoDB query without modifying the result in Java.
Update
Since it is not possible to convert to required format in MongoQuery, I have to change my mind and add some Java code to get the required format.
It's not really a matter of which "client" or "driver library" you use, as much as the only way to do this is via the .aggregate() and $project to "alter the fields" in response. It is your most practical way of doing so:
db.collection.aggregate([
{ "$project": {
"title": 1,
"author_last": "$author.last",
"author_first": "$author.first",
"author_address_street": "$author.address.street",
"author_address_city": "$author.address.city"
}}
])
Very easy to translate into any language, but shows you the basic principles.
Very Java(esque) then the syntax becomes (in basic driver):
BasicDBObject project = new BasicDBObject("$project",
new BasicDBObject( "title", 1 )
.append( "author_last", "$author.last" )
.append( "author_first", "$author.first" )
.append( "author_address_street", "$author.address.street" ),
.append( "author_address_city", "$author.address.city" )
);
collection.aggregate(project);
If you cannot just specify the keys in this way then your only real option on the server is mapReduce. Much in the same way as above, it might be better to do this in the client response:
db.collection.mapReduce(
function() {
var item = {};
var id = this._id;
delete this._id;
var obj = this;
Object.keys(obj).forEach(function(key) {
if ( typeof(obj[key]) == "object" ) {
Object.keys(obj[key]).forEach(function(subKey) {
if ( typeof(obj[key][subkey] == "object" ) {
Object.keys(obj[key][subKey]).forEach(function(subSub) {
item[key + "_" + subKey + "_" + subSub] = obj[key][subKey][subSub];
});
} else {
item[key + "_" + subKey] = obj[key][subKey];
}
});
} else {
item[key] = obj[key];
}
});
emit( id, item );
},
function() {}, // no reduction required
{ "out": { "inline": 1 } }
)
I think that makes sense, but I'm just typing it so possible syntax problems, but the idea should be relevant. You might also need something more complex depending on your actual data.
Point is, that you need the JavaScript execution to "traverse" your document on the server to use this method.
Otherwise, just do the same in code since you are iterating a cursor anyway.
Here is an Java implementation which will convert all child objects into an HashMap
private List<Map<String,String>> mapReduce(AggregationOutput output){
List<Map<String,String>> out = new ArrayList<Map<String, String>>();
for (DBObject result : output.results()) {
Map<String,String> map = ConvertDBObjectToMap(result, "");
out.add(map);
}
return out;
}
private Map<String,String> ConvertDBObjectToMap(DBObject result, String parent)
{
String prefix = parent == "" ? "" : parent + "_";
Map<String,String> out = new HashMap<String, String>();
String[] keys = result.keySet().toArray(new String[result.keySet().size()]);
for(String key : keys)
{
Object obj = result.get(key);
if(obj instanceof BasicDBObject)
{
out.putAll(ConvertDBObjectToMap((BasicDBObject) obj, prefix + key));
}
else if(obj instanceof String)
{
out.put(prefix + key,(String)obj);
}
else if(obj instanceof Date){
out.put(prefix + key,obj.toString());
}
else{
out.put(prefix + key,obj.toString());
}
}
return out;
}