js-software-design-patterns

    1.1.1 • Public • Published

    JS Software Design Patterns

    TOC

    Usage

    import * as patterns from 'js-software-design-patterns';

    Categories

    Creational

    Well known creational patterns

    Builder

    Used to build classes that have minor changes from instance to instance efficiently. Any class that has a set[A-Z][a-zA-Z0-9]* method, will have that method extracted and used to build the instance. Setters should only ever have one property passed.

    import { Builder} from 'js-software-design-patterns'
     
    const Dog {
        setAge(age){ this.age = age }
        setBreed(breed){ this.breed = breed }
    }
     
    const builder = new Builder(Dog);
    builder.breed = 'husky';
    builder.age = .5;
    const puppy = builder.build();
    // expected { breed: 'husky', age: .5 }
     
    buider.age = 10;
    const adult = builder.build();
    // expected { breed: 'husky', age: 10 }

    Curry

    Provides a currying function for partial function calls. Calling curry requires at minimum a function, with any number of optional arguments to be pass to function pointer. The return value of the call is a function that will curry all arguments until no more arguments are passed, which at that point, it will execute the initial function passing all previous arguments the in the order they were received.

    import { curry } from 'js-software-design-patterns';
     
    const add = (...args) => args.reduce((sum, next) => sum + next, 0);
     
    // start the curry
    let curriedAdder = curry(add, 1, 2, 3);
     
    // continue to curry
    curriedAdder = curriedAdder(4);
    curriedAdder = curriedAdder(5,6);
     
    // end the curry and execute the initial function (add)
    const sum = curriedAdder();
     
    console.log(sum);
    // expected output - 21

    Factory

    Provides a generic factory pattern. Because this is generic and meant to set up another factory of enumerated class instances, a method of setEnums is required to make the class strict on what is can instantiate. Also, during setup, a method setLine is provided that will associate an item name in the enum list to reference which class it should construct.

    import { Factory } from 'js-software-design-patterns';
    import { Cat, Dog, Parrot } from './my-animal-classes';
     
    // construct a generic factory
    const animalFactory = new Factory();
     
    // factory needs to be fed well defined production lines
    animalFactory.setEnums(['CAT', 'DOG', 'PARROT'])
     
    // link the production line keys to the Class that they will construct
    animalFactory.setLine('CAT', Cat);
    animalFactory.setLine('DOG', Dog);
    animalFactory.setLine('PARROT', Parrot);
     
    export default animalFactory;
    import AnimalFactory from './animal-factory'
     
    // have factory construct basic classes
    const dog = AnimalFactory.get('DOG');
    const cat = AnimalFactory.get('CAT');
     
    // have factory construct class with constructor arguments
    const parrot = AnimalFactory.get('PARROT', 'Cockatoo', 6, 'Crackers')

    makeLazy

    Provides a generic lazy initialization pattern function. The methods of the class will act as a trigger to instantiate the class. This pattern is especially useful when you need objects in a wide scope but do not know when they will be used or by which instance methods to which the lazy object belongs. Note, methods must be instance methods when constructed; anonymous methods (arrow operators) will not be referenced. Static class methods are obviously spurious to an instance. Class instance properties will not trigger a construction of the instance; use set and get method instead.

    import { makeLazy } from 'js-software-design-patterns';
     
    class Dog {
        bark(){ console.log('bark!!!') }
        eat(){ }
        sleep: () => {}
        set age(age){ this._age = age }
        get age(){ return this._age }
    }
     
    // lazily set up a reference to the constructor
    const lazyDog = makeLazy(Dog);
     
    // all Dog methods are available to the lazyDog wrapper. Dog has not been instantiated yet
    lazyDog.bark();
    // A Dog instance has been created
    // Expected output
    //  - bark!!!
     
    // from this point on, all methods called belonging to Dog are called from the managed instance
    lazyDog.eat();
     
     
    // example of failure where arrow operators will not work
    const brokenLazyDog = makeLazy(Dog);
    brokenLazyDog.sleep();
    // expected output - sleep is not defined
     
     
    // example of using set and get syntax
    const blueDog = makeLazy(Dog);
    blueDog.color = 'blue';
    const color = blueDog.color;
    console.log(color === 'blue');
    // expected output - true
     
     
    // example of getting the actual lazy instance if needed
    const instance = lazyDog.getInstance();
    console.log(instance instanceof Dog)
    // expected output - true

    Behavioral

    Well known behavioral patterns

    Chain of Responsibility

    Implementation of a generic Chain of Responsibility class.

    import { ChainOfResponsibility } from 'js-software-design-patterns';
     
    class Example extends ChainOfResponsibility {
        constructor(number = 0){
            super();
            this.number = number;
        }
     
        executeOn = (number = 0) => this.number + number;
    }
     
     
    const one = new Example(1);
    const two = new Example(2);
     
    one.appendNext(two);
     
    console.log(one.execute());
    // outputs 3
     
    console.log(two.execute());
    // outputs 2

    Command Pattern

    Implementation of the command pattern using function pointers rather than command objects.

    import Commander from 'js-software-design-patterns';
     
    const commander = new Commander();
     
    class LightSwitch {
        static turnOn = () => console.log('Turned on the light')
    }
     
    class Fan {
        static turnOn = () => console.warn('Turned on the fan')
    }
     
    class AC {
        static coolHouse = (temp) => console.log(`Turned down the AC to ${temp}`)
    }
     
    commander.register('turn on', LightSwitch.turnOn);
    commander.register('turn on', Fan.turnOn);
    commander.register('make cooler', AC.coolHouse);
    commander.register('make cooler', Fan.turnOn);
     
     
    // event driven execution
    commander.execute('turn on');
    // expected output
    //  Turned on the light
    //  Turned on the fan
     
     
    // event drive execution
    commander.execute('make cooler', 65);
    // expected output
    //  - Turned down the AC to 65
    //  - Turned on the fan

    Momento

    Implementation of a momento pattern. The momento pattern allows you to save a state of an object or instance and then recover that previously saved state. This mutates the state of the object and will not work on frozen/immutable objects. A single momento instance, once used to save a momento of an originating object, is permanently bound to that originator of the momento and cannot be used by other originating instances.

    import { Momento } from 'js-software-design-patterns';
     
    const fooBar = {
        foo: 'foo',
        bar: 'bar',
    }
     
    const foobarMomento = new Momento();
     
    // Save the current state of fooBar
    foobarMomento.save(fooBar);
     
    fooBar.foo = 'bar';
     
    // Now recover the previous state of fooBar
    foobarMomento.recover(fooBar)
    console.log(fooBar.foo);
    // Expected output - foo
     
    delete fooBar.bar;
    foobarMomento.recover(fooBar);
    console.log(fooBar.bar);
    // Expected output - bar
     
    fooBar.fooBar = 'fooBar';
    foobarMomento.recover(fooBar);
    console.log(fooBar.fooBar);
    // Expected output - undefined
     
    fooBar.fooBar = 'fooBar';
    foobarMomento.save(fooBar);
    foobarMomento.recover(fooBar);
    console.log(fooBar);
    // Expected output
    // {
    //      foo: 'foo'
    //      bar: 'bar'
    //      fooBar: 'fooBar'
    // }

    PubSub (Observer Pattern)

    A declarative namespaced pubsub pattern.

    import { PubSub } from 'js-software-design-patterns';
     
    const pubSub = new PubSub();
     
    const fox = pubSub.getPublisher('Fox Network', 'Fox Sports', 'Fox News');
    const espn = pubSub.getPublisher('ESPN Network', 'ESPN College', 'ESPN 2', 'ESPN Ocho');
     
    const subscriber = pubSub.getSubscriber('Sam Subscriber', {'Fox Network': ['Fox Sports'],'ESPN Network': ['ESPN Ocho']});
    subscriber.setOnPublish( (publisher, channel, data) => { console.log(publisher, channel, data) });
     
    // Positive case
    fox.publish('Fox Sports', 'Bears win');
    // Expect "Fox", "Fox Sports", "Bears win" to be printed
     
    // Negative case
    espn.publish('ESPN College', 'Texas loses');
    // Expect nothing to happen
     
    // Add and subscribe to channel after it is created.
    espn.createChannel('ESPN Golf');
    subscriber.subscribe(espn, 'ESPN Golf');
     
    // Unsubscribe from a channel
    subscriber.unsubscribe(espn, 'ESPN Golf');
     
    // Calling get publishers on an existing publisher returns the same publisher instance
    const sportsChannel = pubSub.getPublisher('ESPN Network');
    console.log(sportsChannel === espn);
    // Expect true.
     
    // Calling subscriber ALWAYS returns a new instance regardless of the name
    const otherSubscriber = pubSub.getSubscriber('Sam Subscriber', {'Fox Network': ['Fox Sports'],'ESPN Network': ['ESPN Ocho']});
    console.log(subscriber === otherSubscriber);
    // Expect false.
     

    Structural

    Well known structural patterns

    Bridge

    A take on the well known bridge pattern. Allows for any two methods of class instances to be joined, resulting in a return of an executable function. The bridge function takes scope to be applied to a function and then returns a to function to join another scope and a function. The to function when called then returns an execute function that accepts the same argument parameters as the initial function passed to bridge. It then calls the first joined function and using its return value as arguments to the second joined function, calls the second, returning its return value.

    import { bridge } from 'js-software-design-patterns';
     
    class Remote {
        changeChannelTo = (channel) => {
            console.log(`Remote changing channel to ${channel}`)
            return channel;
        }
    }
     
    class TV {
        changeChannelTo = (channel) => console.log(`TV set to channel ${channel}.`)
    }
     
    const remote = new Remote();
    const tv = new TV();
     
    const to = bridge(remote, remote.changeChannelTo);
    const execute = to(tv, tv.changeChannelTo);
    execute(20);
    // expected output -
    //      Remote changing channel to 20.
    //      TV set to channel 20.

    Changelog

    v1.0.0

    - adds curry function
    

    v0.5.1

    - updates description of how to use momento
    - adds TOC to readme
    

    v0.5.0

    - fixes export syntax for structural exports
    - adds momento pattern
    

    Install

    npm i js-software-design-patterns

    DownloadsWeekly Downloads

    16

    Version

    1.1.1

    License

    MIT

    Unpacked Size

    86.5 kB

    Total Files

    32

    Last publish

    Collaborators

    • anthonykulis