2

I am currently working on a project that generates videos from templates (a template being a collection of JSON files and assets).

It seems natural to have a Template class that contains all the info of how this specific template is structured (e.g. which parts are personaliseable, what assets are required etc...).

The issue I have been hung up on is how to model a job. A job takes a particular Template, allows assigning values to the personaliseable fields and renders a video using these values. To me, it makes sense that a Job would be an instance of an instance of Template.

A solution I am imagining is something like this (in pseudo-Typescript):

class Template {
    // elements in groups can be subclasses of the generic TemplateField - I'd like to preserve that and their grouping in the DynamicallyCreatedTemplateClass that is generated below
    templateFields : Group<TemplateField>[];

    constructor( sourcePath : string) {
        // build shape of templateFields from a file
    }

    // This should return a constructor for a dynamically created class that inherits from Template as well as a Job mixin but already has a concrete structure in this.templateFields
    // in general, the resulting class should resemble the Template instance this is called on (i.e. properties that have values on the instance should have the same values in every instance of the generated class)
    getInstanceClass() : () => DynamicallyCreatedTemplateClass { /* ??? */ }
}

const someTemplate = new Template('/path/to/some/file.json');

const someJob = new someTemplate.getInstanceClass();

someJob.templateFields[0].get(0).value = 'something';
if(
    someJob.templateFields[0].get(0).type == someTemplate.templateFields[0].get(0).type &&
    typeof someTemplate.templateFields[0].get(0).value === 'undefined'
)
        console.log('success!');

Is there a pattern to describe this relationship or am I going about this all wrong?

  • "Is there a pattern to describe a class that symbolises an instance of an instance of another class?" Inheritance. – radarbob Jul 8 '18 at 5:04
  • @radarbob Yes, the thing I had in mind would have been to inherit from a class generated completely at runtime (derived from another class but with certain properties filled in). However this doesn't seem to be possible in TypeScript and would also be unnecessarily complicated. I added an answer describing the solution I opted for. – Jan Hettenkofer Jul 8 '18 at 12:09
  • I think you are overloading the word instance to mean two very different things. However, you are talking about Template is if it were a class, and a Job as if it were an object. The fact that Template is itself an instance of an object isn't relevant to helping understand the concept. – Berin Loritsch Jul 9 '18 at 18:49
  • An instance of a class is an object. What is an instance of an object? – Tomas Zubiri Nov 22 '18 at 7:27
  • "It seems natural to have a Template class that contains all the info of how this specific template is structured (e.g. which parts are personaliseable, what assets are required etc...)." The template, as described by yourself in the preceding paragraph, should hold a collection of assets and a json. What is this about a template being personalisable? – Tomas Zubiri Nov 22 '18 at 7:32
3

The question is, what do you want to achieve by using this design. If what you're intending to do makes sense may also depend on it being the common way of achieving this in the language of your choice.

To me your explanation sounds to me like your Job inherits from your Template.

Your code looks like your Template is a Job factory.

The latter makes more sense to me. The purpose of inheritance is to achieve runtime polymorphism. A template is not a job and vice versa.

Making your Template a Job factory makes perfect sense. What I would recommend is that your Job takes a template reference in its constructor to create an aggregation (this is also called dependency injection).

In C++ I would declare friend class Template and make Job's constructor private so only your Job factory (aka. the Template class) can create jobs.

In Java I would make Job's constructor protected and stick them into a common namespace.

  • Thanks for your answer. It's true that a Job and a Template are very similar and it makes some sense to have Job inherit from Template. The way I originally built it (before asking the question) is actually quite close to the dependency injection approach. However this didn't quite work since the templateFields[] used by the Job needs to be partially independent of the Template's templateFields[] (namely, the values shouldn't carry over to other Jobs using the same template and TypeScript doesn't have a simple way to create deep copies). I will post an answer with the solution I came up with. – Jan Hettenkofer Jul 8 '18 at 10:19
  • I'm not familiar with type script. If you need to mutate the template instance I would have used composition and copy constructed the dependency. But this is a C++ answer and it sounds like it's not applicable. – kamikaze Jul 8 '18 at 12:04
  • Yes, that's kind of the problem. In TypeScript/JavaScript everything except primitives is a reference and while you can create a copy of the object, it will only be a shallow copy (meaning properties stay references to the original object's properties). This means that once I copy all the things that need copying, the code will be rather error prone. I learned programming with C and C++ and more often than not, I wish TS/JS were a bit more like them. – Jan Hettenkofer Jul 8 '18 at 12:23
0

From what I see here, it would make sense, to have the following components:

  • Template - Holds necessary information for render. Restricts modification of some of its parts. Renders itself

  • Template Builder - which configures the template with the necessary information.

The modeled flow would be something like this:

template = new TemplateBuilder(config).build();
template.render()

No "job" required.

Your "template" is something like a factory and your job does some configuration and carries out the "rendering", which from my point of view it shouldn't - because it is not its responsibility.

  • Thank you for your input. I quite like the idea of separating the Template as a data structure and a TemplateBuilder that configures that structure. However, I can't quite see how this would make a Job superfluous (since a Template with a particular structure can hold different data which it doesn't know about yet). I will add an answer to describe the solution I came up with after taking the two answers into account. – Jan Hettenkofer Jul 8 '18 at 10:13
0

Alright, here's my thoughts after hearing your suggestions:

First of all, a Template really should only be a data class that reads a config file and then exists in that state without doing much.

There are two ways a Template can be interacted with:

1) its configuration can be changed (i.e. TemplateFields added/removed, assets changed, previews generated etc...)

2) its fields can be filled with values and it can be rendered

I used to have the first one blended in with the Template data and the second one was modelled as a Job.

So, taking that into account, I came up with this:

// this is a necessity for mixins in TypeScript
type Constructor<T> = new(...args: any[]) => T;

// the "pure data" template class
class Template {
    templateFieldGroups : TemplateFieldGroup[];
    // other properties

    constructor() {
        // load from disk
    }
}

// The mixin to add Job-specific functionality (saving TemplateField values and rendering)
function JobMixin<T extends Constructor<Template>>(Base : T) {
    return class extends Base {
        constructor(...args: any[]) {
            super(...args);
        }

        render(){}
        saveValuesToDB(){}
        loadValuesFromDB(){}
    }
}

// The mixin to add management functionality
function TemplateManagerMixin<T extends Constructor<Template>>(Base : T) {
    return class extends Base {
        constructor(...args: any[]) {
            super(...args);
        }

        createPreviews(){}
    }
}

// The class that will be used to configure the templates
class TemplateManager extends TemplateManagerMixin(Template) {
}

// The class that will be used to handle specific template instances and render them
class Job extends JobMixin(Template) {
}

I don't know if this is really the very best way to do it, but it does appear to be fairly intuitive from a usability perspective to me.

Thank you again to kamikaze and Thomas Junk for your input.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.