【问题标题】:Javascript polymorphism without OOP classes没有 OOP 类的 Javascript 多态性
【发布时间】:2019-04-16 00:50:02
【问题描述】:

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

例如:

class Field {...}

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

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

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

这将获取所有字段值。请注意,图书馆并不关心它是什么字段。

这样开发人员 A 创建了库,开发人员 B 可以创建新字段:AutocompleterField。

他可以在不更改库代码(Forms.js)的情况下在 AutocompleterField 中添加方法。

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

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

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

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

那么有什么好的方法可以在不使用面向对象编程的情况下解决 javascript 中的问题。

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

谢谢

【问题讨论】:

  • 供应商和工厂。只是没有类型安全。
  • "案例陈述,但这违反了原则" - 哪个原则?如果你不使用 OOP,你就没有 OOP 原则。此外,JS 完全符合 OOP,只是与 OOP 的风格不同,例如Java。
  • @Amadan 不是 OOP 并不意味着它不能遵循“开闭”原则。在我看来,工厂/供应商是这一理念的一部分。
  • "制作不同的类型" - 您的示例似乎将 class 语法与类型混淆了。类型在 JS 中是隐含的。将它们视为您无需在任何地方声明的不可见接口。
  • "不使用面向对象的编程" - 不,你不能在没有对象的情况下使用 JS。而且多态性肯定需要对象。

标签: javascript functional-programming


【解决方案1】:

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

// 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 这样的东西,因为它会使事情变得更复杂,但你可能想查一下。

【讨论】:

    【解决方案2】:

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

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

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

    你似乎在这里confuse classes with types。您根本不需要 JS class 语法来创建对象。

    你可以拥有

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

    并在您的 Forms 实例中使用这两个。

    【讨论】:

      【解决方案3】:

      取决于“多态性”的含义。 Haskell、Scala 或 PureScript 中的类型类提供了所谓的 ad-hoc polymorphism —— 这种分派通常通过传递 witness objects 来实现额外的函数参数,然后它将知道如何执行多态功能。

      例如,以下 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 的证人。

      【讨论】:

        【解决方案4】:

        您可以使用工厂模式来确保遵循开闭原则。 这个原则说“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭”。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-09-15
          • 1970-01-01
          • 1970-01-01
          • 2011-09-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-10-04
          相关资源
          最近更新 更多