This commit is contained in:
fenris 2025-03-03 06:15:30 +00:00
commit 51431ec4cf
14 changed files with 268 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/temp/
/build/

6
source/base.ts Normal file
View file

@ -0,0 +1,6 @@
declare var process;
function show(message : string): void {
process.stdout.write("## " + message + "\n");
}

57
source/composition.ts Normal file
View file

@ -0,0 +1,57 @@
/**
* creates an object, which has all the methods of a class instance, but as
* plain functions (required for composition)
*/
function dullCopy<X>(instance: X): X {
const methodBlacklist: Set<string> = new Set<string>([
"__defineGetter__",
"__defineSetter__",
"__lookupGetter__",
"__lookupSetter__",
"__proto__",
"constructor",
"isPrototypeOf",
"hasOwnProperty",
"propertyIsEnumerable",
"valueOf",
"toString",
"toLocaleString",
]);
const result = {};
// attributes
{
for (const [name, value] of Object.entries(instance)) {
result[name] = value;
}
}
// methods
{
const methodNames: Array<string> = Object.getOwnPropertyNames(instance["__proto__"]);
for (const methodName of methodNames) {
if (! methodBlacklist.has(methodName)) {
result[methodName] = function (...methodArgs) {
return instance[methodName].apply(instance, methodArgs);
};
}
}
}
return (result as X);
}
/**
* creates a combination of two class instances, forming a new object with the
* methods of both operands (as plain functions)
*/
function compose<X, Y>(x: X, y: Y): (X & Y) {
const result = {};
for (const object of [x, y]) {
for (const [name, implementation] of Object.entries(dullCopy(object))) {
if (name in result) {
process.stderr.write("[warn] overwriting method '" + name + "'\n");
}
result[name] = implementation;
}
}
return (result as (X & Y));
}

View file

@ -0,0 +1,49 @@
class Bookkeeper implements Employee {
private identity: Identity;
private occupation: Occupation;
public constructor(name: string) {
this.identity = new Identity("bookkeeper", name);
this.occupation = new Calculating();
}
public introduce(): void {
this.identity.introduce();
}
public work(): void {
this.occupation.work();
}
}
function makeBookkeeper(name: string): Employee {
return compose(
new Identity("smartly composed bookkeeper", name),
new Calculating()
);
}

View file

@ -0,0 +1,9 @@
class Calculating implements Occupation {
public constructor() {
}
public work(): void {
show("*type* *type* *type*");
}
}

9
source/logic/cleaning.ts Normal file
View file

@ -0,0 +1,9 @@
class Cleaning implements Occupation {
public constructor() {
}
public work(): void {
show("*sweep* *sweep* *sweep*");
}
}

5
source/logic/employee.ts Normal file
View file

@ -0,0 +1,5 @@
interface Employee {
introduce(): void;
work(): void;
}

14
source/logic/identity.ts Normal file
View file

@ -0,0 +1,14 @@
class Identity {
private title: string;
private name: string;
public constructor(title: string, name: string) {
this.title = title;
this.name = name;
}
public introduce(): void {
show(`Hi! My name is ${this.name} and i'm the ${this.title} here.`);
}
}

49
source/logic/janitor.ts Normal file
View file

@ -0,0 +1,49 @@
class Janitor implements Employee {
private identity: Identity;
private occupations: Array<Occupation>;
public constructor(name: string) {
this.identity = new Identity("janitor", name);
this.occupations = [
new Repairing(),
new Cleaning(),
];
}
public introduce(): void {
this.identity.introduce();
}
public work(): void {
for (const occupation of this.occupations) {
occupation.work();
}
}
}
function makeJanitor(name: string): Employee {
return compose(
compose(
new Identity("smartly composed janitor", name),
new Repairing()
),
new Cleaning()
);
}

View file

@ -0,0 +1,4 @@
interface Occupation {
work(): void;
}

View file

@ -0,0 +1,9 @@
class Repairing implements Occupation {
public constructor() {
}
public work(): void {
show("*fix* *fix* *fix*");
}
}

10
source/main.ts Normal file
View file

@ -0,0 +1,10 @@
const employees: Array<Employee> = [
new Bookkeeper("Alice"),
new Janitor("Bob"),
];
for (const employee of employees) {
employee.introduce();
employee.work();
}

4
tools/build Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
make --file=tools/makefile

41
tools/makefile Normal file
View file

@ -0,0 +1,41 @@
## consts
dir_source := source
dir_temp := temp
dir_build := build
cmd_mkdir := mkdir --parents
cmd_tsc := tsc --lib es2020,dom
cmd_log := echo "--"
cmd_chmod := chmod
cmd_cat := cat
cmd_echo := echo
## rules
.PHONY: _default
_default: ${dir_build}/mixin
${dir_temp}/mixin.js: \
${dir_source}/base.ts \
${dir_source}/composition.ts \
${dir_source}/logic/identity.ts \
${dir_source}/logic/occupation.ts \
${dir_source}/logic/calculating.ts \
${dir_source}/logic/repairing.ts \
${dir_source}/logic/cleaning.ts \
${dir_source}/logic/employee.ts \
${dir_source}/logic/bookkeeper.ts \
${dir_source}/logic/janitor.ts \
${dir_source}/main.ts
@ ${cmd_log} "compiling …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_tsc} --outFile $@ $^
${dir_build}/mixin: ${dir_temp}/mixin.js
@ ${cmd_log} "finishing …"
@ ${cmd_mkdir} $(dir $@)
@ ${cmd_echo} "#!/usr/bin/env node\n" > $@
@ ${cmd_cat} $^ >> $@
@ ${cmd_chmod} +x $@