admin管理员组

文章数量:1418427

In JS or OOP language the polymorhpism is created by making different types.

For example:

class Field {...}

class DropdownField extends Field {
  getValue() { 
     //implementation ....
  }
}

Imagine I have library forms.js with some methods:

class Forms {
    getFieldsValues() {
      let values = [];
      for (let f of this.fields) {
          values.push(f.getValue());
      }
      return values;
    }
}

This gets all field values. Notice the library doesnt care what field it is.

This way developer A created the library and developer B can make new fields: AutopleterField.

He can add methods in AutopleterField withouth changing the library code (Forms.js) .

If I use functional programming method in JS, how can I achieve this?

If I dont have methods in object i can use case statements but this violates the principle. Similar to this:

if (field.type == 'DropdownField')...
else if (field.type == 'Autopleter')..

If developer B add new type he should change the library code.

So is there any good way to solve the issue in javascript without using object oriented programming.

I know Js isnt exactly OOP nor FP but anyway.

Thanks

In JS or OOP language the polymorhpism is created by making different types.

For example:

class Field {...}

class DropdownField extends Field {
  getValue() { 
     //implementation ....
  }
}

Imagine I have library forms.js with some methods:

class Forms {
    getFieldsValues() {
      let values = [];
      for (let f of this.fields) {
          values.push(f.getValue());
      }
      return values;
    }
}

This gets all field values. Notice the library doesnt care what field it is.

This way developer A created the library and developer B can make new fields: AutopleterField.

He can add methods in AutopleterField withouth changing the library code (Forms.js) .

If I use functional programming method in JS, how can I achieve this?

If I dont have methods in object i can use case statements but this violates the principle. Similar to this:

if (field.type == 'DropdownField')...
else if (field.type == 'Autopleter')..

If developer B add new type he should change the library code.

So is there any good way to solve the issue in javascript without using object oriented programming.

I know Js isnt exactly OOP nor FP but anyway.

Thanks

Share Improve this question asked Nov 13, 2018 at 8:27 user2693928user2693928 6
  • 1 Providers and factories. Just without type safty. – NtFreX Commented Nov 13, 2018 at 8:29
  • "case statements but this violates the principle" - which principle? If you don't use OOP, you don't have OOP principles. Also, JS is totally OOP-pliant, just not the same flavour of OOP like e.g. Java. – Amadan Commented Nov 13, 2018 at 8:34
  • @Amadan Just because it's not OOP doesn't mean it cant follow the "Open-Close" principle. And in my opinion factories/providers are a part of that philosophy. – NtFreX Commented Nov 13, 2018 at 8:38
  • "making different types" - your example seems to confuse class syntax with types. Types are implicit in JS. Consider them as invisible interfaces that you don't have to declare anywhere. – Bergi Commented Nov 13, 2018 at 8:46
  • "without using object oriented programming" - no, you cannot use JS without objects. And polymorphism definitely requires objects. – Bergi Commented Nov 13, 2018 at 8:48
 |  Show 1 more ment

4 Answers 4

Reset to default 4

JavaScript being a multi-purpose language, you can of course solve it in different ways. When switching to functional programming, the answer is really simple: Use functions! The problem with your example is this: It is so stripped down, you can do exactly the same it does with just 3 lines:

// getValue :: DOMNode -> String
const getValue = field => field.value;

// readForm :: Array DOMNode -> Array String
const readForm = formFields => formFields.map(getValue);

readForm(Array.from(document.querySelectorAll('input, textarea, select')));
// -> ['Value1', 'Value2', ... 'ValueN']

The critical thing is: How is Field::getValue() implemented, what does it return? Or more precisely: How does DropdownField::getValue() differ from AutopleteField::getValue() and for example NumberField::getValue()? Do all of them just return the value? Do they return a pair of name and value? Do they even need to be different?

The question is therefor, do your Field classes and their inheriting classes differ because of the way their getValue() methods work or do they rather differ because of other functionality they have? For example, the "autoplete" functionality of a textfield isn't (or shouldn't be) tied to the way the value is taken from it.

In case you really need to read the values differently, you can implement a function which takes a map/dictionary/object/POJO of {fieldtype: readerFunction} pairs:

