没有OOP类的Java语言多态性

时间:2018-11-13 08:27:45

标签: javascript functional-programming

在JS或OOP语言中,通过创建不同的类型来创建多态性。

例如:

class Field {...}

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

想象一下我有一些方法的库form.js:

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

这将获取所有字段值。请注意,图书馆不在乎它是哪个字段。

通过这种方式,开发人员A创建了库,而开发人员B可以创建新字段:AutocompleterField。

他可以在AutocompleterField中添加方法,而无需更改库代码(Forms.js)。

如果我在JS中使用函数式编程方法,该如何实现?

如果对象中没有方法,我可以使用case语句,但这违反了原理。与此类似:

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

如果开发者B添加新类型,他应该更改库代码。

那么有什么好办法可以解决javascript问题,而无需使用面向对象的编程。

我知道Js既不是OOP也不是FP,但是无论如何。

谢谢

4 个答案:

答案 0 :(得分:3)

JavaScript是一种多用途语言,您当然可以用不同的方式解决它。切换到函数式编程时,答案非常简单:使用函数!您的示例的问题是:如此简单,您只需三行就可以完成完全相同的操作:

// 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']

关键是:Field::getValue()是如何实现的,返回是什么?或更准确地说:DropdownField::getValue()AutocompleteField::getValue()有何不同,例如与NumberField::getValue()有何不同?它们都只是返回值吗?他们返回一对名称和值吗?他们甚至需要与众不同吗?

问题在于,您的Field类及其继承类是否因其getValue()方法的工作方式而有所不同,还是由于它们具有的其他功能而有所不同?例如,文本字段的“自动完成”功能与(或不应)与从中获取值的方式有关。

如果您确实需要以不同的方式读取值,则可以实现一个函数,该函数采用{fieldtype: readerFunction}对的map / dictionary / object / POJO:

/* 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']

注意:我故意在这里没有提到IO monad之类的东西,因为它会使事情变得更复杂,但是您可能希望查找它。

答案 1 :(得分:2)

  

在JS或OOP语言中,通过创建不同的类型来创建多态性。

是的。或者,通过在不同对象中实现 same 类型接口。

  

在没有OOP类的情况下如何使用Javascript多态性

您似乎在confuse classes with types。您完全不需要JS class语法来创建对象。

你可以拥有

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

,然后在您的Forms实例中使用两者。

答案 2 :(得分:2)

取决于您所说的“多态性”。在Haskell,Scala或PureScript中提供了所谓的 ad hoc多态性类型类,这种分派通常是通过将见证对象与其他函数参数,它们随后将知道如何执行多态功能。

例如,以下PureScript代码(来自docs)为某些类型提供了show函数:

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]

它被编译为以下JS(我简称):

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 ]);

这里绝对没有魔术,只有一些附加参数。在实际上知道具体类型的“顶部”,您必须传递匹配的具体见证对象。

在您的情况下,您会像HasValue证人那样传递不同的形式。

答案 3 :(得分:1)

您可以使用工厂模式来确保遵循开/关原则。 这项原则说:“软件实体(类,模块,功能等)应该开放以进行扩展,而封闭以进行修改”。

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('Autocompleter', (field) => [ 3, 2, 1 ]);

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

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

现在,当您要注册新的字段类型时,可以在工厂为其注册一个提供程序,而不必修改您的字段代码。

new Field().getFieldsValues();