/* Library code */

// getTextInputValue :: DOMNode -> String
const getTextInputValue = field => field.value;

// getDropdownValue :: DOMNode -> String
const getDropdownValue = field => field.options[field.selectedIndex].value;

// getTextareaValue :: DOMNode -> String
const getTextareaValue = field => field.textContent;

// readFieldsBy :: {String :: (a -> String)} -> DOMNode -> Array String
readFieldsBy = kv => form => Object.keys(kv).reduce((acc, k) => {
  return acc.concat(Array.from(form.querySelectorAll(k)).map(kv[k]));
}, []);



/* Code the library consumer writes */

const readMyForm = readFieldsBy({
  'input[type="text"]': getTextInputValue,
  'select': getDropdownValue,
  'textarea': getTextareaValue
});

readMyForm(document.querySelector('#myform'));
// -> ['Value1', 'Value2', ... 'ValueN']

Note: I intentionally didn't mention things like the IO monad here, because it would make stuff more plicated, but you might want to look it up.

In JS or OOP language the polymorhpism is created by making different types.

Yes. Or rather, by implementing the same type interface in different objects.

How can I use Javascript polymorphism without OOP classes

You seem to confuse classes with types here. You don't need JS class syntax to create objects at all.

You can just have

const autopleteField = {
    getValue() {
        …
    }
};
const dropdownField = {
    getValue() {
        …
    }
};

and use the two in your Forms instance.

Depends on what you mean by "polymorphism". There's the so-called ad-hoc polymorphism which type classes in Haskell, Scala, or PureScript provide -- and this kind of dispatch is usually implemented by passing witness objects along as additional function arguments, which then will know how to perform the polymorphic functionality.

For example, the following PureScript code (from the docs), which provides a show function for some types:

class Show a where
  show :: a -> String

instance showString :: Show String where
  show s = s

instance showBoolean :: Show Boolean where
  show true = "true"
  show false = "false"

instance showArray :: (Show a) => Show (Array a) where
  show xs = "[" <> joinWith ", " (map show xs) <> "]"

example = show [true, false]

It gets piled to the following JS (which I shortened):

var Show = function (show) {
    this.show = show;
};

var show = function (dict) {
    return dict.show;
};

var showString = new Show(function (s) {
    return s;
});

var showBoolean = new Show(function (v) {
    if (v) {
        return "true";
    };
    if (!v) {
        return "false";
    };
    throw new Error("Failed pattern match at Main line 12, column 1 - line 12, column 37: " + [ v.constructor.name ]);
});

var showArray = function (dictShow) {
    return new Show(function (xs) {
        return "[" + (Data_String.joinWith(", ")(Data_Functor.map(Data_Functor.functorArray)(show(dictShow))(xs)) + "]");
    });
};

var example = show(showArray(showBoolean))([ true, false ]);

There's absolutely no magic here, just some additional arguments. And at the "top", where you actually know concrete types, you have to pass in the matching concrete witness objects.

In your case, you would pass around something like a HasValue witness for different forms.

You could use a the factory pattern to ensure you follow the open close principle. This principle says "Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification".

class FieldValueProviderFactory {
    getFieldValue(field) {
        return this.providers.find(p => p.type === field.type).provider(field);
    }
    registerProvider(type, provider) {
        if(!this.providers) {
            this.providers = [];
        }

        this.providers.push({type:type, provider:provider});
    }
}

var provider = new FieldValueProviderFactory();
provider.registerProvider('DropdownField', (field) => [ 1, 2, 3 ]);
provider.registerProvider('Autopleter', (field) => [ 3, 2, 1 ]);

class FieldCollection {
    getFieldsValues() {
        this.fields = [ { type:'DropdownField',value:'1' }, { type:'Autopleter',value:'2' } ];

        let values = [];
        for (let field of this.fields) {
            values.push(provider.getFieldValue(field));
        }
        return values;
    }
}

Now when you want to register new field types you can register a provider for them in the factory and don't have to modify your field code.

new Field().getFieldsValues();

本文标签: functional programmingJavascript polymorphism without OOP classesStack Overflow